From 81eaf1a9a25908708837074cda97cd2f14ee72dc Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 4 Jul 2026 15:41:29 -0700 Subject: [PATCH 1/2] docs(selfhost): document a real backup/restore drill result (#1821) Ran the existing verify-backup.sh scratch-restore flow against a real production Postgres backup: restored into a throwaway, network-isolated scratch database (never the live one, confirmed by the script's own identity check), repopulating all 84 tables with full row counts intact. Documents why the drill stops at DB-level restore rather than a full app boot to /ready -- that endpoint also gates on live Redis, Qdrant, AI provider, Codex auth, and a real GitHub App key, and reproducing those for a disposable scratch instance would mean copying real credentials into new throwaway infrastructure. --- .../docs.self-hosting-backup-scaling.tsx | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx index 422d53b5f2..dedf12482e 100644 --- a/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx +++ b/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx @@ -156,6 +156,30 @@ docker compose --profile backup run --rm backup sh /verify-backup.sh /backups/po can afford to drop. The script refuses to run when that URL equals the live backup source. +

Restore drill: what "restore-tested" actually verifies

+

+ This exact flow was run against a real production backup on a live instance (2026-07-04): + the newest Postgres dump was restored into a throwaway, network-isolated scratch database (a + separate container, never the live one), which the script's own identity check confirmed was + distinct from the backup source before touching anything. The restore completed cleanly and + repopulated all 84 application tables, including the largest operational tables with their + full row counts intact (hundreds of thousands of rows in the biggest tables) — not just an + empty schema. +

+

+ This proves the backup content and the restore path both work end-to-end against real data. + It deliberately stops short of booting a full app instance against the scratch database and + polling /ready: that endpoint also gates on live Redis, Qdrant, the configured + AI provider, Codex auth, and a real GitHub App key (see{" "} + Operations's health endpoints section) — + reproducing all of those for a disposable scratch instance would mean copying real + credentials into new, throwaway infrastructure, which is a bigger risk than the drill is + worth. In an actual disaster recovery, the restored database is loaded onto trusted + infrastructure the operator already controls, using their own real credentials, so once the + db readiness check passes (which this drill proves the restored data supports), + the remaining checks reflect the same subsystems' live state as any other boot. +

+

After scaling, revisit Operations and{" "} Security because network and credential From 7604c9827fcdb14faa6b34d2f8483ca7ba6045b9 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:00:48 -0700 Subject: [PATCH 2/2] fix(docs): stop overclaiming app-readiness proof from the restore drill The drill only exercised the database restore path (pg_restore into a scratch database), not the app's own db readiness probe, migration boot path, or /ready response -- the prior wording implied otherwise. Also names the exact backup file used and frames the table/row counts as a point-in-time result rather than an invariant. --- .../docs.self-hosting-backup-scaling.tsx | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx index dedf12482e..117ab0b10b 100644 --- a/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx +++ b/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx @@ -158,13 +158,15 @@ docker compose --profile backup run --rm backup sh /verify-backup.sh /backups/po

Restore drill: what "restore-tested" actually verifies

- This exact flow was run against a real production backup on a live instance (2026-07-04): - the newest Postgres dump was restored into a throwaway, network-isolated scratch database (a - separate container, never the live one), which the script's own identity check confirmed was - distinct from the backup source before touching anything. The restore completed cleanly and - repopulated all 84 application tables, including the largest operational tables with their - full row counts intact (hundreds of thousands of rows in the biggest tables) — not just an - empty schema. + This exact flow was run against a real production backup on a live instance on 2026-07-04 + (backup gittensory-20260704T090939Z.dump): the dump was restored into a + throwaway, network-isolated scratch database (a separate container, never the live one), + which the script's own identity check confirmed was distinct from the backup source before + touching anything. The restore completed cleanly and, at the time of this drill, repopulated + all 84 application tables, including the largest operational tables with their full row + counts intact (hundreds of thousands of rows in the biggest tables) — not just an empty + schema. Table and row counts will grow over time; treat them as a point-in-time result, not + an invariant.

This proves the backup content and the restore path both work end-to-end against real data. @@ -174,10 +176,11 @@ docker compose --profile backup run --rm backup sh /verify-backup.sh /backups/po Operations's health endpoints section) — reproducing all of those for a disposable scratch instance would mean copying real credentials into new, throwaway infrastructure, which is a bigger risk than the drill is - worth. In an actual disaster recovery, the restored database is loaded onto trusted - infrastructure the operator already controls, using their own real credentials, so once the - db readiness check passes (which this drill proves the restored data supports), - the remaining checks reflect the same subsystems' live state as any other boot. + worth. This drill proves the dump can be restored and its contents inspected at the database + layer — it does not exercise the app's own db readiness probe, migration boot + path, or /ready response. A full disaster-recovery rehearsal still needs to + verify app readiness on the target infrastructure, using the operator's own real + credentials.