From ea40a6794005ab1492fdde3e9f6a46265dd4ae1f Mon Sep 17 00:00:00 2001 From: sax3l Date: Mon, 2 Nov 2026 18:57:45 +0100 Subject: [PATCH] docs(deploy): runbook and workflow headers match the live pipeline The runbook still described Vercel and an unprovisioned Coolify app. It now documents the verified chain (ci -> release attestation -> cd -> Coolify), the proven manual dispatch, the verification endpoints and the rollback. --- .gitea/workflows/cd.yml | 16 ++--- .gitea/workflows/release.yml | 4 +- docs/runbook/deploy.md | 133 ++++++++++++++++++++--------------- 3 files changed, 87 insertions(+), 66 deletions(-) diff --git a/.gitea/workflows/cd.yml b/.gitea/workflows/cd.yml index 3456242..cc92f11 100644 --- a/.gitea/workflows/cd.yml +++ b/.gitea/workflows/cd.yml @@ -6,14 +6,14 @@ name: Deploy to Coolify # Dockerfile (build_pack: dockerfile) -- this workflow only fires the # rebuild+redeploy, it doesn't build or push an image itself. # -# STATUS (2026-08-09): no Coolify application has been provisioned for 0pt -# yet -- siax.config.json's `deploy.target` was flipped from "vercel" to -# "coolify" to state the intended platform, but there is no app UUID to -# point at. COOLIFY_APP_ID below is a literal placeholder. The guard step -# fails the job loudly (rather than silently POSTing to a bogus/empty URL) -# until both the Coolify app exists and its real UUID replaces the -# placeholder here. See docs/architecture/ARCHITECTURE.md §5/§8 for the -# open "Vercel or fleet" question this resolves the config side of. +# STATUS (verified 2026-11-02): LIVE. The Coolify application exists +# (dockerfile build pack, git.siax.io/sax3l/0pt, branch main) and its UUID is +# supplied by the repository variable COOLIFY_APP_ID; COOLIFY_TOKEN and the +# registry/cosign secrets are set. A push to main, or a manual +# workflow_dispatch of this file, has been proven to verify the signed +# attestation for the exact commit and then return HTTP 200 from Coolify. +# The placeholder guard below remains as a fail-closed guard, not as a TODO. +# Operational procedure and verification live in docs/runbook/deploy.md. on: push: branches: [main] diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index ca96919..7a9a903 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -29,8 +29,8 @@ name: Release chain (G17, ADR-W16-003) # workflow file -- learned the hard way on sax3l/h0st's and sax3l/aud0's # own G17 migrations. 0pt does not wire a G20 post-deploy step in this # pass at all (see `.siax/hooks/README.md` "G20 -- deliberately not wired -# yet": no Coolify application provisioned for 0pt, no `/version` -# endpoint), so the single-job constraint that forces G20 into its own +# yet": the app is now provisioned and `/api/version` exists, but G20 is still +# not wired in this pass), so the single-job constraint that forces G20 into its own # workflow file elsewhere does not yet bite here -- documented anyway so a # future PR adding G20 does not repeat the mistake. diff --git a/docs/runbook/deploy.md b/docs/runbook/deploy.md index 50d57cd..a866da5 100644 --- a/docs/runbook/deploy.md +++ b/docs/runbook/deploy.md @@ -1,63 +1,84 @@ # Runbook: Deploy — 0pt -> Mål: sätta 0pt.app på SIAX-flottan (Coolify) och avveckla Vercel i ordning. -> Status 2026-08-10: `siax.config.json` deklarerar `target: coolify`; ingen -> Coolify-app provisionerad; live-instansen körs fortfarande på Vercel. +> Status 2026-11-02: **live on the SIAX fleet (Coolify)** at `https://0pt.app`. +> The automatic chain and the manual path are both verified: a signed image is +> attested, Coolify is called, the container restarts on the new commit, and +> `/api/version` reports it. -## 1. Förutsättningar (blockerare för cutover) +## Pipeline (automatic) -- [ ] Coolify-app provisionerad i `cloud.siax.io` (build_pack: dockerfile, - repo `git.siax.io/sax3l/0pt`, gren `main`). -- [ ] `COOLIFY_APP_ID` (appens UUID) ifylld i `.gitea/workflows/cd.yml`. -- [ ] `COOLIFY_TOKEN`-secret satt på repot (Gitea → Settings → Actions → Secrets). -- [ ] Env-secrets inlagda i Coolify-appen från Infisical-projektet `0pt` - (se backup/restore + incident runbooks). -- [ ] DNS-cutover planerad: `0pt.app` → Coolify-appens ingress (eller Caddy). - -## 1b. Signerad-artefakt-grind (tillagd 2026-09-14, samma mönster som cl0ud/st0re/h0st) - -`.gitea/workflows/release.yml` (nytt) bygger, SBOM:ar (syft), signerar -(cosign) och evidensbokför varje push till `main`, och pushar till -`git.cloud.siax.io/sax3l/0pt:sha-<12>`. `cd.yml`s `deploy`-jobb har nu ett -extra steg (efter guard-steget i punkt 1 ovan) som kräver att exakt denna -commits avbildning finns i registret och att dess cosign-signatur -verifierar (mot committad `cosign.pub`) innan Coolify överhuvudtaget -anropas. - -Detta är byggt FÖRE Coolify-appen finns (se blockerarna i punkt 1) — så att -grinden redan är på plats den dag appen provisioneras, i stället för att bli -ett separat uppföljningsarbete. Samma ärliga begränsning som cl0ud/st0re/ -h0st: när appen väl provisioneras som `dockerfile` build pack (så som -`siax.config.json` föreskriver) kör Coolify sin EGEN oberoende -`docker build` från git-källan, inte den signerade avbildningen — -`/api/v1/deploy` saknar digest-parameter helt. Sann digest-pinning skulle -kräva att appen provisioneras som Coolifys separata `dockerimage`-resurstyp -i stället, ett beslut som inte är fattat än. Se `.gitea/workflows/cd.yml`s -"SIGNED-ARTIFACT GATE"-kommentar för den fullständiga skrivningen. - -**Inte satt på `sax3l/0pt` ännu:** repo-secrets `REGISTRY_USER`, -`REGISTRY_TOKEN`, `COSIGN_PRIVATE_KEY`, `COSIGN_PASSWORD`, `SIAX_CI_TOKEN` — -`release.yml` fail-closed:ar på sin första körning tills dessa finns. - -## 2. Deploy (när appen finns) - -1. Push till `main` → `.gitea/workflows/cd.yml` POSTar till - `https://cloud.siax.io/api/v1/deploy?uuid=$COOLIFY_APP_ID&force=false`, - efter att den signerade-artefakt-grinden i punkt 1b gått grön. -2. Följ run i Gitea Actions; verifiera att den slutar grön. -3. Verifiera hälsa: `curl https://0pt.app/api/health` → `ok:true` och - `db: configured` (när Postgres kopplats in). - -## 3. Manuell redeploy (nöd) - -```bash -curl -fsS -X POST "https://cloud.siax.io/api/v1/deploy?uuid=&force=true" \ - -H "Authorization: Bearer $COOLIFY_TOKEN" +``` +git push main + ├─ ci.yml quality (typecheck, contrast, tests, build) + │ integration (live OpenSearch + ClickHouse) + │ security (Trivy, blocking) + ├─ security.yml push/PR/weekly scan + ├─ release.yml build + SBOM + blocking vuln scan + cosign attest + push + │ → git.cloud.siax.io/sax3l/0pt:sha-<12> + └─ cd.yml waits for THIS commit's attestation, verifies it against + cosign.pub, then POSTs to Coolify +prod-smoke.yml every 30 min: ready + db/stripe + running commit == main ``` -## 4. Vercel-avveckling (EFFER att Coolify kör stabilt) +- One run per ref (`concurrency`); `push` is main-only, PRs are covered by the + `pull_request` event. Deploys have their own non-cancelling group so two + runs never race the same app. +- Dependency caches: pnpm store (Node jobs), npm (doc0/n0d), pip (0pt-motor). -1. Verifiera att ingen verklig trafik/data ligger bakom (2026-08-10: - `/api/health` visar `db/queue: not_configured` — marknads-/dev-läge). -2. Peka DNS om till Coolify. 3. Behåll Vercel-projektet tills certifierad - paritet (katalog, orderflöde, betalningar). 4. Avveckla först därefter. +## Manual deploy (proven) + +Trigger the same, signed path — do not POST to Coolify directly unless the +pipeline itself is broken: + +```bash +curl -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \ + -d '{"ref":"main"}' \ + https://git.siax.io/api/v1/repos/admin/0pt/actions/workflows/cd.yml/dispatches +``` + +Emergency only (bypasses the attestation gate; still builds the branch head): + +```bash +curl -X POST -H "Authorization: Bearer $COOLIFY_API_TOKEN" -H "Content-Type: application/json" \ + -d '{"uuid":""}' https://cloud.siax.io/api/v1/deploy +``` + +## Verify a deploy + +```bash +curl -s https://0pt.app/api/ready # {"ready":true,"db":"ok","stripe":"ok",...} +curl -s https://0pt.app/api/health # liveness envelope +curl -s https://0pt.app/api/version # {"commit":""} +``` + +`commit` must equal the current `main` SHA. `prod-smoke.yml` enforces exactly +that every 30 minutes; a green deploy is never confused with a current service. + +## Rollback + +Coolify builds from the branch head, so a rollback is a normal revert: + +```bash +git revert --no-edit && git push origin main +``` + +That runs the same chain and deploys the reverted tree. Coolify's own +"redeploy previous deployment" is the break-glass alternative. + +## Configuration (names only — values live in Coolify/Infisical) + +- Coolify app: `dockerfile` build pack, repo `git.siax.io/sax3l/0pt`, branch + `main`, health check `/api/health`, domains `0pt.app` + `api.0pt.app`. +- Repository variable: `COOLIFY_APP_ID`. +- Repository secrets: `COOLIFY_TOKEN`, `REGISTRY_USER`, `REGISTRY_TOKEN`, + `COSIGN_PRIVATE_KEY`, `COSIGN_PASSWORD`, `SIAX_CI_TOKEN`. +- App env: `DATABASE_URL`, `STRIPE_SECRET_KEY`, `AUTH_SECRET`… see `.env.example` + for the full, documented list (DOC0, INF0/decision Ollama, OpenFGA/project + grants, model policy, credit price book, OIDC). + +## Known limitation + +Coolify rebuilds from source; `/api/v1/deploy` has no digest parameter, so the +verified digest is recorded as provenance for the deploy rather than pinned as +the running bytes. True digest pinning would require a Coolify `dockerimage` +resource — an infrastructure decision, not a CI-YAML change. -- 2.54.0