From 8af8c4fab7f53b50fe9ee4a65466b2dd924cbca1 Mon Sep 17 00:00:00 2001 From: fabi Date: Wed, 12 Aug 2026 20:00:52 +0200 Subject: [PATCH] docs(runbook): validate the Caddyfile before the freeze, and pair down-migrations with a rollback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing anywhere executes the production `Caddyfile` before the real deploy — the e2e stack mounts `e2e/Caddyfile.test` — and a syntax error there is total: Caddy exits, `restart: unless-stopped` loops, 443 is dead for the whole event, and `docker compose up -d --force-recreate caddy` still exits 0 while it crash-loops. Step zero now validates it. I ran it against the current file (which I changed last commit, unexercised): "Valid configuration", and the new `read_body 30m` adapts to `read_timeout: 1800000000000`ns as intended. And a warning §9 needed: a down migration is not a standalone repair. Roll the IMAGE back first. `Upload::create` sends an `ON CONFLICT ... WHERE` predicate that must match the live partial index exactly and is not compile-checked, so running 026's or 031's down against the current binary turns every upload carrying a client_upload_id — i.e. every upload from the shipped client — into a runtime 500. 026's down can also fail outright on any database where a guest deleted and re-uploaded a photo; it rolls back cleanly, but you cannot go below it. Both verified against a live Postgres. --- DEPLOYMENT_RUNBOOK.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/DEPLOYMENT_RUNBOOK.md b/DEPLOYMENT_RUNBOOK.md index 4bdf963..55e4205 100644 --- a/DEPLOYMENT_RUNBOOK.md +++ b/DEPLOYMENT_RUNBOOK.md @@ -50,6 +50,13 @@ git show HEAD:docker-compose.yml | grep -cE '^[[:space:]]*build:' # must be 0 # Every migration in the tree is committed — a build from a dirty tree bakes in extras. git status --porcelain -- backend/migrations/ # must print nothing + +# The Caddyfile PARSES. Nothing else checks it: the e2e stack mounts `e2e/Caddyfile.test`, +# so the production file is never executed until the real deploy — and a syntax error there +# is total. Caddy exits, `restart: unless-stopped` loops, 443 is dead for the whole event, +# and `docker compose up -d --force-recreate caddy` still exits 0 while it crash-loops. +docker run --rm -v "$PWD/Caddyfile:/etc/caddy/Caddyfile:ro" -e DOMAIN=example.com \ + caddy:2-alpine caddy validate --config /etc/caddy/Caddyfile # must end "Valid configuration" ``` | When | What | @@ -623,6 +630,18 @@ docker compose exec -T db sh -c \ 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "DELETE FROM _sqlx_migrations WHERE version = NN;"' ``` +> **A down migration is only valid PAIRED WITH A CODE ROLLBACK — it is not a standalone repair.** +> `Upload::create` sends an `ON CONFLICT ... WHERE` predicate that must match the live partial +> index exactly, and these queries are not compile-checked. Run **026**'s or **031**'s down against +> the current binary and every upload carrying a `client_upload_id` — i.e. every upload from the +> shipped client — becomes a runtime 500. Roll the image back first, then the migration. +> +> **026's down can also fail outright, and that is expected.** It restores a wider unique index, so +> it aborts with `could not create unique index ... is duplicated` on any database where a guest +> ever deleted a photo and re-uploaded it. The transaction rolls back cleanly and the narrow index +> survives intact — no half-state — but you cannot go below 026 on a database that has seen real +> use. Verified against a live Postgres. + Migration **014** is the only destructive one on the way up, and the only one with a rehearsal harness — `backend/scripts/rehearse-014.sh`. Run it once against a real dump before the event.