Some checks failed
Architecture Governance / architecture (push) Has been cancelled
137 lines
4.8 KiB
YAML
137 lines
4.8 KiB
YAML
name: Deploy Frontend
|
|
|
|
# Multi-tenant: one bundle serves every customer domain, so a single deploy
|
|
# updates all of them at once. There is no per-tenant build or per-tenant deploy.
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
workflow_dispatch:
|
|
inputs:
|
|
ref:
|
|
description: Branch or SHA to deploy
|
|
required: false
|
|
default: main
|
|
|
|
concurrency:
|
|
group: deploy-frontend
|
|
cancel-in-progress: false # never abandon a half-finished release swap
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on: urgent-deploy
|
|
environment: production
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ github.event.inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Enforce boundaries
|
|
run: npm run arch:check
|
|
|
|
- name: Build
|
|
run: npm run build -- --configuration production
|
|
|
|
- name: Resolve build output
|
|
id: dist
|
|
run: |
|
|
set -euo pipefail
|
|
# @angular/build:application emits into dist/<name>/browser.
|
|
# Fall back to the flat layout so this survives a builder change.
|
|
if [ -d dist/dexarmarket/browser ]; then
|
|
DIR=dist/dexarmarket/browser
|
|
elif [ -f dist/dexarmarket/index.html ]; then
|
|
DIR=dist/dexarmarket
|
|
else
|
|
echo "no build output found under dist/dexarmarket" >&2
|
|
ls -R dist || true
|
|
exit 1
|
|
fi
|
|
test -f "$DIR/index.html" || { echo "$DIR has no index.html" >&2; exit 1; }
|
|
echo "dir=$DIR" >> "$GITHUB_OUTPUT"
|
|
echo "Deploying from $DIR ($(find "$DIR" -type f | wc -l) files)"
|
|
|
|
- name: Configure SSH
|
|
env:
|
|
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
|
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
|
|
run: |
|
|
set -euo pipefail
|
|
test -n "$DEPLOY_SSH_KEY" || { echo "secret DEPLOY_SSH_KEY is empty" >&2; exit 1; }
|
|
test -n "$DEPLOY_KNOWN_HOSTS" || { echo "secret DEPLOY_KNOWN_HOSTS is empty" >&2; exit 1; }
|
|
mkdir -p ~/.ssh
|
|
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key
|
|
chmod 600 ~/.ssh/deploy_key
|
|
# Pinned host key, so a MITM or a rebuilt server fails the deploy
|
|
# instead of being trusted silently.
|
|
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > ~/.ssh/known_hosts
|
|
chmod 644 ~/.ssh/known_hosts
|
|
|
|
- name: Upload release
|
|
env:
|
|
HOST: ${{ secrets.DEPLOY_HOST }}
|
|
USER: ${{ secrets.DEPLOY_USER }}
|
|
SRC: ${{ steps.dist.outputs.dir }}
|
|
run: |
|
|
set -euo pipefail
|
|
RELEASE="${GITHUB_SHA::12}"
|
|
echo "RELEASE=$RELEASE" >> "$GITHUB_ENV"
|
|
SSH="ssh -i ~/.ssh/deploy_key -o BatchMode=yes"
|
|
$SSH "$USER@$HOST" "mkdir -p /srv/marketplaces/releases/$RELEASE/frontend"
|
|
tar -C "$SRC" -czf - . | $SSH "$USER@$HOST" \
|
|
"tar -xzf - -C /srv/marketplaces/releases/$RELEASE/frontend"
|
|
|
|
- name: Activate release
|
|
env:
|
|
HOST: ${{ secrets.DEPLOY_HOST }}
|
|
USER: ${{ secrets.DEPLOY_USER }}
|
|
run: |
|
|
set -euo pipefail
|
|
ssh -i ~/.ssh/deploy_key -o BatchMode=yes "$USER@$HOST" bash -euo pipefail <<EOSSH
|
|
BASE=/srv/marketplaces
|
|
REL="\$BASE/releases/$RELEASE"
|
|
test -f "\$REL/frontend/index.html" || { echo "upload incomplete, refusing to swap" >&2; exit 1; }
|
|
# ln -T onto a temp name then mv: the swap is atomic, so no request
|
|
# is ever served from a half-updated root.
|
|
ln -sfnT "\$REL" "\$BASE/current.new"
|
|
mv -Tf "\$BASE/current.new" "\$BASE/current"
|
|
sudo /bin/systemctl reload nginx
|
|
# Keep the last 5 releases so a rollback is a symlink change.
|
|
ls -1dt "\$BASE"/releases/*/ | tail -n +6 | xargs -r rm -rf
|
|
echo "active: \$(readlink -f \$BASE/current)"
|
|
EOSSH
|
|
|
|
- name: Verify
|
|
env:
|
|
HOST: ${{ secrets.DEPLOY_HOST }}
|
|
USER: ${{ secrets.DEPLOY_USER }}
|
|
run: |
|
|
set -euo pipefail
|
|
ssh -i ~/.ssh/deploy_key -o BatchMode=yes "$USER@$HOST" \
|
|
'curl -fsS -o /dev/null -w "health=%{http_code}\n" http://127.0.0.1/health &&
|
|
curl -fsS -o /dev/null -w "index=%{http_code}\n" http://127.0.0.1/'
|
|
|
|
- name: Report
|
|
if: always()
|
|
run: |
|
|
if [ "${{ job.status }}" = "success" ]; then
|
|
echo "Deployed ${GITHUB_SHA::12} to ${{ secrets.DEPLOY_HOST }}" >> "$GITHUB_STEP_SUMMARY"
|
|
else
|
|
echo "Deploy of ${GITHUB_SHA::12} FAILED. The previous release is still active — the symlink only moves after a successful upload." >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|