Skip to content

feat(ops): database backups, ADR 0017 (two-leg: nightly pg_dump + encrypted Drive copy)#112

Merged
miquelmatoses merged 3 commits into
mainfrom
feat/db-backups-adr-0017
Jul 5, 2026
Merged

feat(ops): database backups, ADR 0017 (two-leg: nightly pg_dump + encrypted Drive copy)#112
miquelmatoses merged 3 commits into
mainfrom
feat/db-backups-adr-0017

Conversation

@miquelmatoses

Copy link
Copy Markdown
Collaborator

What

Lands ADR 0017 (Accepted) and its implementation in one PR. Closes the runbook TODO ("Backups are the operator's responsibility... TODO document") that the reliability sprint stopped on.

  • ADR: docs/decisions/0017-database-backups-two-leg.md. Two legs proportionate to a ~20 MB database on a shared VPS: nightly on-box pg_dump -Fc (7 kept) for logical-error recovery, plus a gpg-encrypted rclone push to Google Drive (30-day retention) for disk/box loss. Documents the four rejected alternatives (Storage Box: cost; R2: couples to the unfinished ADR 0013 migration; B2: new identity to manage; dumps-in-Git: security and hygiene) and the Hetzner whole-VM backups as coarse fallback only.
  • Script: api/deploy/backup/cercol-db-backup.sh. Root cron via runuser -u postgres peer auth; integrity check (pg_restore --list); exits non-zero on any failure, logs to /home/cercol/logs/db-backup.log, and maintains a db-backup.FAILED marker removed on next success. Deliberately breaks with the MAILTO="" silent-cron pattern.
  • Cron: api/deploy/cron/cercol-db-backup, daily 03:15 UTC (checked against existing slots: 02:00 crawl, 03:00 Sun Bing, 04:00 purge). Ships in the repo; installed manually by the operator, same pattern as the SEO ingest crons. Nothing is installed on the server by this PR.
  • Runbook: install block, on-box restore, off-box fetch+decrypt+restore, quarterly scratch-DB restore test, Hetzner last-resort note.
  • CI: new shell-lint job (shellcheck over api/deploy/backup/*.sh).

Resolved design details (in the ADR before acceptance)

  • Encryption: gpg symmetric AES256 (gpg 2.2 preinstalled on the server; age is not). Passphrase in root-only /root/.cercol-backup-passphrase, created by the operator, never committed.
  • rclone auth: interactive OAuth token flow (user-owned Drive quota); service accounts rejected because uploads can land in the SA's own quota on a personal account.

Verification

  • bash -n clean; shellcheck runs in CI (not available locally).
  • Server assumptions verified read-only before writing: pg_dump/pg_restore 14.23 present, gpg 2.2 present, rclone absent (installed at manual step), 03:15 slot free, deploy assets convention api/deploy/*.

🤖 Generated with Claude Code

miquelmatoses and others added 3 commits July 5, 2026 13:32
Nightly pg_dump -Fc of the cercol database kept on-box (7 dumps,
/var/backups/cercol) plus a gpg-encrypted copy pushed to Google Drive
via rclone (30-day retention). Script exits non-zero on any failure,
logs to /home/cercol/logs/db-backup.log and maintains a FAILED marker
file, breaking with the silent MAILTO="" cron pattern.

Cron file ships in the repo and is installed manually by the operator
(same pattern as the SEO ingest crons). Runbook gains the install
block, on-box and off-box restore procedures, a quarterly scratch-DB
restore test, and loses the backup TODO at the old line 353. CI gains
a shellcheck job over api/deploy/backup/*.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolved during implementation: gpg symmetric AES256 (preinstalled;
age is not), rclone interactive OAuth token flow (user-owned Drive
quota, no service account), retention 7 local / 30 days remote, cron
daily 03:15 UTC offset from existing jobs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test_infra.py requires every ADR to carry a Related section; add it
with pointers to the runbook, the implementation files, ADR 0013/0006
and the audit that motivated the decision. Replace the ls -1t rotation
pipeline with a glob + sort -r (SC2012); timestamped filenames make
lexical order chronological.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@miquelmatoses miquelmatoses merged commit 1d0df04 into main Jul 5, 2026
8 checks passed
@miquelmatoses miquelmatoses deleted the feat/db-backups-adr-0017 branch July 5, 2026 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant