Offsite tier policy engine: mandatory userdata, raw-data quota, restore rework (Task 3a, v0.134.0)

Each toggled app's offsite push = one multi-path restic snapshot (recovery unit + TierOffsite
mandatory userdata via ComputeCaptureSet); legacy/undeployed stay unit-only. Loud capture gaps
(SP-3.4: restic 0.14.0 silently skips missing paths). Quota = stats --mode raw-data (SP-1;
displayed size drops once). Pre-push enlargement gate blocks the userdata enlargement over-quota
(unit-only push continues; EnlargedBlocked; edge-triggered notify). forget --group-by host,tags
on both sites (SP-2). Restore reworked: scratch off the rootfs + headroom gate (F-A1), unit-only
default via --include, size-first full, place-to-live missing-only merge (never --delete).
UI: unit/full-two-step/place actions + per-app blocked note; route POST /backup/offbox/place.
HUB FLAG: offbox_enlarge_blocked event needs hub allowlist for push delivery.
+13 tests; all 10 §10 red-proofs verified. No tier-2/.fab/hub/agent changes.
This commit is contained in:
2026-07-14 22:51:54 +02:00
parent 0c6e151c1c
commit 2d20859858
17 changed files with 1413 additions and 109 deletions
+13 -2
View File
@@ -270,8 +270,19 @@ Each app can define rich metadata in `.felhom.yml`:
`TierOffsite` = mandatory only; `TierSecondary` = mandatory + optional; excluded dropped;
legacy short-circuits to unit-only. Structural guards (traversal / bare HDD drive-root / reserved
`backups/` zone) move refused would-be captures into `Skipped` for the engines to log. Companion
`CrossAppOverlaps` (pure; WARN wiring deferred to 3a/3b). No engine consumes it yet — 3a (offsite)
/ 3b (tier-2) are the consumers. See `felhom.eu/documentation/architecture/07-backup-architecture.md` §3.
`CrossAppOverlaps` (pure; WARN wiring deferred to 3a/3b). See
`felhom.eu/documentation/architecture/07-backup-architecture.md` §3.
- **Offsite tier engine (v0.134.0, Task 3a — consumes the above):** `internal/backup/offbox.go`
+ `offbox_capture.go` + `offbox_restore.go`. Each toggled app's push is ONE multi-path restic
snapshot = recovery unit + its `TierOffsite` mandatory set (`offboxCaptureSet`); legacy/undeployed
stay unit-only. Skipped/missing mandatory paths are loud gaps (English log + Hungarian
`LastWarning`) because restic 0.14.0 silently skips a missing source path (SP-3.4). Quota reads
`stats --mode raw-data` (real repo bytes, SP-1); a pre-push gate blocks an ENLARGEMENT that would
cross the soft quota (unit-only push continues; `OffboxTarget.EnlargedBlocked`; edge-triggered
notify). Retention `forget --group-by host,tags` (SP-2). Restore (`RestoreOffboxScratch`) scratches
to a data drive off the rootfs (F-A1) behind a headroom gate; unit-only default via `--include`
the absolute unit path; `PlaceOffsiteRestore` merges a full scratch into live via
`rsync --ignore-existing` (never `--delete`), refusing on the pure `mapOffsiteRestorePaths` guards.
The `/apps/{slug}` page renders hero section, screenshots, setup guide, and optional config form.