diff --git a/.gitea/actions/deploy/action.yml b/.gitea/actions/deploy/action.yml index c73d01d..8f5c62a 100644 --- a/.gitea/actions/deploy/action.yml +++ b/.gitea/actions/deploy/action.yml @@ -80,10 +80,13 @@ runs: fi fi + # Stamp the commit that was actually rsynced (the checked-out tree), not + # GITHUB_SHA, so a rollback of an older commit is recorded truthfully. + DEPLOYED=$(git rev-parse HEAD 2>/dev/null || echo "$GITHUB_SHA") SLUG=$(printf '%s' "$TARGET_PATH" | tr '/' '_') - ssh -n $SSHOPT "$R" "mkdir -p ~/.peptide-deploys/$STAGE && printf '%s %s %s\n' '$GITHUB_SHA' '$GITHUB_REPOSITORY' \"\$(date -u +%FT%TZ)\" > ~/.peptide-deploys/$STAGE/$SLUG" \ + ssh -n $SSHOPT "$R" "mkdir -p ~/.peptide-deploys/$STAGE && printf '%s %s %s\n' '$DEPLOYED' '$GITHUB_REPOSITORY' \"\$(date -u +%FT%TZ)\" > ~/.peptide-deploys/$STAGE/$SLUG" \ || echo "::warning::could not record deployed commit for deploy-health" - echo "Deployed ${GITHUB_SHA:0:7} to $STAGE:$TARGET_PATH" + echo "Deployed ${DEPLOYED:0:7} to $STAGE:$TARGET_PATH" - name: Health check (${{ inputs.stage }}) shell: bash diff --git a/.gitea/workflows/deploy-health.yml b/.gitea/workflows/deploy-health.yml new file mode 100644 index 0000000..6385dfd --- /dev/null +++ b/.gitea/workflows/deploy-health.yml @@ -0,0 +1,57 @@ +name: Deploy health (daily drift check) + +# Gitea rewrite of .github/workflows/deploy-health.yml. GitHub's version read +# GitHub Deployment objects, which Gitea does not have. This one compares what is +# ACTUALLY on the server: the deploy composite action stamps +# ~/.peptide-deploys/production/ with the deployed commit, and this job +# compares that stamp with each repo's main HEAD. Drift (or a missing stamp after +# cutover) opens one issue in peptide-e2e; it never opens duplicates. +on: + schedule: + - cron: '0 13 * * *' # 21:00 SGT, same slot as the GitHub job + workflow_dispatch: {} + +jobs: + drift: + name: Compare deployed commit with main + runs-on: peptide-vps + steps: + - uses: actions/checkout@v4 + - name: Check every deployable repo + shell: bash + env: + TOKEN: ${{ secrets.GITHUB_TOKEN }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PORT: ${{ secrets.SSH_PORT }} + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + PROD_ENABLED: ${{ vars.PROD_DEPLOY_ENABLED }} + run: | + set -euo pipefail + WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT + printf '%s\n' "$SSH_PRIVATE_KEY" > "$WORK/key"; chmod 600 "$WORK/key" + ssh-keyscan -p "$SSH_PORT" "$SSH_HOST" > "$WORK/kh" 2>/dev/null || true + SSHOPT="-n -i $WORK/key -p $SSH_PORT -o UserKnownHostsFile=$WORK/kh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=30 -o BatchMode=yes" + API="$GITHUB_SERVER_URL/api/v1"; H="Authorization: token $TOKEN" + problems="" + for row in $(python3 -c 'import json; d=json.load(open("tools/gitea-pipeline/repos.json")); [print(k+"="+v["target_path"]) for k,v in d.items() if not k.startswith("_") and v.get("target_path")]'); do + repo=${row%%=*}; tp=${row#*=}; slug=$(printf '%s' "$tp" | tr '/' '_') + head=$(curl -s -H "$H" "$API/repos/peptide-repo/$repo/branches/main" | python3 -c 'import json,sys;print(json.load(sys.stdin)["commit"]["id"])') + stamp=$(ssh $SSHOPT "$SSH_USERNAME@$SSH_HOST" "cat ~/.peptide-deploys/production/$slug 2>/dev/null" || true) + dep=${stamp%% *} + if [ -z "$dep" ]; then + msg="$repo: no Gitea production deploy recorded" + if [ "$PROD_ENABLED" = "true" ]; then problems="$problems\n- $msg (main is ${head:0:7})"; else echo "::notice::$msg (expected before cutover)"; fi + elif [ "$dep" != "$head" ]; then + problems="$problems\n- $repo: production has ${dep:0:7}, main is ${head:0:7}" + else + echo "OK $repo ${head:0:7}" + fi + done + [ -z "$problems" ] && { echo "No drift."; exit 0; } + printf "Drift found:%b\n" "$problems" + open=$(curl -s -H "$H" "$API/repos/$GITHUB_REPOSITORY/issues?state=open&type=issues&limit=50" | python3 -c 'import json,sys;print(sum(1 for i in json.load(sys.stdin) if i["title"]=="Deploy drift detected"))') + if [ "$open" -gt 0 ]; then echo "An open drift issue already exists; not opening another."; exit 1; fi + python3 -c 'import json,sys; print(json.dumps({"title": "Deploy drift detected", "body": "Daily deploy-health found production out of step with main:\n" + sys.argv[1].replace("\\n","\n") + "\n\nRe-run the repo pipeline on main (workflow_dispatch) or investigate the failed deploy. Opened by deploy-health.yml."}))' "$problems" > issue.json + curl -s -o /dev/null -w "issue HTTP %{http_code}\n" -H "$H" -H 'Content-Type: application/json' -X POST "$API/repos/$GITHUB_REPOSITORY/issues" --data-binary @issue.json + exit 1 diff --git a/.gitea/workflows/smoke.yml b/.gitea/workflows/smoke.yml new file mode 100644 index 0000000..c3d5435 --- /dev/null +++ b/.gitea/workflows/smoke.yml @@ -0,0 +1,24 @@ +name: Smoke Tests (manual) + +# Manual run of the staging smoke suite (the deploy pipeline runs it automatically +# via the smoke composite action). Gitea port of the workflow_dispatch half of +# .github/workflows/smoke.yml. +on: + workflow_dispatch: + inputs: + base_url: + description: 'Target URL (defaults to staging)' + required: false + default: 'https://staging.peptiderepo.com' + +jobs: + smoke: + name: Playwright smoke suite + runs-on: peptide-vps + timeout-minutes: 15 + steps: + - uses: https://git.protopeer.com/peptide-repo/peptide-e2e/.gitea/actions/smoke@main + with: + base_url: ${{ inputs.base_url }} + wp_admin_user: ${{ secrets.STAGING_WP_ADMIN_USER }} + wp_admin_password: ${{ secrets.STAGING_WP_ADMIN_PASSWORD }}