docs(v0.218.0): README names the volume leg and the compose-project attribution; CONTEXT records the blocker
gates / gates (push) Successful in 12s

The README's reconstitution sequence gains the volume replay it never had, and the AppBackup row
states that DiscoverDatabases now prefers the compose project label. CONTEXT records the thing an
operator most needs next: R-354's fix cannot reach the 40 apps that need it most until R-356 is
closed, because the off-site restore still refuses outright for every app that declares no data
drive. The live confirmation was therefore done on calibre-web and paperless-ngx.
This commit is contained in:
2026-08-22 09:58:57 +02:00
parent 5ce3a44645
commit 2da259af38
2 changed files with 377 additions and 342 deletions
+11 -3
View File
@@ -239,7 +239,7 @@ backups, monitoring and notifications. All Proxmox/disk operations are delegated
| **Infra** | `internal/infra/` | Pure renderers (embedded `text/template`) for the base-infra stacks (traefik/cloudflared/filebrowser); **pinned image tags as the single source of truth** (web filebrowser sync delegates here) |
| **Crypto** | `internal/crypto/` | AES-256-GCM encryption for sensitive app.yaml values (passwords, secrets), key management |
| **Sync** | `internal/sync/` | Git-based app catalog sync (clone/pull, content-hash copy) |
| **AppBackup** | `internal/appbackup/` | Self-contained app-data backup primitives: DB dump discovery/execution (`DiscoverDatabases`, `DumpOne`), Docker-volume/app-data discovery (`StackDataProvider`, `DiscoverAppData`), keep-side path helpers (`AppDBDumpPath`, `AppVolumeDumpPath`, `AppDataDir`). `DiscoverDatabases` takes the deployed-stack set so a DB container maps to the right stack even when a slug ends in a DB-role token (M19, v0.62.0). `ListDumpFiles` takes an optional `cached(name,size,mod)` lookup so an unchanged dump isn't re-validated (line-scan) every ~5-min cycle (M18, v0.62.0). No dependency on restic/cross-drive/drive-mount. Imported directly by `appexport` and `storage`. |
| **AppBackup** | `internal/appbackup/` | Self-contained app-data backup primitives: DB dump discovery/execution (`DiscoverDatabases`, `DumpOne`), Docker-volume/app-data discovery (`StackDataProvider`, `DiscoverAppData`), keep-side path helpers (`AppDBDumpPath`, `AppVolumeDumpPath`, `AppDataDir`). `DiscoverDatabases` reads each container's `com.docker.compose.project` label and prefers it as the stack name (**v0.218.0, R-355**) — the label is the stack name by construction, since compose runs with `cmd.Dir` set to the stack directory and no `-p`; the deployed-stack set (M19, v0.62.0) remains the fallback for containers not started by compose, and an attribution that resolves to no known stack now WARNs instead of being returned silently. Before this, `paperless-ngx` (container `paperless-postgres`) was attributed to a non-existent stack `paperless`, so its 72-table PostgreSQL dump landed outside its recovery unit, never reached the off-site copy, and was never restored — and the same value reaching `writeSafetyDump` meant a destructive restore of that app took no undo copy at all. `ListDumpFiles` takes an optional `cached(name,size,mod)` lookup so an unchanged dump isn't re-validated (line-scan) every ~5-min cycle (M18, v0.62.0). No dependency on restic/cross-drive/drive-mount. Imported directly by `appexport` and `storage`. |
| **Backup** | `internal/backup/` | Per-drive 3-layer backup: DB dumps → restic snapshots → cross-drive copies, restore. Re-exposes the `appbackup` primitives via aliases/forwarders (`appbackup_bridge.go`) for the disk/host-side code and the web/api/report consumers. |
| **Storage** | `internal/storage/` | Disk scanning (`lsblk`), partitioning (`sfdisk`), formatting (`mkfs.ext4`), mounting, data migration (`rsync`) |
| **System** | `internal/system/` | System info (`/proc`), CPU collector, mount points, disk usage, FS info |
@@ -554,9 +554,17 @@ Each app can define rich metadata in `.felhom.yml`:
- **Offsite reconstitution (v0.148.0, R-43 — `offbox_reconstitute.go`):** the leg that was missing.
`ReconstituteFromOffsite` (`/backup/offbox/reconstitute`, „Teljes visszaállítás (fájlok +
adatbázis)") makes the live app equal to the chosen snapshot: **safety dump → stop → files
overwritten (`rsyncRestoreOverwrite`: no `--ignore-existing`, no `--delete`) → the DATABASE
SERVICE ONLY started (`StartStackServices`, v0.153.0) → the snapshot's dump replayed
overwritten (`rsyncRestoreOverwrite`: no `--ignore-existing`, no `--delete`) → **the snapshot's
NAMED VOLUMES replayed (`restoreDockerVolumesFrom`, reading the SCRATCH unit — v0.218.0, R-354)**
→ the DATABASE SERVICE ONLY started (`StartStackServices`, v0.153.0) → the snapshot's dump replayed
(`reimportDBDumpsFrom`, reading the SCRATCH unit) → the full stack started → health wait**.
**The volume leg did not exist before v0.218.0** — the archives live inside the recovery unit, whose
placement is (correctly) skipped, so the off-site restore returned files and a database and silently
nothing else. For the 40 of 53 catalogue apps that declare no data drive, that archive is the entire
dataset. Volumes replay BEFORE the database, so a logical dump still wins over a volume-tar copy of
the same database, and inside the stopped window because Docker will not replace a volume in use.
`restoreDockerVolumesFrom` is the local restore path's own replay with an explicit directory — one
implementation, two callers.
Two invariants: nothing is ever deleted (post-snapshot files survive as extras), and the
`pre-restore-` safety dump is verified on disk BEFORE anything is stopped or overwritten — if it
cannot be taken the operation refuses with zero changes. Safety dumps appear in `ListDumpFiles`