From 90f94df24b409018d2e1f223cb5afbbb325f6323 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Sat, 25 Jul 2026 20:57:56 -0700 Subject: [PATCH] ci: make backend indexer deploy self-contained + config-gated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two problems with the backend deploy, fixed together: 1. It errored on every main push with 'missing server host' because BACKEND_VPS_* wasn't set. A permanently-red job is a broken window — it trains people to ignore deploy alerts and hides real failures. Add a backend-cfg check job; deploy-backend now runs only when BACKEND_VPS_HOST is set and skips cleanly (neutral) otherwise, activating automatically once the host is provisioned. 2. It required a hand-placed /opt/pwap-indexer/.env on the host — a fragile out-of-band manual step. The deployed image runs only the indexer (src/index.js), which needs just WS_ENDPOINT beyond the fixed PORT/DB_PATH (no KYC/council service, so no seed/Supabase secret). Inject WS_ENDPOINT from the workflow (INDEXER_WS_ENDPOINT secret with a public default) via -e; drop the --env-file dependency. Config now lives in the pipeline, not a manual file on the box. --- .github/workflows/quality-gate.yml | 44 ++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml index f2aede76..7ebf85a8 100644 --- a/.github/workflows/quality-gate.yml +++ b/.github/workflows/quality-gate.yml @@ -753,12 +753,34 @@ jobs: # Host param via secret BACKEND_VPS_HOST (no hardcoded IP). If the backend # shares the web DEV VPS, set BACKEND_VPS_HOST = VPS_HOST (see backend/DEPLOY.md). # ======================================== + # Config gate: the backend indexer only deploys once its host is provisioned. + # Until BACKEND_VPS_HOST is set, deploy-backend skips cleanly (neutral, no false + # failure/alert) instead of erroring on "missing server host" every push. + backend-cfg: + name: Backend deploy config check + runs-on: pwap-runner + if: (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + outputs: + configured: ${{ steps.c.outputs.configured }} + steps: + - id: c + env: + BH: ${{ secrets.BACKEND_VPS_HOST }} + run: | + if [ -n "$BH" ]; then + echo "configured=true" >> "$GITHUB_OUTPUT" + else + echo "::notice::BACKEND_VPS_HOST unset — backend indexer deploy skipped (provision the host to enable)" + echo "configured=false" >> "$GITHUB_OUTPUT" + fi + deploy-backend: name: Deploy Backend (indexer) runs-on: pwap-runner - needs: [telegram-gate, build-image-backend] + needs: [telegram-gate, build-image-backend, backend-cfg] if: | always() && + needs.backend-cfg.outputs.configured == 'true' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) && needs.telegram-gate.result == 'success' && ((github.event_name == 'push' && needs.build-image-backend.result == 'success') || @@ -811,7 +833,7 @@ jobs: username: ${{ secrets.BACKEND_VPS_USER }} key: ${{ secrets.BACKEND_VPS_SSH_KEY }} port: ${{ secrets.BACKEND_VPS_SSH_PORT || 22 }} - envs: IMAGE_REF,NEW_SHA,GHCR_USER,GHCR_TOKEN + envs: IMAGE_REF,NEW_SHA,GHCR_USER,GHCR_TOKEN,WS_ENDPOINT,HOST_PORT command_timeout: 10m script_stop: true script: | @@ -820,6 +842,7 @@ jobs: IMAGE="$REGISTRY_IMAGE:$NEW_SHA" STATE_DIR="/opt/pwap-indexer" NAME="pwap-indexer" + HOST_PORT="${HOST_PORT:-3001}" mkdir -p "$STATE_DIR" PREV="$(cat "$STATE_DIR/.deploy-sha" 2>/dev/null || echo "")" echo "Deploying $IMAGE (previous: ${PREV:-none})" @@ -832,18 +855,25 @@ jobs: docker rm -f "$NAME" >/dev/null 2>&1 || true # Named volume pwap-indexer-db = stateful sqlite DB. NEVER removed # by this script — it is created on first run and reused forever. + # Config comes from the workflow (GitHub secrets/vars), NOT a + # hand-placed file on the host. The indexer (src/index.js) only + # needs WS_ENDPOINT beyond the fixed PORT/DB_PATH; it runs no + # KYC/council service, so no seed/Supabase secret is required. + # Host port is configurable (HOST_PORT, default 3001) so the indexer + # can co-exist with whatever else runs on the box; the container + # always listens on 3001 internally. docker run -d --name "$NAME" --restart unless-stopped \ - -p 3001:3001 \ + -p "${HOST_PORT}:3001" \ -v pwap-indexer-db:/data \ -e DB_PATH=/data/transactions.db \ -e PORT=3001 \ - --env-file "$STATE_DIR/.env" \ + -e WS_ENDPOINT="$WS_ENDPOINT" \ "$img" } healthy () { for i in $(seq 1 12); do - if curl -fsS --max-time 5 "http://localhost:3001/health" >/dev/null 2>&1; then + if curl -fsS --max-time 5 "http://localhost:${HOST_PORT}/health" >/dev/null 2>&1; then return 0 fi echo "health attempt $i/12 failed, retry in 5s..." @@ -880,6 +910,10 @@ jobs: NEW_SHA: ${{ steps.sha.outputs.sha }} GHCR_USER: ${{ github.actor }} GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Non-secret chain endpoint; override via the INDEXER_WS_ENDPOINT secret if needed. + WS_ENDPOINT: ${{ secrets.INDEXER_WS_ENDPOINT || 'wss://rpc.pezkuwichain.io' }} + # Host port for the indexer (container is always 3001 internally). + HOST_PORT: ${{ secrets.INDEXER_HOST_PORT || '3001' }} - name: Post-deploy notification if: success()