diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 751656d..7f81f77 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -69,6 +69,12 @@ jobs: done curl -fsS http://localhost:8080/ | grep -q "Agentage Design System" \ || { echo "::error::showcase did not render"; docker logs ds; exit 1; } + # Assert provenance here too, so a build that failed to bake COMMIT_SHA + # fails before prod rather than as an unexplained post-deploy timeout. + got="$(curl -fsS http://localhost:8080/health | grep -o '"commit":"[0-9a-f]\{7,40\}"' | cut -d'"' -f4)" + [ "$got" = "$GITHUB_SHA" ] \ + || { echo "::error::/health commit '$got' != '$GITHUB_SHA'"; exit 1; } + echo "PASS image /health reports $got" env: GITHUB_SHA: ${{ github.sha }} @@ -107,13 +113,8 @@ jobs: run: echo "${{ secrets.GITHUB_TOKEN }}" | ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "docker login ghcr.io -u ${{ github.actor }} --password-stdin" - name: Deploy stack run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "cd /opt/agentage/ds && docker stack deploy -c docker-compose.yml agentage-ds --with-registry-auth" - - name: Wait + live smoke + - name: Verify deploy env: SITE_FQDN: ${{ vars.SITE_FQDN }} - run: | - for i in $(seq 1 40); do - curl -fsS "https://${SITE_FQDN}/" 2>/dev/null | grep -q "Agentage Design System" && { ok=1; break; } - sleep 5 - done - echo "Smoke: https://${SITE_FQDN}/" - [ -n "${ok:-}" ] || { echo "::error::${SITE_FQDN} never served the showcase"; exit 1; } + COMMIT_SHA: ${{ github.sha }} + run: bash scripts/verify-deploy.sh diff --git a/CLAUDE.md b/CLAUDE.md index 1e49566..c064a57 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,6 +36,7 @@ Single source of truth for the Agentage design system (OKLCH tokens + React comp - LIVE at https://ds.agentage.io since 2026-08-06. The `dev/` showcase deploys as its own Swarm stack (`agentage-ds`) behind Traefik on the main prod box. Dockerfile builds the static SPA → nginx-unprivileged; `docker-compose.yml` carries the Traefik labels; `.github/workflows/deploy.yml` builds → smokes → deploys on push to master. - Production-only (the platform-wide dev env was removed 2026-07-30). Gated on `vars.DEPLOY_ENABLED == 'true'` + the `production` environment (`vars.SITE_FQDN`, `SSH_PRIVATE_KEY` / `SSH_HOST` / `SSH_USER`). - Container healthcheck must probe `127.0.0.1`, not `localhost` — nginx binds IPv4 only; busybox wget picks `::1`. +- Post-deploy verification lives in `scripts/verify-deploy.sh` (estate standard, same shape as auth/dashboard): it asserts `https://${SITE_FQDN}/health` reports **this** commit before checking that the page renders. A content grep alone is fail-open — on a failed rollout Swarm keeps the old task serving and the grep still matches. ## Conventions diff --git a/scripts/verify-deploy.sh b/scripts/verify-deploy.sh new file mode 100755 index 0000000..b1052c1 --- /dev/null +++ b/scripts/verify-deploy.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Verify the deployed showcase from the public edge after `docker stack deploy`. +# 1. Serves-this-commit - the image bakes COMMIT_SHA into /health, so a failed +# rollout (Swarm keeps the OLD task serving) can't pass silently. Prefix match. +# 2. Renders - the SPA shell is really there, not just a live health file. +# Ordered commit-first on purpose: the content grep alone is fail-open, since the +# previous container answers it just as happily as the new one. +set -euo pipefail + +SITE_FQDN="${SITE_FQDN:-ds.agentage.io}" +want="${COMMIT_SHA:?COMMIT_SHA required}" + +# grep, not jq/python: the payload is a flat static file and the runner is +# guaranteed nothing beyond coreutils + curl. +read_commit() { + curl -sf "https://${SITE_FQDN}/health" 2>/dev/null | + grep -o '"commit":"[0-9a-f]\{7,40\}"' | cut -d'"' -f4 || true +} + +echo "-- serves-this-commit --" +ok= +for i in $(seq 1 30); do + got="$(read_commit)" + case "${want}" in + "${got:-__none__}"*) + echo "PASS showcase serving ${got}" + ok=1 + break + ;; + esac + echo " attempt $i/30: showcase commit='${got:-}' want='${want:0:12}...'" + [ "$i" -lt 30 ] && sleep 10 +done +[ -n "$ok" ] || { + echo "::error::${SITE_FQDN} is not serving ${want} (stale task - rollout did not land)" + exit 1 +} + +echo "-- renders --" +curl -fsS "https://${SITE_FQDN}/" | grep -q "Agentage Design System" || { + echo "::error::${SITE_FQDN} answered /health but did not render the showcase" + exit 1 +} +echo "PASS showcase renders" + +echo "Deploy verified against ${SITE_FQDN} (commit ${want:0:12}...)."