Part A plan + readings, Part E read-backs (R-861 a, R-518) in audits/dooplex-survival-2026-10-09/. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
7.1 KiB
PLAN — an encrypted off-site copy of Gitea and DooPlex's secrets (R-232 (b), (h))
2026-10-09, Part A of the DooPlex-survival brief. Everything below was measured read-only; nothing was created.
Raw readings: partA/readings.txt.
What is measured
| Thing | Size | Growth | Where it lives today |
|---|---|---|---|
Gitea repositories (/data/git/repositories, 10 repos under admin/) |
614 MB | ~600 MB in 8 months (oldest product repo 2026-02-11); felhom.eu.git 188 MB | Longhorn PVC gitea-data on sdb1; Longhorn backup on sda1, retain=1. Nothing else. |
Gitea database (CNPG postgresql, db gitea) |
64 MB live, 6.2 MB as a pg_dump -Fc |
5.62 → 6.22 MB in 4 days (~150 KB/day) | 6-hourly dumps in /mnt/5_hdd/backup/postgresql/dumps/ (same disk as every backup) |
Gitea config app.ini (holds SECRET_KEY, INTERNAL_TOKEN, JWT secrets) |
2 KB | — | in the PVC only |
Gitea registry (/data/gitea/packages) |
27.7 GB | — | Left out on purpose: the images rebuild from the code. |
DooPlex secrets set: the nightly GPG files (secrets-, configmaps-, by-namespace-*.gpg) |
4.4 MB per night | flat (2,055,542 → 2,056,090 B in 12 days) | sda1 only, 30 days |
Correction to the 2026-08-06 recon (§7): it said the Gitea repositories are "covered twice". The API mirror
(backup/homelab-manifests/git-mirrors/) holds one repository, homelab-manifests.git (7.1 MB). The four
product repositories have exactly one backup: the Longhorn copy with retain=1, on the same machine.
The two places measured
ep0, namespace operator (where the hub DB goes) |
A new Hetzner Storage Box sub-account | |
|---|---|---|
| Free space | 83 GB of 98 GB (/mnt/pbs-datastore, 16 % used) |
depends on the box; not measured |
| Write-only key | Already exists. Token dooplex-hub@pbs!push has DatastoreBackup on /operator only: it cannot forget a snapshot or list the households (measured 2026-10-05). A second read-only token !restore exists for the restore test. |
the R-820 append-only pin; the sub-account shell can still rm unless the pin is right |
| Changes needed there | None. A new backup group (host/dooplex-gitea) in the same namespace needs no new token, ACL or prune job. The existing prune job prune-operator-hubdb (keep-daily 14, keep-weekly 8) covers every group in operator. |
a new sub-account + key custody |
| Cost | 0 € extra | a sub-account (same Storage Box fee) |
| Return copy | ep0's nightly pull to DooPlex (ep0-copy, 05:00) copies it back too — ciphertext only |
none |
Pick: ep0 operator. No change on ep0 at all, the same write-only token as the hub DB, the same prune and
the same alarm family. Worst case on ep0: a git repack every night makes each copy new chunks — 22 kept copies ×
0.65 GB ≈ 14 GB, inside the 83 GB free.
What runs
A script and a timer on DooPlex, felhom-dooplex-offsite (versioned in felhom.eu/scripts/dooplex-offsite/,
installed by its install.sh, tests by hand like the hub DB's), daily 00:20 (after the 00:00 database dump,
before the hub DB at 02:30):
- The database first: the newest complete
gitea.dump+globals.sqlfrom the 6-hourly dumps (folder withSUCCESS; refused if older than 7 h). This is PostgreSQL's own consistent snapshot. - Then the files, read from the running Gitea pod with
taroverkubectl exec:/data/git/repositories,/data/git/lfs,/data/gitea/conf/app.ini, attachments, avatars,jwt. Not packages, logs, indexers, queues, tmp. - Then the secrets set: the newest night's three
.gpgfiles (already GPG-encrypted with DooPlex's restic passphrase, which the operator holds offline). - A
MANIFEST.sha256of everything, thenproxmox-backup-client backup→host/dooplex-giteainoperator,--crypt-mode encryptwith a new key/etc/felhom-dooplex-offsite/enc.key. - Stage shredded; a success timestamp (written only after the push returns 0) for Prometheus.
Why not Gitea's own gitea dump: Gitea's documentation says the instance "must be shutdown during backup" for
consistency, and gitea dump does not stop it either. A nightly stop costs CI and the registry a gap and adds a
restart that must come back. Instead the order makes the copy safe: the database is older than the repositories, so
every commit the database names is in the copy (git writes objects before it moves a ref). A repository newer than
its database is the normal state after a push and Gitea reads it. The restore test catches the rest: it runs
git fsck on every repository.
Encryption and the key
- New key, generated on DooPlex as root:
proxmox-backup-client key create --kdf none→0600file in/etc/felhom-dooplex-offsite/, never printed by CC, never in chat. - The operator copies its paper form into the password manager at his own terminal (not through
!here):sudo proxmox-backup-client key paperkey /etc/felhom-dooplex-offsite/enc.key --output-format text— thedatafield is enough (as for the hub DB key, runbook Step 0). - ep0 never sees the key. The secrets files inside are encrypted twice (GPG + PBS).
Retention and who can delete
- ep0's existing
prune-operator-hubdb(03:45): 14 daily + 8 weekly, per group. ep0's GC frees chunks. - Can delete: only ep0's root (and that prune job). The push token cannot forget a snapshot; the restore token cannot write. DooPlex's root cannot delete on ep0 with what it holds.
Alarms and failure mail
DooplexGiteaOffsiteStale(26 h,absent()included) andDooplexGiteaRestoreTestStale(8 days), same group asHubDBBackupStale, proven withpromtool test rulesand a red-proof.- Failure mail: an
OnFailure=unit that calls the R-232 (a)notify_failure(Resend → admin@felhom.eu). The sameOnFailure=is added to the two hub-DB units. Proven with one dry failure (a unit that fails on purpose, no backup).
The restore test
- Weekly, Sunday 05:30, with the read-only token: restore the newest copy to a temp dir, check
MANIFEST.sha256,git fsckevery repository,pg_restore --list gitea.dump, then shred; success timestamp. - Once now (Part C): restore into a throwaway machine, start Gitea there with no outside network, compare every
product repository's
mainwith live Gitea, read one file byte for byte, log in with a throwaway admin. Steps becomerunbooks/gitea-restore.md. The throwaway and every copy are deleted.
What changes on DooPlex (needs the operator's yes)
/usr/local/sbin/felhom-dooplex-offsite+felhom-dooplex-offsite-restore-test(scripts, from git).- Units:
felhom-dooplex-offsite.{service,timer},felhom-dooplex-offsite-restore-test.{service,timer},felhom-backup-failmail@.service; oneOnFailure=line added to the two hub-DB services. /etc/felhom-dooplex-offsite/(root 0700):env(no secret) and the newenc.key. The tokens are the existing/etc/felhom-hub-backup/token-pushandtoken-restore(read, not copied).- Two alarm rules in
homelab-manifestsprometheus-rules+ the Prometheus reload. - One manual run, one manual restore test, one dry failure mail.
On ep0: nothing. Reads only (the snapshot list).