Commit 9e4827a050

9e4827a050e337dd51a558790ecefa1c123b20ea

parent: 5f1152bca4

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-28 22:27 UTC

docs: restore puts the secret key file back before starting gitbayd

Ref #259

Layout: unified · split

.gitbay/wiki/Admin.org +17 −8
@@ -489,8 +489,11 @@ restic credentials), so verifying an encrypted archive happens there
489or on a restore host. 489or on a restore host.
490 490
491Restore: extract into an empty directory, point =server.root= at it, 491Restore: extract into an empty directory, point =server.root= at it,
492start gitbayd. Host keys are preserved, so clients keep their 492restore =server.secret_key_file= from its own copy (mode 0600, owned
493known_hosts entries; hooks regenerate at startup. 493by the account gitbayd runs as), then start gitbayd. No archive carries
494the key file, and without it gitbayd refuses to start. Host keys are
495preserved, so clients keep their known_hosts entries; hooks regenerate
496at startup.
494 497
495** Schedule and recovery point 498** Schedule and recovery point
496 499
@@ -518,8 +521,10 @@ too.
518** Offsite copies 521** Offsite copies
519 522
520bay1 also takes a nightly restic snapshot of =/var/lib/gitbay= and 523bay1 also takes a nightly restic snapshot of =/var/lib/gitbay= and
521=/var/lib/gitbay-stage= (the staged database copy) to an S3 bucket at 524=/var/lib/gitbay-stage= (a database copy and =config.toml=, staged
522Scaleway, with a key that can only add snapshots. The key that can 525there) to an S3 bucket at Scaleway, with a key that can only add
526snapshots. Nothing else under =/etc/gitbay= is in it: not the secret
527key file, not =apns.p8=. The key that can
523remove them lives on the operator's machine, in 528remove them lives on the operator's machine, in
524=~/.config/gitbay/offsite.env=, and never on bay1: a compromised host 529=~/.config/gitbay/offsite.env=, and never on bay1: a compromised host
525cannot destroy its own history. Forgetting, pruning and rewriting all 530cannot destroy its own history. Forgetting, pruning and rewriting all
@@ -568,7 +573,10 @@ each value prefixed with the id of the key that sealed it
568=server.root=, and therefore in neither the local archives nor the 573=server.root=, and therefore in neither the local archives nor the
569main restic repository. It must be copied off the host separately; 574main restic repository. It must be copied off the host separately;
570without it a restored database's secrets cannot be opened, and 575without it a restored database's secrets cannot be opened, and
571gitbayd refuses to start against them. 576gitbayd refuses to start against them. A separate restic repository
577for it and =apns.p8= is planned (runbook D of the data-at-rest plan)
578and not yet in place, so today the only off-host copy is one the
579operator makes by hand after =init= and after every =rotate=.
572 580
573#+begin_src sh 581#+begin_src sh
574gitbayd admin secrets init # once; deploy/install.sh does it on first install 582gitbayd admin secrets init # once; deploy/install.sh does it on first install
@@ -598,9 +606,10 @@ backup code (=cmd/gitbayd/backup.go=, the offsite job), and recorded
598below. The disaster it rehearses is losing bay1, so the local archives 606below. The disaster it rehearses is losing bay1, so the local archives
599are gone with it and the sources are the main offsite restic 607are gone with it and the sources are the main offsite restic
600repository (repositories, LFS, the staged database, =config.toml=), 608repository (repositories, LFS, the staged database, =config.toml=),
601the keys repository (=secret.key=, =apns.p8=), and the operator's 609the off-host copy of =secret.key= and =apns.p8= (a keys repository once
602password manager (=offsite.env=, the keys repository's password and 610runbook D creates it; until then the operator's hand-made copy), and
603token, =backup-identity.txt=). The steps are in the data-at-rest 611the operator's password manager (=offsite.env=, the keys repository's
612password and token once it exists, =backup-identity.txt=). The steps are in the data-at-rest
604plan's operator runbook 613plan's operator runbook
605(=docs/plans/2026-09-27-data-at-rest-and-backup.md=). 614(=docs/plans/2026-09-27-data-at-rest-and-backup.md=).
606 615
.gitbay/wiki/Architecture/06-Data-and-Cryptography.org +1 −1
@@ -45,7 +45,7 @@ throttling (=internal/sshd/sshd.go=).
45| API tokens, sessions, login links, email codes, invites | SHA-256 of a 256-bit random value; the value is shown once and never stored (=internal/store/sessions.go=) | 45| API tokens, sessions, login links, email codes, invites | SHA-256 of a 256-bit random value; the value is shown once and never stored (=internal/store/sessions.go=) |
46| CI secrets, webhook secrets, mirror tokens, APNs device tokens | AES-256-GCM under a key file outside the database and outside =server.root=; additional data binds table, column and row (=internal/seal=, =internal/store/secrets.go=) | 46| CI secrets, webhook secrets, mirror tokens, APNs device tokens | AES-256-GCM under a key file outside the database and outside =server.root=; additional data binds table, column and row (=internal/seal=, =internal/store/secrets.go=) |
47| SQLite file | mode 0640, directory 0750 | 47| SQLite file | mode 0640, directory 0750 |
48| Backups | local archives age-encrypted when =[backup] age_recipients= is set; restic encrypts the offsite copy; neither carries the secret key file, whose offsite copy is a separate keys repository | 48| Backups | local archives age-encrypted when =[backup] age_recipients= is set; restic encrypts the offsite copy; neither carries the secret key file, which is copied off the host by hand until a separate keys repository is set up |
49| Disk | no application-level encryption; any disk encryption is the host's | 49| Disk | no application-level encryption; any disk encryption is the host's |
50 50
51The database file or a backup read by anyone other than the =gitbay= 51The database file or a backup read by anyone other than the =gitbay=
cmd/gitbayd/backup.go +5 −2
@@ -47,8 +47,11 @@ affordable, and the database is the copy of issues, merge requests and
47comments that exists nowhere else. Repositories are not in such an archive, 47comments that exists nowhere else. Repositories are not in such an archive,
48so it supplements a full backup and does not replace one. 48so it supplements a full backup and does not replace one.
49 49
50Restore: extract into an empty directory, point server.root at it, start 50Restore: extract into an empty directory, point server.root at it,
51gitbayd. Host keys are preserved, so clients keep their known_hosts entries. 51restore server.secret_key_file from its own backup (mode 0600, owned by
52the daemon user), start gitbayd. No archive carries the key file, and
53without it gitbayd refuses to start. Host keys are preserved, so clients
54keep their known_hosts entries.
52 55
53With [backup] age_recipients set, the archive is encrypted to those age 56With [backup] age_recipients set, the archive is encrypted to those age
54public keys and its name ends in .age. --verify then needs --identity 57public keys and its name ends in .age. --verify then needs --identity
deploy/cloud-init.yaml +6 −1
@@ -11,7 +11,12 @@
11# - opens ufw for 22, 80, 443, 2222 11# - opens ufw for 22, 80, 443, 2222
12# 12#
13# It does NOT install the gitbayd binary (it is not hosted anywhere yet); 13# It does NOT install the gitbayd binary (it is not hosted anywhere yet);
14# scp it to /usr/local/bin/gitbayd afterward and `systemctl start gitbayd`. 14# scp it to /usr/local/bin/gitbayd afterward, then create the secret key
15# and hand it to the daemon user before starting:
16# gitbayd --config /etc/gitbay/config.toml admin secrets init
17# chown gitbay:gitbay /etc/gitbay/secret.key
18# systemctl start gitbayd
19# On a restore, put the key file's off-host copy there instead of init.
15 20
16package_update: true 21package_update: true
17packages: 22packages: