**The backup that did not exist.** `.env.example` ships `EVENT_NAME=Max & Maria's Wedding` and the runbook tells you to `cp .env.example .env`. Compose's env_file parser reads that fine. POSIX `sh` does not: `. ./.env` aborts with "Unterminated quoted string" (verified, rc=2), and every variable defined after that line is left unset. The §10.2 cron script is `#!/bin/sh` + `set -eu` + `. ./.env`, so it exited before `pg_dump` — every hour, into a log nobody reads. The only automated backup of the one thing the runbook calls irreconstructible produced nothing, and §10.2's own "prove it works NOW" only catches it if `.env` is already final at that moment. The script reads NOTHING from `.env` — `POSTGRES_USER`/`POSTGRES_DB` are expanded inside the db container by the single-quoted `sh -c`. The source line was pure liability and is gone. `EVENT_NAME` is now double-quoted in `.env.example`, which both parsers read identically (verified), and the three interactive sourcing sites now read just `$DOMAIN` instead of sourcing the whole file. The verify step also proves the dump is a non-empty valid gzip containing tables, rather than that a file exists. Also fixes the script's `cd /root/eventsnap`, which contradicts §5's non-root deploy and §13's `~/eventsnap` — under a non-root deploy it failed the same way, silently. **The crash loop with no message.** `docker-compose.yml` interpolated `POSTGRES_USER`/`POSTGRES_DB` with no default and no `:?` guard, into `environment:`, which OVERRIDES `env_file`. Unset does not fall back — it resolves to the empty string, initdb creates a role and database named "", `DATABASE_URL` still says `eventsnap`, and the app hits `FATAL: role "eventsnap" does not exist` forever. `pg_isready -U "" -d ""` never passes, so `app` never turns healthy and Caddy — gated on `service_healthy` — never starts: port 443 dead for the whole event, exit only via `down -v`. Both now carry `:?` guards (verified they fire), and §3's ".env template — ALL of them" list, which omitted both, now includes them. Other runbook corrections: the backup/restore pointer named a line range that had drifted into an unrelated section and stopped mid-restore, before the media restore and the mandatory `chown` — now referenced by heading, which cannot go stale. §7.3 told you to verify that `EXPORT_PATH` is not pinned when §3 correctly says it is. Stale counts: rev-list 196 -> 217, "versions 007–022" -> 007–031, `frontend/Dockerfile:9` -> :8, and the low-disk description now matches the code (the 10 GB absolute floor was removed as unreachable). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
17 KiB
17 KiB