docs(backend): track backend deploy runbook (gitignored *.md override)

This commit is contained in:
2026-07-25 11:02:16 -07:00
parent 80d273ff11
commit 3c7c4192b9
+89
View File
@@ -0,0 +1,89 @@
# Backend (indexer) — Deploy Runbook
The backend is a runnable Node service (`src/index.js`) that indexes chain
transfers into a **stateful** local sqlite DB and serves a small read API. It is
built into a SHA-tagged, cosign-signed GHCR image and run as a Docker container
on the backend host by the `build-image-backend` + `deploy-backend` jobs in
`.github/workflows/quality-gate.yml`.
> The KYC/council service (`src/server.js`) is a separate surface (Supabase +
> sudo signer). It is not containerized by this pipeline; only the indexer is.
## Pipeline shape (mirrors the web deploy)
```
backend (npm ci + npm test + audit)
└─ telegram-gate (CEO approves in Telegram — SAME gate as web)
└─ build-image-backend (docker build ./backend → GHCR :<sha> + :latest, cosign sign)
└─ deploy-backend (cosign verify → ssh host → docker run → /health → auto-rollback)
```
- **Trigger:** only `main` or tags, `push`/`workflow_dispatch`. Never fork PRs
(the `if:` requires `refs/heads/main`|`refs/tags/*` + `packages:write`, which
fork PRs never get).
- **Rollback:** `gh workflow run quality-gate.yml -f rollback_to=<sha>` — skips
build, re-runs the old signed image. The deploy step also **auto-rolls-back**
to the previously-live SHA on a failed health check.
## STATEFUL DB — do not wipe (critical)
The indexer's sqlite file **is** the service's memory. It lives in the Docker
**named volume `pwap-indexer-db`**, mounted at `/data`, with
`DB_PATH=/data/transactions.db`.
- The image never contains the DB (see `.dockerignore` + `Dockerfile` `VOLUME`).
- `deploy-backend` does `docker rm -f` + `docker run` on every deploy/rollback
but **never touches the volume** — state survives every deploy and rollback.
- Never run `docker volume rm pwap-indexer-db` as part of a deploy. Back it up
before host maintenance:
`docker run --rm -v pwap-indexer-db:/data -v "$PWD:/backup" busybox tar czf /backup/indexer-db.tgz -C /data .`
## One-time host setup
On the backend host (as the `BACKEND_VPS_USER`):
```bash
mkdir -p /opt/pwap-indexer
# Runtime env for the container (owner-managed, NOT in git):
cat > /opt/pwap-indexer/.env <<'EOF'
WS_ENDPOINT=wss://rpc.pezkuwichain.io
PORT=3001
# DB_PATH is set by the deploy to /data/transactions.db — leave unset here.
EOF
```
Docker must be installed and the deploy user able to run it. Port `3001` is
published on the host; front it with the existing nginx/Cloudflare if it needs
to be public (remember `set_real_ip_from` behind the CF proxy).
## Required GitHub secrets
New (backend-specific):
| Secret | Purpose |
| --- | --- |
| `BACKEND_VPS_HOST` | Backend host address (no hardcoded IP in the workflow). **If the backend shares the web DEV VPS, set this equal to `VPS_HOST`.** |
| `BACKEND_VPS_USER` | SSH user on the backend host. |
| `BACKEND_VPS_SSH_KEY` | SSH private key for that user. |
| `BACKEND_VPS_SSH_PORT` | Optional; defaults to `22`. |
Reused from the existing web pipeline (no new value needed):
`GITHUB_TOKEN` (GHCR push/pull + cosign), `PEXSEC_BOT_TOKEN`,
`TELEGRAM_CEO_CHAT_ID`.
## Post-deploy smoke checks
```bash
curl -fsS http://<host>:3001/health # {"status":"ok"}
curl -fsS http://<host>:3001/api/stats # {"total": <n>} — grows as blocks index
docker inspect --format '{{.State.Health.Status}}' pwap-indexer # healthy
docker volume inspect pwap-indexer-db # confirm the DB volume exists/persists
```
## Notes / can't-run-in-this-environment
- **cosign keyless signing/verification** only works inside GitHub Actions
(needs the Sigstore OIDC token). It cannot be exercised locally/offline.
- The offline test suite (`npm test`, `test/*.test.js`) needs no chain/DB. The
`integration-tests/*.live.test.js` suites need a live chain + Supabase and are
**excluded** from CI on purpose.