Commit Graph

1 Commits

Author SHA1 Message Date
null e000f53883 feat(ops): restore-check.sh, the other half of backup.sh
backup.sh says it outright -- it verifies the artefact, only a restore verifies
the backup -- and its header names this script as the missing half, with the
pg_restore command it should run. This is that command with the parts that stop
it being dangerous.

A dump pg_restore --list can read is a file with a table of contents, not a
database. Between those sit every reason a restore fails on the day it is
needed: a missing extension, an owner that does not exist, version skew, a dump
of the wrong database that reads perfectly. And the number nobody has and will
want badly: how long it takes. During an incident that decides whether you
restore or fail over, and it is unknowable from the file size. Printed every run.

**The dangerous part.** pg_restore --clean issues DROPs, and pointed at
production it obeys immediately and irreversibly. Handled by never accepting a
target: there is no --database flag, because naming the database is the mistake.
The script creates `restorecheck_<epoch>_<pid>`, restores into that, and drops it
from a trap so an interrupted run leaves no copy of production data behind.
Same argument status.sh makes for having no --host flag.

Shares BACKUP_DIR, BACKUP_NAME and BACKUP_MIN_TABLES with backup.sh rather than
taking its own, so the two cannot disagree about which series belongs to this
project.

Proved against a real PostgreSQL, not asserted -- GUARDS.md section 1:

  exit 0  a real 3-table dump, minimum 1
  exit 1  minimum raised to 99; a dump truncated to 2000 bytes; a zero-byte dump
  exit 2  unconfigured (naming the missing value one at a time); server
          unreachable
  exit 0  --dry-run, always, contacting nothing

Two things that testing found and assertion would not. Every scratch database
was dropped, confirmed by querying pg_database afterwards. And --dry-run could
exit 1 on an empty dump, because the emptiness check ran before it; a mode whose
exit code depends on the state of the data is not a dry run, so the check moved
below and the dry run now notes the emptiness in its plan instead.

closes #7

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 23:07:36 -05:00