Validate Gitea USB backup restore

This commit is contained in:
Fabio Scotto di Santolo
2026-10-02 09:13:06 +02:00
parent 31fedb8d44
commit 3f9a626759
2 changed files with 20 additions and 14 deletions

View File

@@ -290,10 +290,13 @@ successfully. The first monthly scrub remains a runtime check.
Borg archive `atlas-20261001T193420Z` included its database. A private one-file restore from Borg archive `atlas-20261001T193420Z` included its database. A private one-file restore from
each independently matched the staged database and passed SQLite `quick_check`; temporary files each independently matched the staged database and passed SQLite `quick_check`; temporary files
and snapshot mounts were removed, the Borg service ended successfully, and the pool was healthy. and snapshot mounts were removed, the Borg service ended successfully, and the pool was healthy.
- [ ] Include the new Gitea dataset in the next UUID-bound offline USB version and test a file restore - [x] Include the new Gitea dataset in a UUID-bound offline USB version and test a file restore
from that version before accepting production writes; the UUID-bound disk is connected but its before accepting production writes. The operator's 2026-10-01 manual run published version
LUKS mapper is closed, so the manual backup still requires interactive unlock. On 2026-10-01 the `20261001T201220Z-254397` successfully on 2026-10-02. Its Gitea database was restored to a
operator could not unlock it and chose to defer traffic cutover until this check passes. temporary directory from a read-only mount: contents, owner, group, mode, size, mtime and POSIX
ACL matched, and SQLite `quick_check` passed. Temporary files and mounts were removed, LUKS
was closed, and the pool remained healthy. A redundant run was stopped during verification;
its temporary snapshot was cleaned up and the service's resulting failed state was reset.
- [x] Install a separate opt-in final Gitea export helper on Prometheus. Its 2026-10-01 targeted - [x] Install a separate opt-in final Gitea export helper on Prometheus. Its 2026-10-01 targeted
deployment and `bash -n` passed while Gitea and NPM stayed running. It refuses an active export deployment and `bash -n` passed while Gitea and NPM stayed running. It refuses an active export
timer, stops only Gitea, verifies SQLite, publishes a checksum-verified Gitea-only version for timer, stops only Gitea, verifies SQLite, publishes a checksum-verified Gitea-only version for

View File

@@ -71,9 +71,13 @@ source into private `/var/tmp` directories matched the live staged database
and passed SQLite `quick_check`. Temporary files and the on-demand snapshot and passed SQLite `quick_check`. Temporary files and the on-demand snapshot
mount were removed; the Borg temporary snapshot was cleaned up and the pool mount were removed; the Borg temporary snapshot was cleaned up and the pool
remained healthy. This is file-level proof, **not** a full Gitea recovery. remained healthy. This is file-level proof, **not** a full Gitea recovery.
The UUID-bound offline USB disk is connected but its LUKS mapper is closed; The operator's UUID-bound offline USB run published version
its manual backup requires interactive unlock. It has not yet captured or `20261001T201220Z-254397` on 2026-10-02. A separate read-only mount and
restored this new dataset. temporary restore of `services/data/gitea/data/gitea/gitea.db` matched
contents, owner, group, mode, size, mtime and POSIX ACL; SQLite
`quick_check` returned `ok`. The temporary mount and copy were removed,
LUKS was closed, and the pool was healthy. This is a file-level restore test,
not a complete Gitea recovery rehearsal from USB.
1. Provision a dedicated target dataset and non-login service identity via 1. Provision a dedicated target dataset and non-login service identity via
Ansible, keeping UID/GID distinct from Atlas' reserved Immich `1100`. Ansible, keeping UID/GID distinct from Atlas' reserved Immich `1100`.
@@ -98,9 +102,8 @@ restored this new dataset.
container with no production ingress or outbound network. Because the container with no production ingress or outbound network. Because the
source stays active, this is a rehearsal copy, not the final cutover copy. source stays active, this is a rehearsal copy, not the final cutover copy.
Regenerate Git hooks if the changed installation path requires it. Regenerate Git hooks if the changed installation path requires it.
4. ZFS and Borg inclusion and one-file restores have passed. Complete a 4. ZFS, Borg and UUID-bound offline USB inclusion and one-file restores have
UUID-bound offline USB version and a one-file restore for the new dataset passed. These do not replace the final consistent source copy.
before accepting user traffic.
## Phase 2: explicit final cutover ## Phase 2: explicit final cutover
@@ -132,10 +135,10 @@ is enabled yet. The future Prometheus configuration passed a check-run; the
installed socket units passed `systemd-analyze verify` while remaining installed socket units passed `systemd-analyze verify` while remaining
inactive. Source Gitea still answered HTTP 200 after preparation. inactive. Source Gitea still answered HTTP 200 after preparation.
The operator cannot unlock the UUID-bound USB disk now and chose to defer The previously agreed USB prerequisite is now met, but the final export,
traffic activation until a new USB version covers Gitea and its file restore production flags, and public cutover remain uninvoked. The prepared
passes. This blocks the final export, production flags, and public cutover; configuration alone does not constitute a migration; confirm a fresh outage
the prepared configuration alone does not constitute a migration. window before stopping the source or switching traffic.
1. Agree on an outage and record source/target versions, pool health, the 1. Agree on an outage and record source/target versions, pool health, the
latest backups, SSH host-key fingerprints, and both current NPM routes. latest backups, SSH host-key fingerprints, and both current NPM routes.