91 lines
9.5 KiB
Markdown
91 lines
9.5 KiB
Markdown
# A backup is never changed by a new install, a restore downloads first, and a restore tells the truth (2026-10-25)
|
||
|
||
Brief: Sunday 2026-10-25, up to 12 h, commit only. Operator rulings D47 and D48 (2026-10-25 08:38, `09` §3 decisions
|
||
256, 257). Box: scratch guest **9202** on demo-hp (`ssh hp`), controller 0.342.0 for the BEFORE proofs (its restore,
|
||
unit and kept-data code is byte-identical to `e1613ae`: no commit between `42cda07` and `e1613ae` touched
|
||
`recovery_unit.go`, `restore_unit.go`, `restore_dbcreds.go`), then **0.343.0-rc.1** built from `bd9f4f7` (an `-rc` tag
|
||
is never offered to a household) for the AFTER proofs; 0.342.0 put back at the end.
|
||
|
||
## Safest readings taken (the brief allowed no question)
|
||
|
||
1. **Install identity:** a new field `install_id` (16 random hex), not the data stamps — the stamps say which VERSION
|
||
wrote the data, not which install. A unit from before D47 has none: then a carried secret that differs on both sides
|
||
means another install (a reinstall mints new ones); with no differing secret it is this install's (the old behaviour).
|
||
2. **Where the set-aside unit lives and how it is removed:** `<namespace>/backups/set-aside/<app>/<UTC stamp>/` on its
|
||
own drive (a rename), recorded as a D25 kept copy — listed on the restore page with „Visszaállítás" and
|
||
„Eltávolítás". The app's remove with „delete its backups too" deletes it too: the D25 / R-1252 H9 rule for kept copies.
|
||
3. **A „partial write"** = one half (definition or data) written into a unit whose other half belongs to another
|
||
install. The writer refuses it when the set-aside cannot be done. A definition-only unit of a fresh install (no data
|
||
yet) is not partial — it is that install's whole unit.
|
||
4. **R-1270 (the brief's own safest reading):** a health timeout does not pour the old data back; the data stays, the
|
||
undo copy is kept (the „kept for support" state a failed put-back leaves — no new retention rule), a late start
|
||
within 30 minutes turns the record into a success and releases the copy.
|
||
5. **D48 sentences:** Docker's „manifest unknown / not found / pull access denied / repository does not exist" reads as
|
||
„the maker no longer offers it"; everything else (busy, rate limit, timeout, unreachable) as „try again later".
|
||
6. **R-1271:** the clock-skew allowance is **10 minutes**. A future-dated copy that is the only copy is still offered
|
||
(it may be the household's own), marked and not recommended. The „another drive" check stays a path check (the
|
||
brief: an old box's data on a new box is what off-site is for).
|
||
7. **The `.fab` import is not a „unit" path** and is not changed here; its gap is a row (R-1279).
|
||
|
||
## Before (9202, controller 0.342.0 = `e1613ae` for these files) — `before/`
|
||
|
||
`catalog-sweep-2026-10-24/tools/unitprobe.py tandoor` on 9202 (`before/_unitprobe/tandoor/`): install, one account,
|
||
„Mentés most" (unit 07:53:46), remove keeping data and backups, install again, wait for the controller's next capture,
|
||
„Visszaállítás".
|
||
|
||
- **R-1269:** 08:00:36 „Recovery unit captured for tandoor" — the kept unit's `app.yaml` checksum `cbfcb538…` →
|
||
`e19d2610…`, `created_at` 07:53:46 → 08:00:36, the data files unchanged. Cause at `file:line`:
|
||
`felhom-controller/controller/internal/backup/recovery_unit.go:194` (`e1613ae`) — the refresh's only „same
|
||
definition" test is the pins (`frozen`), and a reinstall of the same version has the same pins; the writes at
|
||
`:254-258` then put the new install's `app.yaml` (its new database password) beside the old data. The restore takes
|
||
the unit's password (`restore_dbcreds.go` R-1106 rule) — the new one — over the old data directory.
|
||
- **R-1270:** the restore record `ok: true`, „A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás
|
||
újraindult." while the app stayed `starting`; `password authentication failed` ×44 (app) / ×23 (postgres); the account
|
||
not there (`restore_unit.go:567-569` at `e1613ae`: the health failure is a WARN).
|
||
- **Why a volume app's reinstall asks no kept-data question:** the question is about the app's drive FOLDER
|
||
(`${HDD_PATH}` binds under `appdata/`, `stacks/kept.go` `OldAppDataPaths`; `07` §6.5, decision 36). Tandoor keeps
|
||
nothing there; „remove, keep my data" removed its three named volumes (the record's `volumes_removed`) and kept only
|
||
its backup. A gap for the household → **R-1278**.
|
||
|
||
## After (9202, controller 0.343.0-rc.2 = `1f9c863`) — `after/`
|
||
|
||
| proof | before (`e1613ae`) | after |
|
||
|---|---|---|
|
||
| **R-1269 / D47, Tandoor** (`tools/unitprobe2.py`, `after/_unitprobe2/tandoor/`) | the refresh rewrote the kept unit's `app.yaml`; the restore paired the new password with the old data, `starting`, account gone | 09:13:03 „the recovery unit of an earlier install (77ea…; the install ids differ) is SET ASIDE whole → `backups/set-aside/tandoor/20261025T091303Z`"; the set-aside unit is install A's unit byte for byte (9 files, sha256 each); „Megőrzött másolatok" offers „Visszaállítás"; restored in 42 s, `running`, the account reads back, 0 password errors (app and postgres); remove with data leaves nothing, the set-aside folder included |
|
||
| **R-1269 / D47, AdventureLog** (`after/_unitprobe2/adventurelog/`) | (sweep: the same fault on its re-run) | set aside 09:23:03, byte for byte (8 files), restored, `running`, the item back, 0 password errors, nothing left |
|
||
| **D48** (`tools/pullprobe.py vikunja`, `after/_pullprobe/vikunja/`) | the first pull came after the stop (code order, `restore_unit.go` at `e1613ae`) | the unit's pin edited to `vikunja:0.0.0-felhom-d48-missing` → „REFUSED … before anything was touched: … could not be downloaded (the version does not exist any more)"; the household read „…verzióját (vikunja:0.0.0-felhom-d48-missing) nem lehet letölteni: a készítője már nem kínálja. Az alkalmazáshoz nem nyúltunk…"; the container's `StartedAt` the same before and after (09:29:54.57); the item still there; the original unit back byte for byte → a normal restore worked, the item back |
|
||
| **R-1270** (same run) | `ok: true` „…az alkalmazás újraindult." over `starting` | the unit's pin edited to `busybox:1.36.1` (pulls, exits at once) → after the 90 s wait the record `ok: false`, `not_started: true`, „A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet…"; the undo copy kept (`restore-undo/vikunja/20261025T093149Z`); the original back → a normal restore worked, the item back |
|
||
| **R-1271** (`tools/keptprobe.py romm`, `after/_keptprobe/romm/`) | the future-dated off-site copy chosen over the family's own (sweep) | a forged second-drive copy dated 2026-10-28 06:39 beside the app's own unit: the question named „saját mentés, 2026-10-25 10:38", no future flag; the own unit moved away for a moment: the forged copy alone was offered with `use_future: true` and „Figyelem: ennek a mentésnek a dátuma (2026-10-28 07:39) a jövőben van…"; with the own unit back, „use my kept data" loaded tier 1 (`backups/primary/romm`, 09:38:55Z), the item back |
|
||
|
||
**Two findings of the live runs, fixed without a row (each red-proved):** (1) run 1 of the Tandoor proof
|
||
(`after/run1-list-hidden/`) found the restore page listing NO kept copy on 9202: the „Megőrzött másolatok" section sat
|
||
inside the off-site card, and 9202 has no off-site — so a box without off-site never listed a D25 copy either. Fixed in
|
||
`1f9c863` (one named template, two call sites, parity fixtures unchanged); rc.2 then listed it. (2) The R-1270 run's
|
||
kept undo copy outlived the app's remove with its backups; fixed in `1f82959` (the remove deletes
|
||
`backups/restore-undo/<app>`), proven by its test only — the copy on 9202 was removed by hand (2.6 MB, mine).
|
||
`after/run1-probe-bug/` is a probe of mine that crashed on its own bug before touching anything.
|
||
|
||
**The `file:line` causes (at `e1613ae`):** R-1269 `backup/recovery_unit.go:194` (the `frozen` pins test) and `:254-258`
|
||
(the writes); R-1270 `backup/restore_unit.go:567-569`, the same WARN at `offbox_reconstitute.go` (the off-site path),
|
||
`tier2_restore.go` (the file restore) and `restore.go` (the fallback); D48 `restore_unit.go:497` (the stop) before
|
||
`:552` (the first `compose up`, the DB-only start); R-1271 `backup/kept_load.go:179-184` (`KeptNewer`: the newer date
|
||
wins, future or not) and `:97` (the „another drive" check compares `HDD_PATH`, a mount path).
|
||
|
||
**Not covered and filed:** R-1278 (a volume app's reinstall does not offer its kept backup), R-1279 (the `.fab` import
|
||
removes the app's volumes before its pull).
|
||
|
||
**Observation, NOT-A-FINDING:** 9202 logs „immich: definition REFUSED by the allow-list" at every start — it was there
|
||
before this run's image (an earlier session's Immich on `/mnt/sizing-bench`); not this run's.
|
||
|
||
|
||
## Teardown
|
||
|
||
- **Machine:** 9202 only — my throwaway installs (Tandoor ×3, AdventureLog, Vikunja, RomM) each removed through the
|
||
product with their data; the two empty folders my runs left (RomM's `appdata/romm`, the empty `backups/set-aside/`)
|
||
and the one kept undo copy (above) removed by hand after checking they held nothing else; the `0.343.0-rc.1/.2` images
|
||
removed from its Docker; the controller back on 0.342.0 (`/etc/felhom-controller-image`); the apps found running
|
||
(Immich, Paperless-ngx, the file browser, traefik) running. No VM, no LXC created; Tester 1 and the thin pool untouched.
|
||
- **Host:** DooPlex — the session's 9202 password file in the session scratchpad shredded at the end. The rc images
|
||
stay in the registry as test tags (never offered to a household).
|
||
- **Hub:** provisioned nothing (9202 has no hub). **ep0:** nothing.
|