Commit 504b4d488c

504b4d488c600b733a3dc0cea6a8f485f83ff9a6

parent: a27671383a

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-29 03:30 UTC

wiki, changelog: restore-drill, what a drill restores and checks

Ref #259

Layout: unified · split

.gitbay/wiki/Admin.org +66 −14
@@ -660,28 +660,80 @@ gitbayd admin secrets rotate # new key, reseal, retire the old one (as root)
660660
661661** Restore drill
662662
663A restore onto a clean host, run quarterly and after any change to the
664backup code (=cmd/gitbayd/backup.go=, the offsite job), and recorded
665below. The disaster it rehearses is losing bay1, so the local archives
666are gone with it and the sources are the main offsite restic
667repository (repositories, LFS, the staged database, =config.toml=),
668the off-host copy of =secret.key= and =apns.p8= (a keys repository once
669runbook D creates it; until then the operator's hand-made copy), and
670the operator's password manager (=offsite.env=, the keys repository's
671password and token once it exists, =backup-identity.txt=). The steps are in the data-at-rest
672plan's operator runbook
673(=docs/plans/2026-09-27-data-at-rest-and-backup.md=).
663A restore onto a clean host, run quarterly (January, April, July,
664October) and after any change to the backup code
665(=cmd/gitbayd/backup.go=, =cmd/gitbayd/restoredrill.go=, the offsite
666job), and recorded below. The disaster it rehearses is losing bay1, so
667the local archives are gone with it and the sources are the main
668offsite restic repository (repositories, LFS, the staged database,
669=config.toml=), the off-host copy of =secret.key= and =apns.p8= (a keys
670repository once runbook D creates it; until then the operator's
671hand-made copy), and the operator's password manager (=offsite.env=,
672the keys repository's password and token once it exists,
673=backup-identity.txt=). The full steps are runbook C of the
674data-at-rest plan (=docs/plans/2026-09-27-data-at-rest-and-backup.md=).
675
676=gitbayd admin restore-drill= does the archive half. It extracts a
677full archive into an empty or absent directory, runs every =--verify=
678check on the extracted copy, and prints what was restored, the newest
679issue, issue comment, merge request comment and push in the restored
680database, and the elapsed time. Exit is non-zero if any check fails.
681
682#+begin_src sh
683gitbayd admin restore-drill /var/backups/gitbay/gitbay-20260927-090000.tar.gz --into /srv/drill
684gitbayd admin restore-drill <archive>.tar.gz.age --identity backup-identity.txt --into /srv/drill
685#+end_src
686
687What to restore, and from where:
688
689| Item | Source | Path on the drill host |
690|-------------------------------+-------------------------------------------------------------+------------------------------------------------|
691| Database | staged copy in =/var/lib/gitbay-stage= (restic), or an archive | =<root>/gitbay.db= |
692| Repositories | =/var/lib/gitbay/repos= (restic), or an archive | =<root>/repos= |
693| LFS objects | =/var/lib/gitbay/lfs= (restic), or an archive | =<root>/lfs= (or =[lfs] root=) |
694| Release assets | inside each repository (=gitbay-releases/=) | with the repositories |
695| Host keys | =/var/lib/gitbay/ssh= (restic), or an archive | =<root>/ssh= (or =[ssh] host_keys=) |
696| =config.toml= | =/var/lib/gitbay-stage/config.toml= (restic) | =/etc/gitbay/config.toml= |
697| =secret.key=, =apns.p8= | the off-host copy; no archive or main snapshot carries them | =/etc/gitbay/=, mode 0600, owned by =gitbay= |
698
699From the offsite path (restic is not run by any gitbay command):
700
701#+begin_src sh
702restic restore latest --target / --include /var/lib/gitbay --include /var/lib/gitbay-stage
703cp /var/lib/gitbay-stage/gitbay.db /var/lib/gitbay/gitbay.db
704gitbayd --config /etc/gitbay/config.toml admin backup --out /tmp/drill.tar.gz
705gitbayd admin restore-drill /tmp/drill.tar.gz --into /tmp/drill-root
706#+end_src
707
708The second archive is how the restic tree gets the same checks and
709timestamps; =/tmp/drill-root= is discarded afterwards.
710
711What to check, each a column below:
712
713- DB integrity, connectivity, release assets, LFS: =restore-drill=
714 prints =integrity ok=, =connectivity ok on N repositories=, =release
715 assets ok: N=, =LFS objects ok: N=. Compare N with =gitbayd admin
716 stats --json= on the source at the snapshot time.
717- Secrets: =gitbayd --config /etc/gitbay/config.toml admin secrets
718 check= opens every value.
719- Host key: =ssh-keyscan -p 22 <drill-host>= matches the source's
720 fingerprint.
721- Config: =gitbayd --config /etc/gitbay/config.toml check-config=.
722- Service: =ssh -p 22 git@<drill-host> whoami= and a =git clone= over
723 SSH succeed.
674724
675725Time to service runs from the clean host's first root login to the
676726first successful =git clone= over SSH from it. The recovery point is
677the time of the newest restic snapshot restored.
727the time of the newest restic snapshot restored; record beside it the
728newest issue, comment and push =restore-drill= printed, which show how
729much activity the restore carries.
678730
679731No drill has been run yet; the procedure above is written but
680732unexercised, and #259 stays open until the first row below is
681733recorded.
682734
683| Date | Host | Snapshot restored (UTC) | Time to service | DB integrity | Connectivity | LFS | Release assets | Host key | Secrets | Notes |
684|------+------+-------------------------+-----------------+--------------+--------------+-----+----------------+----------+---------+-------|
735| Date | Host | Snapshot restored (UTC) | Newest issue / comment / push | Time to service | DB integrity | Connectivity | LFS | Release assets | Host key | Secrets | Notes |
736|------+------+-------------------------+-------------------------------+-----------------+--------------+--------------+-----+----------------+----------+---------+-------|
685737
686738* Upgrades
687739
.gitbay/wiki/Architecture/08-Operations.org +6 −2
@@ -62,8 +62,12 @@ the product activity feed, not an audit trail.
6262- Excluded: WAL files, the hook socket, askpass scripts, generated
6363 hooks.
6464- =gitbayd admin backup --verify= checks SQLite integrity, that every
65 repository the database names is present, and =git fsck
66 --connectivity-only= on each (=backup.go=).
65 repository the database names is present, =git fsck
66 --connectivity-only= on each, release assets against their recorded
67 sha256, and LFS objects against their names (=backup.go=).
68 =gitbayd admin restore-drill= runs the same checks on a full
69 extraction and reports elapsed time and the newest recovered
70 activity (=restoredrill.go=).
6771- Repository deletes, renames and transfers refuse while a full backup
6872 runs (=internal/backuplock=), so the snapshot and the walk agree.
6973- The host's restic credentials are append-only; the key that can
.gitbay/wiki/Architecture/09-Controls.org +1 −1
@@ -99,6 +99,6 @@ chapter names of OWASP ASVS 4.0 where one fits.
9999| Concurrency limit on git pack generation | in place | global, per-principal, bounded queue across SSH, HTTP and git:// (=internal/packlimit=); not in system SSH mode |
100100| Service hardening | in place | systemd sandboxing ([[file:03-Deployment.org][3]]) |
101101| Backups offsite and append-only | in place | restic with append-only credentials (documented) |
102| Restore tested | gap | #259 |
102| Restore tested | gap | tooling in place (=admin restore-drill=, Admin wiki "Restore drill"); clean-host drill pending (#259) |
103103| Migrations validated before commit | gap | foreign-key check runs after commit (#261) |
104104| Signed, reviewed changes to production | in place | signed commits, =require-mr=, ff-only merges, clean-tree deploys |
.gitbay/wiki/Architecture/10-Known-Gaps.org +1 −1
@@ -10,7 +10,7 @@ what the 2026-09-27 review found; remove a row when its issue closes.
1010
1111| Issue | Area | Gap | Severity |
1212|-------+------------------+-----------------------------------------------------------------------+----------|
13| #259 | Recovery | No restore has been exercised; the drill is written (Admin wiki) and not yet run | high |
13| #259 | Recovery | No restore has been exercised; the procedure and tooling (=admin restore-drill=, =backup --verify=) are in place, the clean-host drill is pending | high |
1414| #260 | CI network | Builds share the runner's source address; no egress policy | medium |
1515| #261 | Various | Migration foreign-key check after commit; three web writes bypass dispatch; documentation drift | medium |
1616
CHANGELOG.org +12
@@ -4,6 +4,18 @@ Versioning follows semver from v0.1.0. Database migrations run
44automatically on daemon start; upgrade notes appear per release when
55anything beyond "replace the binary and restart" is needed.
66
7* Unreleased
8
9- =gitbayd admin backup --verify= also checks every release asset the
10 database names against its recorded size and sha256, and every
11 archived LFS object against its name (#259).
12- =gitbayd admin restore-drill <archive> --into <dir>= extracts a full
13 archive into an empty directory, runs the =--verify= checks on the
14 extracted copy, and prints the newest issue, comment and push it
15 recovered and the elapsed time. The Admin wiki's Restore drill
16 section lists what to restore, what to check and where to record it
17 (#259).
18
719* v1.37.0 — 2026-09-29
820
921Findings from the 2026-09-27 architecture review.