Restore-truth run (2026-10-25): D47 + D48 recorded (09 §3 decisions 256, 257; CONTEXT); 07 §6.5 (R-1271 cause + the future-date rule), §6.6 (D48 order, the R-698 sentence), §8.2d (D47), §8.2e (R-1270); register 217 -> 219 (R-1278, R-1279 opened; R-1269/1270/1271 VERIFY; notes R-1206, R-1243 dated check done read-only, R-232 moved one day); STATUS D49 + Monday list; evidence (9202)
gates / gates (push) Successful in 13m44s

This commit is contained in:
admin committed 2026-10-25 11:04:46 +01:00
1 parent 67053335d1
commit 9647f1d158
58 files changed
+1834 -14

No files matched your search

+10
View File
@@ -16,6 +16,16 @@
> and holds nothing of its own; this file does hold its own content, namely the standing rulings below.
> **2026-10-25 08:38 — operator rulings by starting the R-1269 brief: D47 and D48** (`09` §3 decisions 256, 257;
> `audits/restore-truth-2026-10-25/README.md`). **D47:** an existing recovery unit is never changed in part — its
> definition and data are written only together, by a backup of the same install; a unit an earlier install left is set
> aside whole and stays restorable (`07` §8.2d). **D48:** every restore path resolves and pulls every image its
> definition names before the first change to the app; a failed pull is a refusal with the app untouched (`07` §6.6).
> **Confirmed, no change:** `07` §6.6's version rule; R-698 and R-933. Built the same day, controller commits only (rides
> v0.343.0): `02daebe` (D47 / R-1269), `b680db8` (R-1270: a restore whose app does not start is never a success; the
> safest reading — no automatic pour-back, the undo copy kept, a late start turns it into a success), `2d71270` (D48),
> `bd9f4f7` (R-1271: a copy dated more than 10 minutes in the future is never „the newest").
> **2026-10-24 (afternoon, attended) — two DooPlex backup alarms — DONE.** One cause: the 2026-10-23 window reboot
> restarted `postgresql-2`; CNPG 1.29 sets the role `postgres` from the superuser secret, then from the
> `bootstrap.initdb` owner secret (`owner: postgres`), so `postgres-superuser` is in force at every start; the dump
+59
View File
@@ -0,0 +1,59 @@
# REPORT — a backup is never changed by a new install, a restore downloads first, a restore tells the truth (2026-10-25, unattended, commit only)
**Plain words first.**
- **R-1269 is fixed and proven:** a reinstall no longer writes into the old backup. The old backup is set aside whole,
listed on the restore page, and restores with its own password (Tandoor and AdventureLog on 9202: the account back).
- **D48 download-first on every restore path that starts a definition (the unit restore and with it the second
drive's unit and whole restore, both kept-data loads and a set-aside unit; the off-site restore; the second drive's
file restore; the no-unit fallback).** An unpullable version is refused and the app is never stopped (proven).
- **R-1270:** a restore whose app does not start says so — never „it worked"; the old copy is kept (proven).
- **R-1271:** cause — the other box's off-site copy was dated in the future (its clock), and „newest wins" took it;
now a copy more than 10 minutes in the future never counts as the newest (proven on a forged copy).
- **One question for you:** D49 in STATUS — after a restore whose app does not start, keep the data and wait (CC's
pick, done) or pour the old data back by itself.
**Safest readings taken** (also at the head of `documentation/audits/restore-truth-2026-10-25/README.md`): an install
identity field, not the data stamps; the set-aside unit is a D25 kept copy (remove-with-backups deletes it, as it does
other kept copies); a „partial write" means one half into another install's unit; R-1270 keeps the data and the undo
copy (no new retention rule); D48 tells a deleted version from a busy registry by Docker's answer; 10 minutes of clock
skew; the `.fab` import left to its own row.
**Architecture read:** `07` §6.3, §6.5, §6.6, §8 (D20/D22, §8.2a D25); `03` (drive identity); `09` §3 decisions 14,
40, 53; rows R-1269–R-1271, R-1206, R-698, R-933, R-1267.
**Each proof, before (`e1613ae`, 9202 on 0.342.0) → after (9202 on 0.343.0-rc.2):**
- R-1269: the kept unit's `app.yaml` rewritten (`cbfcb538…` → `e19d2610…`), restore broken → set aside byte for byte,
restored, the account back, 0 password errors (Tandoor, AdventureLog).
- R-1270: `ok: true` over a `starting` app → `ok: false`, `not_started`, the honest sentence, the undo copy kept.
- D48: the first pull after the stop (code order) → an unpullable pin refused, the container's start time unchanged.
- R-1271: the future copy chosen (sweep) → the own copy chosen; alone, the future copy marked and said.
**Commits.** Controller: `02daebe` (D47) CI 2311 ✓, `b680db8` (R-1270) 2312 ✓, `2d71270` (D48) 2313 ✓, `bd9f4f7`
(R-1271) 2314 ✓, `1f9c863` (the kept list without off-site) 2315 ✓, `1f82959` (remove deletes the undo copy) 2316 ✓,
`e8d2151` (REPORT). felhom.eu: this commit (`07` §6.5/§6.6/§8.2d/§8.2e, `09` §3 decisions 256–257, CONTEXT, register,
STATUS, STATUS-HISTORY, the audit with its evidence and tools) — CI by hash in the final message. Test images
`0.343.0-rc.1` (`bd9f4f7`) and `0.343.0-rc.2` (`1f9c863`) in the registry; nothing released.
**Teardown.** Machine: 9202 only; my throwaway apps removed through the product; two empty folders and one undo copy of
mine removed by hand after checking; my rc images removed from its Docker; back on 0.342.0; the apps found running still
run. Host: the session's 9202 password and session files shredded. Hub: provisioned nothing. ep0: nothing.
**Register:** before 217, after 219; opened 2 (R-1278, R-1279), closed 0; R-1269, R-1270, R-1271 → VERIFY (fixed on
main, wait for the release); notes on R-1206 (cross-reference) and R-1243 (its dated check done, read-only); R-232's
dated check moved one day (this brief: no DooPlex work).
**Observations:**
- FILED: R-1278 (a volume app's reinstall does not offer its kept backup).
- FILED: R-1279 (the `.fab` import removes the app's volumes before its pull — D48 does not cover it).
- FIXED without a row (found live, red-proved): the kept-copies list was hidden on a box without off-site (`1f9c863`);
a kept undo copy outlived remove-with-backups (`1f82959`).
- NOT-A-FINDING: Immich's 404 after the sweep's load — the copy brought its own address (`photos.`), as every unit
restore does; the app was healthy there.
- NOT-A-FINDING: „immich: definition REFUSED by the allow-list" on 9202 — present before this run's image; an earlier
session's Immich, not this change.
- NOT-A-FINDING: the D48 refusal reads „Visszaállítás sikertelen: …nem lehet letölteni… Az alkalmazáshoz nem
nyúltunk" — the page's failure frame around an honest sentence; true as written.
**Secrets:** used only through `sec`; the evidence scanned for the box password and the session tokens (the CSRF token
in two saved page snippets was the positive control and is blanked); units compared by sha256 only. **System poster:**
no fact changed. **Instruction files:** none edited.
+5 -4
View File
@@ -2,7 +2,7 @@
**Ready for Tester 2? Yes — tell the friend go (hand-over list: `documentation/pilot/TESTER2-handover.md`).**
**Updated 2026-10-25 (night).** 65 catalog apps walked on a fresh box: 52 work, 10 with a problem, 0 broken; worst: a restore after a reinstall breaks an app (R-1269).
**Updated 2026-10-25 (Sunday).** A reinstall keeps old backups whole; a restore downloads first and tells the truth.
## What works
- **The Plex attack (10-17 → 10-23) did not reach Felhom's secrets** (R-1241).
@@ -18,13 +18,13 @@
- The hub mails alarms and recoveries and keeps a weekly whole-box copy on ep0; CC uses its own limited token (map F1, F11, G15).
## What is broken
- **P1 (fixed on main, NOT released):** a power cut while an app on a drive is restored can later pour old data over new data (R-1251); one wrong line in an app's backup settings deletes its second-drive copy (R-1252, P2).
- **P1 (fixed on main, NOT released):** a power cut while an app on a drive is restored can later pour old data over new data (R-1251); one wrong line in an app's backup settings deletes its second-drive copy (R-1252, P2); three restore holes (R-1269–R-1271).
- **P1:** any hacked app on DooPlex can read the hub's nightly backups, and maybe the signing keys (R-1241, homelab session); about 12 homelab logins still use a password that was public, and the public Gitea repo is not decided (R-925, yours).
- Tester 2 is off since 2026-10-04: old emergency password, no off-site check, no bootstrap (R-1139, R-1104, R-862). The scratch box cannot use agent features (R-1083).
- DooPlex: something sets the shared checkout to „bare" (R-935).
## What we work on
- Dated checks, run by CC. 2026-10-25: tonight's database copy reaches the restic sweep (R-232). 2026-10-26: each hub daily job ran once on the clock-change day (R-1183). 2026-10-27: the weekly clean-up window (R-435; the logs of the last one were lost to restarts). 2026-10-30: D35, website and installer to Cloudflare, starts (R-1228, ruled yes).
- Dated checks, run by CC. 2026-10-26: tonight's database copy reaches the restic sweep (R-232); each hub daily job ran once on the clock-change day (R-1183). 2026-10-27: the weekly clean-up window (R-435; the logs of the last one were lost to restarts). 2026-10-30: D35, website and installer to Cloudflare, starts (R-1228, ruled yes).
## Waiting on you — decide
@@ -32,6 +32,7 @@ Newest first. CC's pick is in the „Pick" column. Each row also has its full te
| # | Row | Question | Pick (why) | The other option costs | If you do nothing | Blocked |
|---|---|---|---|---|---|---|
| D49 | R-1270 | A restore's data is back but the app does not start in 90 s. Keep the restored data and wait, or pour the old data back by itself? | **Keep and wait** (a slow first start is common; the old copy is kept, a late start turns it into a success; you get one alarm) | pour back by itself: a slow but correct restore is thrown away | the data stays restored, the old copy stays for support | — |
| D46 | R-1272 | Put Homepage behind the family gate, like Glance? | **Yes** (a stranger now sees the box's CPU, memory and disk, and later the family's links; one line in the template, tried on the drill first) | leave it open: anyone who finds the address sees those | it stays open to strangers | — |
| D45 | R-1266 | Seven places where a design document says one thing and the code does another. One line each, with a pick: `documentation/audits/review-round-2-2026-10-24/D44-D45-picks.md`. Answer „all picks" or name the ones you disagree with. | **Yes — the text follows the code** (each is how the system works today; you read every changed sentence) | the code follows the text: six code changes, one of them to the box's door for you | the documents keep saying what is not true | — |
| D44 | R-1265 | 25 behaviours the code has but no document named. One line each, with a pick (21 confirm, 4 small changes): the same file. Answer „all picks" or name the ones you disagree with. | **All picks** (each was read in the code) | name the ones to change; each becomes a row | the notes stay marked „undocumented" | — |
@@ -51,7 +52,7 @@ Newest first. CC's pick is in the „Pick" column. Each row also has its full te
6. BIOS: on the N100, read „After Power Loss"; on the HP, set „Bypass F1 Prompt on Configuration Changes" to Enable. Since 2026-10-14.
7. Pin the first Facebook post (R-917), and look at the new cover in the Facebook phone app (R-919). Since 2026-10-12.
**Monday 2026-10-26, release list (attended).** 1. Controller v0.343.0: the restore and backup fixes (R-1251–R-1253), R-1250. If not: the P1 stays open. 2. Agent v0.165.0 + your vouch: BYO keeps its SSH (R-1257). 3. Hub v0.170.0: the recovery package in one step (R-1254). 4. Catalog: n8n usage data off; OpenGist 256 MB.
**Monday 2026-10-26, release list (attended).** 1. Controller v0.343.0: the restore and backup fixes (R-1251–R-1253, R-1269–R-1271), R-1250. If not: the P1 stays open. 2. Agent v0.165.0 + your vouch: BYO keeps its SSH (R-1257). 3. Hub v0.170.0: the recovery package in one step (R-1254). 4. Catalog: n8n usage data off; OpenGist 256 MB.
**Day, 2026-10-23 — your do list.**
1. Print the break-glass sheet (`documentation/runbooks/break-glass-sheet.md`, the part from „The sheet”, one A4), fill it by hand, keep it outside the home (D32; R-923, R-924). If not: after a DooPlex loss no off-site copy opens.
2. On the hub's Hosts page: Discard appliance 49, and delete host Tester4-aa9466. If not: two dead entries alarm.
@@ -937,6 +937,26 @@ holds no database, or **does not record its data version** (§6.6 — a unit wri
a mixed or mismatched unit is refused by §6.6's own rule. Then a dated folder's files move back and the one unit restore
runs. The downloaded copy is removed on every path. A failed download or a refusal leaves the kept files as they were.
**[FACT] 2026-10-25 — why the „another drive" check let another box's copy through (R-1271; catalog sweep 2026-10-24,
Tester4: Paperless-ngx, Nextcloud, Immich).** The check (`KeptCopyAt`, `internal/backup/kept_load.go`) compares the
unit's `app.yaml` `HDD_PATH` with the drive the kept files sit on. That is a mount PATH, and every box mounts its first
data drive under the same name (`/mnt/felhom-drives/hdd_1`) — so a copy taken of another box's disk matches. It is a
path check, not a physical identity, and it stays one: loading an old box's data on a new box is what off-site is for.
What chose the wrong copy was the DATE: the rehearsal box's clock had been set to 2026-10-28, its off-site snapshot
carried that time, this box had no data time of its own to cap it (`offsiteDataTime`), and „newer than every local
copy" took it over the family's own copy of the same evening. Immich's 20 minutes of 404 were the ROUTING, not a half
load: the unit brought the old box's `SUBDOMAIN` (`photos`), so the app answered at `photos.<domain>` and the probe
asked `immich.<domain>`; its containers were healthy.
**[DESIGN] 2026-10-25 — a copy dated in the future is never „the newest" (R-1271; brief 2026-10-25 Part 4, the safest
reading; controller `bd9f4f7`, unreleased, rides v0.343.0).** A copy whose date lies more than **10 minutes** past this box's clock (`KeptClockSkew`: NTP keeps a box
within a second; ten minutes covers a box that synced late after a power cut) is future-dated. It never counts as the
newest: the newest copy with a true date wins (a tie goes local); the household's own local copy wins over any
off-site copy that is not newer by a true date. A future-dated copy is offered only when no other copy exists — then
the question says its date is in the future and does not recommend it (the „use" button is not the primary one). The
question always names the copy it will use. An off-site copy is never refused only because another box of the
household made it.
**It is NOT backed up.** No tier captures `<drive>/kept` or a leftover `appdata/<x>` of a removed app; the page says
so („Erről nem készül mentés." / "This is not backed up."). What brings it back into an app is the unit it carries
(or the app's own unit on the drive), listed per row as „Visszatölthető innen" / "Can be loaded from".
@@ -1027,13 +1047,28 @@ requires a database dump written after the conversion whose recorded engine is t
image is re-pulled on restore. A version its maker has deleted cannot start. Measured 2026-09-26: the 42 ladder
digests all resolve today (`audits/version-travel-2026-09-26/A7/`). **Accepted as a known limit — operator ruling
2026-10-12 (S8, `09` §3 decision 203; R-698 closed).** What a household meets: a restore whose version the maker has
deleted stops at the image pull, the app stays as it was, and the household uses another copy or a newer version.
deleted stops at the image pull, the app stays as it was (since D48 by ORDER — the pull comes before the stop; before it,
by the D22 put-back), and the household uses another copy or a newer version.
The operator's follow-up — *„will need to mirror the apps we support later, just to be sure"* — is **R-933**: mirror
the images of every catalog app we support into our own registry, before scale. Until it is built, this limit holds.
**Since decision 53 (`09` §3, 2026-09-30 evening):** a box keeps only the image each app service runs and the one before
it; older images of the app are deleted. A restore that needs an older version re-pulls it — as before; the limit
above is unchanged, and kept data (decision 40) is not touched by the image clean-up.
**[DESIGN] 2026-10-25 — a restore downloads first (operator ruling D48, `09` §3 decision 257; controller `2d71270`,
unreleased — rides v0.343.0).** „The app stays as it was" in the paragraph above is now true BY ORDER, not by the D22
put-back. After the existing refusals and before the first stop or change, every restore path resolves every image the
definition that will RUN names — present on the box, or pulled now — under the image-work lock held from the first
check until the restore's last start (R-863: the image clean-up cannot delete a pulled image before the start). A pull
that fails is a REFUSAL: the app is not stopped, its data is not moved, and the household reads which version, and
why: „Ennek a mentésnek a verzióját (%s) nem lehet letölteni: a készítője már nem kínálja…" when Docker says the version
does not exist (`manifest unknown`, `not found`, `pull access denied`), „…most nem lehet letölteni… Próbáld újra
később…" for everything else (a busy or unreachable registry). The operator gets one `backup_run_failures` line (leg
`restore-image-unavailable`). The paths: the local unit restore — and with it the second drive's unit restore, the
second drive's whole restore (its `CheckOnly` pass carries the pull, so its file half never runs first), a kept-data
load from a unit and from off-site, and a set-aside unit (D47) — the off-site restore, the second drive's file restore
and the no-unit fallback. Not covered: the `.fab` import — it removes the running app's volumes before its definition is pulled (R-1279).
### 6.7 Why an off-site run failed — the failure classifier (R-571)
**[FACT, read from source 2026-10-05, felhom-controller `114ff27`]** When an off-site (restic) run fails, the
@@ -1651,6 +1686,60 @@ removed the old container at start; a restore probe during the copy was refused;
15-second test limit → the container removed, the partial copy stopped growing. Controller:
`internal/backup/tier2_copy_lifecycle.go`.
### 8.2d An earlier install's unit is set aside, never rewritten [DESIGN, operator ruling D47, 2026-10-25 08:38; `09` §3 decision 256; R-1269 — controller `02daebe`, unreleased, rides v0.343.0]
**[FACT] Measured before (controller 0.342.0, which is `e1613ae` for these files):** the catalog sweep 2026-10-24
(three times) and 9202 2026-10-25 (`audits/restore-truth-2026-10-25/before/`): Tandoor, one account, „Mentés most",
remove keeping data and backups, install again. Within five minutes the periodic refresh rewrote ONLY the unit's
`compose/app.yaml` (checksum `cbfcb538…` → `e19d2610…`) — the new install's database password — beside the OLD install's
data. Its one „same definition" test was the image pins (`recovery_unit.go:194`, the R-696 `frozen` rule), and a
reinstall of the same version has the same pins. A restore then paired the new password with the old data directory:
`password authentication failed` ×44 (app) / ×23 (postgres), the app stayed `starting`, the account was gone from
view. The next night's dump legs would have overwritten the old data as well, and the stale-drive prune deleted a unit
on another drive whatever install it belonged to.
**[DESIGN] The rule.** A recovery unit's definition and its data are written only together, by a backup of the same
install. Every install has an identity (`install_id` in its `app.yaml`): minted at a fresh install and at a `.fab`
import, carried by every load-then-save, taken FROM the unit by a restore (the app then holds that unit's data), and
minted once for an app installed before D47. The unit records it in its own `app.yaml`. Every writer of a unit asks
first — the capture and its refresh, the night database dump, the pre-backup and the update's pre-backup dumps, the
volume dump, the second drive's unit copy, the stale-drive prune. A unit of ANOTHER install (different identities; for
a unit written before D47, a carried secret that differs — a reinstall mints new ones) is **moved whole** (a rename on
its own drive; never a copy, never an edit) to `<namespace>/backups/set-aside/<app>/<UTC stamp>/` and recorded as a
kept copy (§8.2a's record: kind `unit`, reason `earlier_install`); the new install then gets its own unit from its
first backup. A move that cannot be done **refuses the write** and leaves the old unit byte for byte (an ERROR line
names the app and both identities). Nothing moves a unit while that app is being restored.
**What the household sees.** The restore page lists the set-aside unit under „Megőrzött másolatok" („…· egy korábbi
telepítés mentése") with **„Visszaállítás"** — the one unit-restore body, with the unit's own database password — and
„Eltávolítás". Only those two, and the app's remove with „delete its backups too", ever remove it (the §8.2a / R-1252 H9
rule). After a restore from it, the app is that unit's install; the app's own unit of the install it replaced is then
set aside at the next backup — never lost.
**Known limit, stated.** A box that reinstalled and refreshed BEFORE this release already holds a rewritten unit; its
old password is gone and nothing can bring it back. A unit from before D47 of an app with no carried secret has no
signal: it is treated as this install's (the old behaviour) and gets the identity at its next write.
**Why a volume app's reinstall asks no kept-data question:** the question (§6.5, decision 36) is about the app's drive
FOLDER (`${HDD_PATH}` binds under `appdata/`); a volume app keeps nothing there — „remove, keep my data" removes its named
volumes and keeps only its backups — so the install starts empty, and the kept backup is now the set-aside unit on the
restore page. (The missing offer at install is its own row, R-1278.)
### 8.2e A restore whose app does not start is never „success" [DESIGN, R-1270, 2026-10-25 — controller `b680db8`, unreleased, rides v0.343.0]
**[FACT] Measured before:** the health check after a restore was a WARN on every path, so a Tandoor restore whose app
stayed `starting` was recorded `ok: true`, „…visszaállítva — az alkalmazás újraindult." (catalog sweep 2026-10-24; 9202
2026-10-25, `audits/restore-truth-2026-10-25/before/`). **[DESIGN]** Every restore path — the unit restore (so the
second drive's unit and whole restore, a kept load, a set-aside unit), the off-site restore, the second drive's file
restore and the no-unit fallback — returns „not started" when the app does not reach running within the health wait
(90 s). The household reads „A(z) %s: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet,
majd nyisd meg újra. Ha így marad, szólj nekünk." (the record `ok: false`, `not_started: true`); the operator gets one
`backup_run_failures` line (leg `restore-not-started`). **The safest reading, the operator may change it:** a timeout
does NOT pour the old data back — a slow first start (a database migration) is common, and a put-back would throw away a
correct restore. The data stays restored and the restore's undo copy is KEPT — the same „kept for support" state a failed
put-back leaves (§8 D20/D22), so no new retention rule. A watcher looks 30 more minutes: when the app runs, the record
turns to success („…elindult: a visszaállítás kész.") and the copy is removed, as after any successful restore; if it
never runs, the copy stays for support.
### 8.2c Decisions in code that no document recorded [DESIGN — undocumented, found 2026-10-24, review round 2 — the operator confirms them through one register row]
Read at `file:line` on `main` (controller `321df1f`, hub `44d19042`) by the main session:
@@ -1089,6 +1089,24 @@ its length, and both fixes cost something the household would notice — operato
`plex: security fix CVE-2026-96652,CVE-2026-96654,CVE-2026-96655,CVE-2026-96656 taken` and ended `done` in 30.1 s
after the update's second-drive copy and undo copy. Released as controller 0.341.0, global floor 11:24 UTC.
### 2026-10-25 (08:38) — D47: nothing changes an existing backup; D48: a restore downloads first (operator rulings by starting the run; R-1269)
256. **An existing recovery unit is never changed in part (D47).** A unit's definition (compose, pins, `app.yaml`) and
its data are written only together, by a backup of the same install. A new install never adopts, rewrites or
deletes a unit that an earlier install left; that unit is set aside whole and stays restorable. *Operator ruling
2026-10-25 08:38, taken by starting the R-1269 brief.* Why: on 2026-10-24 the catalog sweep measured three times
that a reinstall's own five-minute refresh wrote the new install's database password into the unit „remove — keep
backups" had kept; a restore then could not open the old data (R-1269). Built: every install has an identity
(`install_id`), the unit records it, and every writer asks first — `07` §8.2a „An earlier install's unit is set
aside" (controller, unreleased; rides v0.343.0).
257. **Every restore path resolves and pulls every image its definition names before the first change to the app; a
failed pull is a refusal with the app untouched (D48).** *Operator ruling 2026-10-25 08:38, taken by starting the
brief.* Why: the restore stopped the app and moved its data first and pulled only inside the database-only start;
a version that could not be downloaded then ended in the D22 put-back, a full stop-and-pour-back for nothing.
Built: `07` §6.6 „The limit — the image is not in the backup" (controller, unreleased; rides v0.343.0).
**Confirmed, no change:** `07` §6.6 — a restore brings the app back at its data's version, then the guarded update
climbs the ladder; R-698 (a deleted version cannot come back) and R-933 (our own image mirror) are unchanged.
### 2026-10-15 (07:48–08:20) — bake first; Plex through its own key; HSTS one day (recorded before the media build)
247. ~~**Bake first.** A controller version is RELEASED only when its golden is baked.~~ **REVERSED by decision 250,
@@ -0,0 +1,90 @@
# 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.
@@ -0,0 +1,43 @@
{
"app": "romm",
"install": true,
"seed": true,
"backup": {
"code": "200",
"seconds": 75,
"status": "{\"db_dump\": {\"count\": 3, \"duration\": \"1m13.631435029s\", \"last_run\": \"2026-10-25T09:40:08.943436205Z\", \"success\": true}, \"enabled\": true, \"running\": false}"
},
"own_unit": "/mnt/felhom-drives/scratch_hdd/backups/primary/romm",
"forged_at": "\"at\": \"2026-10-28T06:39:39Z\"\n\"at\": \"2026-10-28T06:39:39Z\"",
"q1_code": "409",
"q1": {
"code": "kept_data_choice",
"use_offered": true,
"use_desc": "Az adatbázist innen töltjük vissza: saját mentés, 2026-10-25 10:38. A régi fájlokkal indítjuk az alkalmazást. Ami ezután változott, hiányozhat.",
"use_future": null,
"use_warn": null,
"use_off": ""
},
"q2_code": "409",
"q2": {
"code": "kept_data_choice",
"use_offered": true,
"use_desc": "Az adatbázist innen töltjük vissza: második meghajtó, 2026-10-28 07:39. A régi fájlokkal indítjuk az alkalmazást. Ami ezután változott, hiányozhat.",
"use_future": true,
"use_warn": "Figyelem: ennek a mentésnek a dátuma (2026-10-28 07:39) a jövőben van. A mentést készítő doboz órája rosszul járhatott. Csak akkor válaszd, ha biztosan ez a ti mentésetek.",
"use_off": ""
},
"use_ok": true,
"load_status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"romm\", \"started_at\": \"2026-10-25T09:41:21.356199563Z\", \"last\": {\"op\": \"restore\", \"stack\": \"romm\", \"ok\": true, \"message\": \"A(z) RomM a megőrzött adataival fut.\", \"finished_at\": \"2026-10-25T09:43:03.140979706Z\"}, \"last_recent\": true}",
"item_after_load": true,
"controller_lines": "2026/10/25 09:41:10 image_retention.go:238: [INFO] [stacks] image retention (remove of romm): pass over 42 image(s) of [mariadb redis rommapp/romm] — 2 candidate(s), 2 deleted, the rest kept\n2026/10/25 09:41:14 kept_install.go:88: [INFO] [api] Deploy romm: the drive already holds its old data [/mnt/felhom-drives/scratch_hdd/appdata/romm/resources] (0 bytes) — asking the household (use offered: true, future-dated: false)\n2026/10/25 09:41:17 kept_install.go:88: [INFO] [api] Deploy romm: the drive already holds its old data [/mnt/felhom-drives/scratch_hdd/appdata/romm/resources] (0 bytes) — asking the household (use offered: true, future-dated: true)\n2026/10/25 09:41:21 kept_install.go:105: [INFO] [api] Deploy romm: USE MY KEPT DATA — loading from /mnt/felhom-drives/scratch_hdd/backups/primary/romm (tier 1, 2026-10-25T09:38:55Z) under the kept files [/mnt/felhom-drives/scratch_hdd/appdata/romm/resources]\n2026/10/25 09:41:21 restore_dbcreds.go:114: [INFO] [backup] Restore romm: database password(s) [DB_PASSWORD MYSQL_ROOT_PASSWORD] come from the backup, [] stay as installed (older than the data) — values not logged (R-1106)\n2026/10/25 09:41:21 restore_unit.go:410: [INFO] [backup] Restoring romm from recovery unit /mnt/felhom-drives/scratch_hdd/backups/primary/romm: images=3, secrets recovered=3/3, data_keys=0\n2026/10/25 09:41:32 restore_pull.go:103: [INFO] [backup] Restore romm: downloaded rommapp/romm:5.3.1@sha256:0d66b4ea152a237c7f24b95e87459f46d3c596a7c0b35a82f26bc971487278b3 before anything was touched (11s, D48)\n2026/10/25 09:41:57 restore_pull.go:103: [INFO] [backup] Restore romm: downloaded mariadb:11.8@sha256:79d59758afc91b89b120b0a8904d637f5a3b3e1c4900f29b740d6d46c72fef68 before anything was touched (25s, D48)\n2026/10/25 09:41:57 restore.go:225: [INFO] [backup] Restoring Docker volume romm_romm_assets for romm\n2026/10/25 09:42:08 restore.go:263: [DEBUG] [backup] Volume romm_romm_assets restored successfully\n2026/10/25 09:42:08 restore.go:225: [INFO] [backup] Restoring Docker volume romm_romm_config for romm\n2026/10/25 09:42:08 restore.go:263: [DEBUG] [backup] Volume romm_romm_config restored successfully\n2026/10/25 09:42:08 restore.go:225: [INFO] [backup] Restoring Docker volume romm_romm_db_data for romm\n2026/10/25 09:42:13 restore.go:263: [DEBUG] [backup] Volume romm_romm_db_data restored successfully\n2026/10/25 09:42:13 restore.go:225: [INFO] [backup] Restoring Docker volume romm_romm_redis_data for romm\n2026/10/25 09:42:13 restore.go:263: [DEBUG] [backup] Volume romm_romm_redis_data restored successfully\n2026/10/25 09:42:13 restore.go:268: [INFO] [backup] Restored 4 Docker volume(s) for romm\n2026/10/25 09:42:14 restore_db.go:87: [INFO] [backup] Restore romm: replaying DB dump into romm-db (mariadb)\n2026/10/25 09:42:18 restore_db.go:97: [INFO] [backup] Restore romm: replayed 1 DB dump(s)\n2026/10/25 09:42:32 restore.go:308: [DEBUG] [backup] Post-restore health check: romm not yet running, waiting...\n2026/10/25 09:42:37 restore.go:308: [DEBUG] [backup] Post-restore health check: romm not yet running, waiting...\n2026/10/25 09:42:42 restore.go:308: [DEBUG] [backup] Post-restore health check: romm not yet running, waiting...\n2026/10/25 09:42:48 restore.go:308: [DEBUG] [backup] Post-restore health check: romm not yet running, waiting...\n2026/10/25 09:42:53 restore.go:308: [DEBUG] [backup] Post-restore health check: romm not yet running, waiting...\n2026/10/25 09:42:58 restore.go:308: [DEBUG] [backup] Post-restore health check: romm not yet running, waiting...\n2026/10/25 09:43:03 restore.go:303: [DEBUG] [backup] Post-restore health check: romm is running\n2026/10/25 09:43:03 restore_unit.go:591: [INFO] [backup] Restore-from-unit completed: romm — 4 volume(s) of 4 listed, 1 database(s) of 1 listed\n2026/10/25 09:43:03 kept_load.go:391: [INFO] [backup] kept load romm from /mnt/felhom-drives/scratch_hdd/backups/primary/romm done in 1m42s (volumes 4/4, dbs 1/1)\n",
"after_remove": {
"containers": "",
"volumes": "",
"appyaml": "",
"appdata": "/mnt/felhom-drives/scratch_hdd/appdata/romm",
"kept": "",
"subdomain": "404"
},
"forged_left": "ls: cannot access '/mnt/sys_drive/felhom-data/backups/secondary/romm': No such file or directory"
}
@@ -0,0 +1,21 @@
09:37:11Z [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
09:37:12Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:38:47Z [1] deployed, controller state=running, pinned={'romm': 'rommapp/romm:5.3.1', 'romm-db': 'mariadb:11.8', 'romm-redis': 'redis:7-alpine'}
09:38:47Z gate: arcade is gated — passed as the household (cookie set)
09:38:55Z romm: POST /api/users http=201
09:38:55Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:40:23Z forged a future-dated second-drive copy: "at": "2026-10-28T06:39:39Z"
"at": "2026-10-28T06:39:39Z" (own unit: /mnt/felhom-drives/scratch_hdd/backups/primary/romm)
09:41:09Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'romm', 'volumes_removed': ['romm_romm_assets', 'romm_romm_config', 'romm_romm_db_data', 'romm_romm_redis_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': ['/mn
09:41:14Z [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
09:41:14Z [question, own copy + future copy] 409 {"code": "kept_data_choice", "use_offered": true, "use_desc": "Az adatbázist innen töltjük vissza: saját mentés, 2026-10-25 10:38. A régi fájlokkal indítjuk az alkalmazást. Ami ezután változott, hiányozhat.", "use_future": null, "use_warn": null, "use_off": ""}
09:41:17Z [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
09:41:17Z [question, the future copy alone] 409 {"code": "kept_data_choice", "use_offered": true, "use_desc": "Az adatbázist innen töltjük vissza: második meghajtó, 2026-10-28 07:39. A régi fájlokkal indítjuk az alkalmazást. Ami ezután változott, hiányozhat.", "use_future": true, "use_warn": "Figyelem: ennek a mentésnek a dátuma (2026-10-28 07:39) a jövőben van. A mentést készítő doboz órája rosszul járhatott. Csak akkor válaszd, ha biztosan ez a ti mentésetek.", "use_off": ""}
09:41:21Z [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
09:41:21Z [1] deploy -> 202 {'ok': True, 'message': 'A(z) RomM megőrzött adatainak betöltése elindult.'}
09:42:26Z [1] deployed, controller state=degraded, pinned={'romm': 'rommapp/romm:5.3.1', 'romm-db': 'mariadb:11.8', 'romm-redis': 'redis:7-alpine'}
09:42:26Z [1] NOTE: the controller's own state is 'degraded', not 'running' — recorded, not treated as a failure; the fixture's front-door wait is the real gate
09:43:06Z load: {"running": false, "op": "restore", "stack": "romm", "started_at": "2026-10-25T09:41:21.356199563Z", "last": {"op": "restore", "stack": "romm", "ok": true, "message": "A(z) RomM a megőrzött adataival fut.", "finished_at": "2026-10-25T09:43:03.140979706Z"}, "last_recent": true}
09:43:15Z romm: login as the seeded user http=200 ok=True
09:43:15Z item after use-my-kept-data: True
09:43:57Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'romm', 'volumes_removed': ['romm_romm_assets', 'romm_romm_config', 'romm_romm_db_data', 'romm_romm_redis_data'], 'hdd_paths_removed': ['/mnt/felhom-drives/scratch_hdd
@@ -0,0 +1,68 @@
{
"app": "vikunja",
"install": true,
"seed": true,
"backup": {
"code": "200",
"seconds": 55,
"status": "{\"db_dump\": {\"count\": 2, \"duration\": \"50.712761411s\", \"last_run\": \"2026-10-25T09:31:32.624265386Z\", \"success\": true}, \"enabled\": true, \"running\": false}"
},
"unit_files": 7,
"d48_restore": {
"points": 1,
"location": [
"location: /backups/restore?flash=flash.restore.started"
],
"seconds": 10,
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"vikunja\", \"started_at\": \"2026-10-25T09:30:11.335278772Z\", \"last\": {\"op\": \"restore\", \"stack\": \"vikunja\", \"ok\": false, \"message\": \"Visszaállítás sikertelen: Ennek a mentésnek a 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. Válassz egy másik mentést.\", \"finished_at\": \"2026-10-25T09:30:13.943080647Z\"}, \"last_recent\": true}",
"state": "running"
},
"d48_started_before": "/vikunja 2026-10-25T09:29:54.57273795Z running",
"d48_started_after": "/vikunja 2026-10-25T09:29:54.57273795Z running",
"d48_app_untouched": true,
"d48_item_still_there": true,
"original_back_1": true,
"normal_restore_1": {
"points": 1,
"location": [
"location: /backups/restore?flash=flash.restore.started"
],
"seconds": 10,
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"vikunja\", \"started_at\": \"2026-10-25T09:30:31.410646319Z\", \"last\": {\"op\": \"restore\", \"stack\": \"vikunja\", \"ok\": true, \"message\": \"A(z) vikunja: 2 adatkötet visszaállítva — az alkalmazás újraindult.\", \"finished_at\": \"2026-10-25T09:30:38.26202133Z\"}, \"last_recent\": true}",
"state": "running"
},
"item_after_normal_restore_1": true,
"r1270_restore": {
"points": 1,
"location": [
"location: /backups/restore?flash=flash.restore.started"
],
"seconds": 100,
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"vikunja\", \"started_at\": \"2026-10-25T09:31:47.895007425Z\", \"last\": {\"op\": \"restore\", \"stack\": \"vikunja\", \"ok\": false, \"message\": \"A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.\", \"finished_at\": \"2026-10-25T09:33:26.51097487Z\", \"not_started\": true}, \"last_recent\": true}",
"state": "restarting"
},
"r1270_state": "restarting",
"r1270_undo_copies": "20261025T093149Z",
"original_back_2": true,
"normal_restore_2": {
"points": 1,
"location": [
"location: /backups/restore?flash=flash.restore.started"
],
"seconds": 10,
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"vikunja\", \"started_at\": \"2026-10-25T09:33:38.075945508Z\", \"last\": {\"op\": \"restore\", \"stack\": \"vikunja\", \"ok\": true, \"message\": \"A(z) vikunja: 2 adatkötet visszaállítva — az alkalmazás újraindult.\", \"finished_at\": \"2026-10-25T09:33:44.816871537Z\"}, \"last_recent\": true}",
"state": "running"
},
"item_after_normal_restore_2": true,
"controller_lines": "2026/10/25 09:30:11 handlers.go:1852: [DEBUG] [web] backupRestoreHandler: stack=vikunja snapshot=helyi from 172.18.0.4:37324\n2026/10/25 09:30:11 handlers.go:1894: [WARN] [web] Restore requested (async): stack=vikunja, snapshot=helyi from 172.18.0.4:37324\n2026/10/25 09:30:11 restore_unit.go:398: [INFO] [backup] Restore vikunja: the data belongs to [vikunja/vikunja:0.0.0-felhom-d48-missing] (written 2026-10-25T09:29:53Z); the app ran [vikunja/vikunja:2.6.0@sha256:417ada6f94e81f0267aa2f007d0a811fc82d38dd2aa58351e3ea520ca01c2ea5] — it comes back at its data's version, an\n2026/10/25 09:30:11 restore_unit.go:410: [INFO] [backup] Restoring vikunja from recovery unit /mnt/sys_drive/felhom-data/backups/primary/vikunja: images=1, secrets recovered=1/1, data_keys=0\n2026/10/25 09:30:13 restore_pull.go:96: [ERROR] [backup] Restore REFUSED for vikunja before anything was touched: the image vikunja/vikunja:0.0.0-felhom-d48-missing could not be downloaded (the version does not exist any more; after 1s): exit status 1 — Error response from daemon: manifest for vikunja/vikunja:0.0.0-f\n2026/10/25 09:30:13 handlers.go:1902: [ERROR] [web] Restore failed (async): stack=vikunja: Ennek a mentésnek a 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. Válassz egy másik mentést.\n2026/10/25 09:30:31 handlers.go:1852: [DEBUG] [web] backupRestoreHandler: stack=vikunja snapshot=helyi from 172.18.0.4:37324\n2026/10/25 09:30:31 handlers.go:1894: [WARN] [web] Restore requested (async): stack=vikunja, snapshot=helyi from 172.18.0.4:37324\n2026/10/25 09:30:31 restore_unit.go:410: [INFO] [backup] Restoring vikunja from recovery unit /mnt/sys_drive/felhom-data/backups/primary/vikunja: images=1, secrets recovered=1/1, data_keys=0\n2026/10/25 09:30:33 offbox_restore_undo.go:163: [INFO] [offbox] vikunja: the live volumes are copied before the restore (2 copied, 0 new from the snapshot) in 883ms — /mnt/sys_drive/felhom-data/backups/restore-undo/vikunja/20261025T093032Z (R-893)\n2026/10/25 09:30:33 restore_crash_putback.go:89: [INFO] [backup] vikunja: the restore copy is recorded — a power cut from here on puts the app back on start (R-1195)\n2026/10/25 09:30:33 restore.go:225: [INFO] [backup] Restoring Docker volume vikunja_vikunja_data for vikunja\n2026/10/25 09:30:34 restore.go:263: [DEBUG] [backup] Volume vikunja_vikunja_data restored successfully\n2026/10/25 09:30:34 restore.go:225: [INFO] [backup] Restoring Docker volume vikunja_vikunja_db for vikunja\n2026/10/25 09:30:34 restore.go:263: [DEBUG] [backup] Volume vikunja_vikunja_db restored successfully\n2026/10/25 09:30:34 restore.go:268: [INFO] [backup] Restored 2 Docker volume(s) for vikunja\n2026/10/25 09:30:38 restore.go:303: [DEBUG] [backup] Post-restore health check: vikunja is running\n2026/10/25 09:30:38 restore_unit.go:591: [INFO] [backup] Restore-from-unit completed: vikunja — 2 volume(s) of 2 listed, 0 database(s) of 0 listed\n2026/10/25 09:30:38 handlers.go:1906: [INFO] [web] Restore completed (async): stack=vikunja in 6.850941322s (volumes 2/2, dbs 0/0)\n2026/10/25 09:31:47 handlers.go:1852: [DEBUG] [web] backupRestoreHandler: stack=vikunja snapshot=helyi from 172.18.0.4:37324\n2026/10/25 09:31:47 handlers.go:1894: [WARN] [web] Restore requested (async): stack=vikunja, snapshot=helyi from 172.18.0.4:37324\n2026/10/25 09:31:47 restore_unit.go:398: [INFO] [backup] Restore vikunja: the data belongs to [busybox:1.36.1] (written 2026-10-25T09:31:31Z); the app ran [vikunja/vikunja:2.6.0@sha256:417ada6f94e81f0267aa2f007d0a811fc82d38dd2aa58351e3ea520ca01c2ea5] — it comes back at its data's version, and the update climbs from t\n2026/10/25 09:31:47 restore_unit.go:410: [INFO] [backup] Restoring vikunja from recovery unit /mnt/sys_drive/felhom-data/backups/primary/vikunja: images=1, secrets recovered=1/1, data_keys=0\n2026/10/25 09:31:50 offbox_restore_undo.go:163: [INFO] [offbox] vikunja: the live volumes are copied before the restore (2 copied, 0 new from the snapshot) in 960ms — /mnt/sys_drive/felhom-data/backups/restore-undo/vikunja/20261025T093149Z (R-893)\n2026/10/25 09:31:50 restore_crash_putback.go:89: [INFO] [backup] vikunja: the restore copy is recorded — a power cut from here on puts the app back on start (R-1195)\n2026/10/25 09:31:50 restore.go:225: [INFO] [backup] Restoring Docker volume vikunja_vikunja_data for vikunja\n2026/10/25 09:31:51 restore.go:263: [DEBUG] [backup] Volume vikunja_vikunja_data restored successfully\n2026/10/25 09:31:51 restore.go:225: [INFO] [backup] Restoring Docker volume vikunja_vikunja_db for vikunja\n2026/10/25 09:31:51 restore.go:263: [DEBUG] [backup] Volume vikunja_vikunja_db restored successfully\n2026/10/25 09:31:51 restore.go:268: [INFO] [backup] Restored 2 Docker volume(s) for vikunja\n2026/10/25 09:31:55 restore.go:308: [DEBUG] [backup] Post-restore health check: vikunja not yet running, waiting...\n2026/10/25 Line truncated
"after_remove": {
"containers": "",
"volumes": "",
"appyaml": "",
"appdata": "",
"kept": "",
"subdomain": "404"
},
"test_copy_removed": "ls: cannot access '/mnt/sys_drive/felhom-data/backups/d48-test-original/vikunja': No such file or directory",
"restore_undo_left": "20261025T093149Z"
}
@@ -0,0 +1,27 @@
09:28:46Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:28:56Z [1] deployed, controller state=running, pinned={'vikunja': 'vikunja/vikunja:2.6.0'}
09:28:56Z gate: tasks is gated — passed as the household (cookie set)
09:28:57Z vikunja: register http=200
09:28:57Z vikunja: create project http=201
09:28:57Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:30:08Z [D48] the unit's pin → vikunja/vikunja:0.0.0-felhom-d48-missing :: compose/docker-compose.yml:1 manifest.json:7 data-stamps.json:4
09:30:11Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:30:21Z d48_restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T09:30:11.335278772Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "Visszaállítás sikertelen: Ennek a mentésnek a 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. Válassz egy másik mentést.", "finished_at": "2026-10-
09:30:24Z [D48] containers never restarted: True (/vikunja 2026-10-25T09:29:54.57273795Z running)
09:30:25Z vikunja: readback of the seeded project http=200 ok=True
09:30:31Z the original unit is back byte for byte: True
09:30:31Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:30:41Z normal_restore_1: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T09:30:31.410646319Z", "last": {"op": "restore", "stack": "vikunja", "ok": true, "message": "A(z) vikunja: 2 adatkötet visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-25T09:30:38.26202133Z"}, "last_recent": true}
09:30:41Z vikunja: readback of the seeded project http=200 ok=True
09:30:41Z normal restore: item back True, state running
09:30:41Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:31:47Z [R-1270] the unit's pin → busybox:1.36.1 (pulls, exits at once) :: compose/docker-compose.yml:1 manifest.json:7 data-stamps.json:4
09:31:47Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:33:28Z r1270_restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T09:31:47.895007425Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.", "finished_at": "2026-10-25T09:33:26.51097487Z", "not_started": true}, "last_re
09:33:31Z [R-1270] state restarting, kept undo copy: 20261025T093149Z
09:33:38Z the original unit is back byte for byte: True
09:33:38Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:33:48Z normal_restore_2: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T09:33:38.075945508Z", "last": {"op": "restore", "stack": "vikunja", "ok": true, "message": "A(z) vikunja: 2 adatkötet visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-25T09:33:44.816871537Z"}, "last_recent": true}
09:33:48Z vikunja: readback of the seeded project http=200 ok=True
09:33:48Z normal restore 2: item back True, state running
09:34:24Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'vikunja', 'volumes_removed': ['vikunja_vikunja_data', 'vikunja_vikunja_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt
@@ -0,0 +1,34 @@
{
"app": "adventurelog",
"install": true,
"seed": true,
"backup": {
"code": "200",
"seconds": 70,
"status": "{\"db_dump\": {\"count\": 3, \"duration\": \"1m6.314033847s\", \"last_run\": \"2026-10-25T09:20:04.942724577Z\", \"success\": true}, \"enabled\": true, \"running\": false}"
},
"reinstall": true,
"set_aside_line": "2026/10/25 09:23:03 unit_owner.go:182: [INFO] [backup] adventurelog: the recovery unit of an earlier install (39b3ae7e65d7c197; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/adventurelog/20261025T092303Z; this install (920d6a875fa0dadf) gets its own unit from its first backup (D47)",
"set_aside_dirs": [
"20261025T092303Z"
],
"set_aside_byte_identical": true,
"restore_page_offers": true,
"restore": {
"seconds": 45,
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"adventurelog\", \"started_at\": \"2026-10-25T09:23:47.915496662Z\", \"last\": {\"op\": \"restore\", \"stack\": \"adventurelog\", \"ok\": true, \"message\": \"A(z) adventurelog: 2 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.\", \"finished_at\": \"2026-10-25T09:24:32.727338795Z\"}, \"last_recent\": true}"
},
"state_after_restore": "running",
"item_after_restore": true,
"db_auth_errors_after_restore": "adventurelog: 0\nadventurelog-frontend: 0\nadventurelog-postgres: 0",
"controller_lines": "2026/10/25 09:18:03 recovery_unit.go:297: [INFO] [backup] Recovery unit captured for adventurelog → /mnt/sys_drive/felhom-data/backups/primary/adventurelog (images=3, secrets-referenced=2, data_keys=1, portable-carried=2/2, withheld=0)\n2026/10/25 09:18:22 healthprobe.go:196: [WARN] Health probe adventurelog: API GET :8000/api/ → Get \"http://adventurelog:8000/api/\": dial tcp 172.18.0.8:8000: connect: connection refused\n2026/10/25 09:20:04 recovery_unit.go:297: [INFO] [backup] Recovery unit captured for adventurelog → /mnt/sys_drive/felhom-data/backups/primary/adventurelog (images=3, secrets-referenced=2, data_keys=1, portable-carried=2/2, withheld=0)\n2026/10/25 09:23:03 unit_owner.go:182: [INFO] [backup] adventurelog: the recovery unit of an earlier install (39b3ae7e65d7c197; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/adventurelog/20261025T092303Z; this install (920d6a875fa0dadf) gets its own unit\n2026/10/25 09:23:03 recovery_unit.go:297: [INFO] [backup] Recovery unit captured for adventurelog → /mnt/sys_drive/felhom-data/backups/primary/adventurelog (images=3, secrets-referenced=2, data_keys=1, portable-carried=2/2, withheld=0)\n2026/10/25 09:23:47 kept_copies.go:108: [WARN] [web] Restore of adventurelog from a set-aside unit requested (async): /mnt/sys_drive/felhom-data/backups/set-aside/adventurelog/20261025T092303Z from 172.18.0.4:37324\n2026/10/25 09:23:47 unit_owner.go:316: [INFO] [backup] restore adventurelog from the set-aside unit /mnt/sys_drive/felhom-data/backups/set-aside/adventurelog/20261025T092303Z (an earlier install, D47)\n2026/10/25 09:23:47 restore_dbcreds.go:114: [INFO] [backup] Restore adventurelog: database password(s) [DB_PASSWORD] come from the backup, [] stay as installed (older than the data) — values not logged (R-1106)\n2026/10/25 09:23:47 restore_unit.go:410: [INFO] [backup] Restoring adventurelog from recovery unit /mnt/sys_drive/felhom-data/backups/set-aside/adventurelog/20261025T092303Z: images=3, secrets recovered=2/2, data_keys=1\n2026/10/25 09:23:56 offbox_restore_undo.go:163: [INFO] [offbox] adventurelog: the live volumes are copied before the restore (2 copied, 0 new from the snapshot) in 4.55s — /mnt/sys_drive/felhom-data/backups/restore-undo/adventurelog/20261025T092351Z (R-893)\n2026/10/25 09:23:56 restore_crash_putback.go:89: [INFO] [backup] adventurelog: the restore copy is recorded — a power cut from here on puts the app back on start (R-1195)\n2026/10/25 09:23:56 restore.go:225: [INFO] [backup] Restoring Docker volume adventurelog_adventurelog_media for adventurelog\n2026/10/25 09:23:57 restore.go:263: [DEBUG] [backup] Volume adventurelog_adventurelog_media restored successfully\n2026/10/25 09:23:57 restore.go:225: [INFO] [backup] Restoring Docker volume adventurelog_adventurelog_postgres_data for adventurelog\n2026/10/25 09:24:01 restore.go:263: [DEBUG] [backup] Volume adventurelog_adventurelog_postgres_data restored successfully\n2026/10/25 09:24:01 restore.go:268: [INFO] [backup] Restored 2 Docker volume(s) for adventurelog\n2026/10/25 09:24:01 deploy.go:866: [INFO] [stacks] adventurelog: the restored data belongs to install 39b3ae7e65d7c197 (was 920d6a875fa0dadf) — its backups follow it (D47)\n2026/10/25 09:24:02 restore_db.go:87: [INFO] [backup] Restore adventurelog: replaying DB dump into adventurelog-postgres (postgres)\n2026/10/25 09:24:08 restore_db.go:97: [INFO] [backup] Restore adventurelog: replayed 1 DB dump(s)\n2026/10/25 09:24:12 restore.go:308: [DEBUG] [backup] Post-restore health check: adventurelog not yet running, waiting...\n2026/10/25 09:24:17 restore.go:308: [DEBUG] [backup] Post-restore health check: adventurelog not yet running, waiting...\n2026/10/25 09:24:22 restore.go:308: [DEBUG] [backup] Post-restore health check: adventurelog not yet running, waiting...\n2026/10/25 09:24:27 restore.go:308: [DEBUG] [backup] Post-restore health check: adventurelog not yet running, waiting...\n2026/10/25 09:24:32 restore.go:303: [DEBUG] [backup] Post-restore health check: adventurelog is running\n2026/10/25 09:24:32 restore_unit.go:591: [INFO] [backup] Restore-from-unit completed: adventurelog — 2 volume(s) of 2 listed, 1 database(s) of 1 listed\n",
"after_remove": {
"containers": "",
"volumes": "",
"appyaml": "",
"appdata": "",
"kept": "",
"subdomain": "404"
},
"set_aside_after_remove": "ls: cannot access '/mnt/sys_drive/felhom-data/backups/set-aside/adventurelog': No such file or directory"
}
@@ -0,0 +1,20 @@
Megőrzött másolatok</h3>
<p class="form-hint" style="margin-bottom:.75rem">Ezek a második meghajtón lévő másolatok olyan mappákhoz tartoznak, amelyek most nem érhetők el: a meghajtó nincs csatlakoztatva, már nincs a gépen, vagy a mappa hiányzik. Megőrizzük őket, amíg te el nem távolítod őket.</p>
<div class="app-row-list">
<div class="app-row">
<img class="app-row-icon" src="/static/assets/-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<div class="app-row-text">
<span class="app-row-name">adventurelog</span>
<span class="app-row-secondary">a(z) sys_drive meghajtóról megőrzött másolat · egy korábbi telepítés mentése (újratelepítés előtt készült) · 288,9 MB · 2026. 10. 25. 10:23</span>
</div>
<div class="app-row-actions">
<form method="POST" action="/backup/kept/restore" style="display:inline">
<input type="hidden" name="_csrf" value="<blanked>">
<input type="hidden" name="id" value="a76776b93e5e59df">
<input type="hidden" name="confirm" value="1">
<button type="button" class="btn btn-xs btn-outline"
@@ -0,0 +1,8 @@
c229bf3847b573aa965308952a5da555fa41f3bb96cefa4ab0ae137bd9aeb6e4 ./compose/.felhom.yml
7350a65f5a6dfc8ada83f66d28929e8ac54720d7379babf80634bbe35ebc92e1 ./compose/app.yaml
c8f2e1dec1a7adfe7026d3471bdc20962dee23aac6c1012ee048e54919e07c11 ./compose/docker-compose.yml
47344b7405fa51fc03f008501b43c3c8f6ac0dd941b953764d75aec6573147a3 ./data-stamps.json
d2238cef009ff1c540486ce80ab9c006c05c4767b684d4b78ed24e10e667d59d ./db-dumps/adventurelog-postgres.sql
5cb66a1a41ae248f122196509d36c4556130d80ebd4d20f23c113dc64d0b42a4 ./manifest.json
c3d9eb9dd880faeea2f4a6caf6ffc6ab62994cd0f1c2ca8618d6e6966721e66f ./volume-dumps/adventurelog_adventurelog_media.tar
51eee7ad0aa146554c8ecc495f089d62f018c84b2c4759073c72a85e145ed1e4 ./volume-dumps/adventurelog_adventurelog_postgres_data.tar
@@ -0,0 +1,8 @@
c229bf3847b573aa965308952a5da555fa41f3bb96cefa4ab0ae137bd9aeb6e4 ./compose/.felhom.yml
7350a65f5a6dfc8ada83f66d28929e8ac54720d7379babf80634bbe35ebc92e1 ./compose/app.yaml
c8f2e1dec1a7adfe7026d3471bdc20962dee23aac6c1012ee048e54919e07c11 ./compose/docker-compose.yml
47344b7405fa51fc03f008501b43c3c8f6ac0dd941b953764d75aec6573147a3 ./data-stamps.json
d2238cef009ff1c540486ce80ab9c006c05c4767b684d4b78ed24e10e667d59d ./db-dumps/adventurelog-postgres.sql
5cb66a1a41ae248f122196509d36c4556130d80ebd4d20f23c113dc64d0b42a4 ./manifest.json
c3d9eb9dd880faeea2f4a6caf6ffc6ab62994cd0f1c2ca8618d6e6966721e66f ./volume-dumps/adventurelog_adventurelog_media.tar
51eee7ad0aa146554c8ecc495f089d62f018c84b2c4759073c72a85e145ed1e4 ./volume-dumps/adventurelog_adventurelog_postgres_data.tar
@@ -0,0 +1,4 @@
c229bf3847b573aa965308952a5da555fa41f3bb96cefa4ab0ae137bd9aeb6e4 ./compose/.felhom.yml
61006693c9d91f9200677823a100c47ff0000cadba6085772efc28644447731b ./compose/app.yaml
c8f2e1dec1a7adfe7026d3471bdc20962dee23aac6c1012ee048e54919e07c11 ./compose/docker-compose.yml
6856f0a8df476d64a24975ec53ee56a6f6e05a0a87d165fb97eade8ba597a600 ./manifest.json
@@ -0,0 +1,26 @@
09:15:44Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:18:15Z [1] deployed, controller state=unhealthy, pinned={'adventurelog': 'ghcr.io/seanmorley15/adventurelog-backend:v0.13.0', 'adventurelog-frontend': 'ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0', 'adventurelog-postgres': 'postgis/postgis:16-3.5-alpine'}
09:18:15Z [1] NOTE: the controller's own state is 'unhealthy', not 'running' — recorded, not treated as a failure; the fixture's front-door wait is the real gate
09:18:15Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:20Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:25Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:30Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:35Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:40Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:45Z gate: travel is gated — passed as the household (cookie set)
09:18:53Z adventurelog: createsuperuser :: Superuser created successfully.
09:18:58Z adventurelog: seeded superuser drillf3d0fa
09:18:58Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:20:48Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'adventurelog', 'volumes_removed': ['adventurelog_adventurelog_media', 'adventurelog_adventurelog_postgres_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
09:20:53Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:23:24Z [1] deployed, controller state=unhealthy, pinned={'adventurelog': 'ghcr.io/seanmorley15/adventurelog-backend:v0.13.0', 'adventurelog-frontend': 'ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0', 'adventurelog-postgres': 'postgis/postgis:16-3.5-alpine'}
09:23:24Z [1] NOTE: the controller's own state is 'unhealthy', not 'running' — recorded, not treated as a failure; the fixture's front-door wait is the real gate
09:23:37Z set aside: 2026/10/25 09:23:03 unit_owner.go:182: [INFO] [backup] adventurelog: the recovery unit of an earlier install (39b3ae7e65d7c197; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/adventurelog/20261025T092303Z; this install (920d6a875fa0dadf) gets its own unit f
09:23:44Z the set-aside unit is install A's unit byte for byte: True (8 files)
09:23:47Z [6] POST /backup/kept/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:24:33Z restore status: {"running": false, "op": "restore", "stack": "adventurelog", "started_at": "2026-10-25T09:23:47.915496662Z", "last": {"op": "restore", "stack": "adventurelog", "ok": true, "message": "A(z) adventurelog: 2 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-2
09:25:03Z adventurelog: readback of the seeded account found=True
09:25:06Z state after restore: running, item: True, db auth errors: adventurelog: 0
adventurelog-frontend: 0
adventurelog-postgres: 0
09:25:44Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'adventurelog', 'volumes_removed': ['adventurelog_adventurelog_media', 'adventurelog_adventurelog_postgres_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
@@ -0,0 +1,34 @@
{
"app": "tandoor",
"install": true,
"seed": true,
"backup": {
"code": "200",
"seconds": 65,
"status": "{\"db_dump\": {\"count\": 3, \"duration\": \"1m4.757425445s\", \"last_run\": \"2026-10-25T09:07:24.767762156Z\", \"success\": true}, \"enabled\": true, \"running\": false}"
},
"reinstall": true,
"set_aside_line": "2026/10/25 09:13:03 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (77ea4da495f5cdbf; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T091303Z; this install (bf68aee48f37e20e) gets its own unit from its first backup (D47)",
"set_aside_dirs": [
"20261025T091303Z"
],
"set_aside_byte_identical": true,
"restore_page_offers": true,
"restore": {
"seconds": 45,
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"tandoor\", \"started_at\": \"2026-10-25T09:13:23.43182369Z\", \"last\": {\"op\": \"restore\", \"stack\": \"tandoor\", \"ok\": true, \"message\": \"A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.\", \"finished_at\": \"2026-10-25T09:14:05.476224352Z\"}, \"last_recent\": true}"
},
"state_after_restore": "running",
"item_after_restore": true,
"db_auth_errors_after_restore": "tandoor: 0\ntandoor-postgres: 0",
"controller_lines": "2026/10/25 09:07:24 recovery_unit.go:297: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable-carried=2/2, withheld=0)\n2026/10/25 09:13:03 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (77ea4da495f5cdbf; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T091303Z; this install (bf68aee48f37e20e) gets its own unit from its \n2026/10/25 09:13:03 recovery_unit.go:297: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable-carried=2/2, withheld=0)\n2026/10/25 09:13:23 kept_copies.go:108: [WARN] [web] Restore of tandoor from a set-aside unit requested (async): /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T091303Z from 172.18.0.4:37324\n2026/10/25 09:13:23 unit_owner.go:316: [INFO] [backup] restore tandoor from the set-aside unit /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T091303Z (an earlier install, D47)\n2026/10/25 09:13:23 restore_dbcreds.go:114: [INFO] [backup] Restore tandoor: database password(s) [DB_PASSWORD] come from the backup, [] stay as installed (older than the data) — values not logged (R-1106)\n2026/10/25 09:13:23 restore_unit.go:410: [INFO] [backup] Restoring tandoor from recovery unit /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T091303Z: images=2, secrets recovered=2/2, data_keys=0\n2026/10/25 09:13:31 offbox_restore_undo.go:163: [INFO] [offbox] tandoor: the live volumes are copied before the restore (3 copied, 0 new from the snapshot) in 3.532s — /mnt/sys_drive/felhom-data/backups/restore-undo/tandoor/20261025T091327Z (R-893)\n2026/10/25 09:13:31 restore_crash_putback.go:89: [INFO] [backup] tandoor: the restore copy is recorded — a power cut from here on puts the app back on start (R-1195)\n2026/10/25 09:13:31 restore.go:225: [INFO] [backup] Restoring Docker volume tandoor_tandoor_media for tandoor\n2026/10/25 09:13:31 restore.go:263: [DEBUG] [backup] Volume tandoor_tandoor_media restored successfully\n2026/10/25 09:13:31 restore.go:225: [INFO] [backup] Restoring Docker volume tandoor_tandoor_postgres_data for tandoor\n2026/10/25 09:13:33 restore.go:263: [DEBUG] [backup] Volume tandoor_tandoor_postgres_data restored successfully\n2026/10/25 09:13:33 restore.go:225: [INFO] [backup] Restoring Docker volume tandoor_tandoor_static for tandoor\n2026/10/25 09:13:36 restore.go:263: [DEBUG] [backup] Volume tandoor_tandoor_static restored successfully\n2026/10/25 09:13:36 restore.go:268: [INFO] [backup] Restored 3 Docker volume(s) for tandoor\n2026/10/25 09:13:36 deploy.go:866: [INFO] [stacks] tandoor: the restored data belongs to install 77ea4da495f5cdbf (was bf68aee48f37e20e) — its backups follow it (D47)\n2026/10/25 09:13:36 restore_db.go:87: [INFO] [backup] Restore tandoor: replaying DB dump into tandoor-postgres (postgres)\n2026/10/25 09:13:40 restore_db.go:97: [INFO] [backup] Restore tandoor: replayed 1 DB dump(s)\n2026/10/25 09:13:45 restore.go:308: [DEBUG] [backup] Post-restore health check: tandoor not yet running, waiting...\n2026/10/25 09:13:50 restore.go:308: [DEBUG] [backup] Post-restore health check: tandoor not yet running, waiting...\n2026/10/25 09:13:55 restore.go:308: [DEBUG] [backup] Post-restore health check: tandoor not yet running, waiting...\n2026/10/25 09:14:00 restore.go:308: [DEBUG] [backup] Post-restore health check: tandoor not yet running, waiting...\n2026/10/25 09:14:05 restore.go:303: [DEBUG] [backup] Post-restore health check: tandoor is running\n2026/10/25 09:14:05 restore_unit.go:591: [INFO] [backup] Restore-from-unit completed: tandoor — 3 volume(s) of 3 listed, 1 database(s) of 1 listed\n",
"after_remove": {
"containers": "",
"volumes": "",
"appyaml": "",
"appdata": "",
"kept": "",
"subdomain": "404"
},
"set_aside_after_remove": "ls: cannot access '/mnt/sys_drive/felhom-data/backups/set-aside/tandoor': No such file or directory"
}
@@ -0,0 +1,20 @@
Megőrzött másolatok</h3>
<p class="form-hint" style="margin-bottom:.75rem">Ezek a második meghajtón lévő másolatok olyan mappákhoz tartoznak, amelyek most nem érhetők el: a meghajtó nincs csatlakoztatva, már nincs a gépen, vagy a mappa hiányzik. Megőrizzük őket, amíg te el nem távolítod őket.</p>
<div class="app-row-list">
<div class="app-row">
<img class="app-row-icon" src="/static/assets/-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<div class="app-row-text">
<span class="app-row-name">tandoor</span>
<span class="app-row-secondary">a(z) sys_drive meghajtóról megőrzött másolat · egy korábbi telepítés mentése (újratelepítés előtt készült) · 144,3 MB · 2026. 10. 25. 10:13</span>
</div>
<div class="app-row-actions">
<form method="POST" action="/backup/kept/restore" style="display:inline">
<input type="hidden" name="_csrf" value="<blanked>">
<input type="hidden" name="id" value="88ecdaffa2c9c0b0">
<input type="hidden" name="confirm" value="1">
<button type="button" class="btn btn-xs btn-outline"
@@ -0,0 +1,9 @@
761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6 ./compose/.felhom.yml
2701229e98a7b0d269bd971e54d11015970af1df9c38cf90435d77e61c53a153 ./compose/app.yaml
851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25 ./compose/docker-compose.yml
ffc60018dd63fcc1aee0fc0348c601dc2b94536f44335f1a0df4313a93ed2952 ./data-stamps.json
e8a1bd7a175d4c1d021d4e5bdba2d0f9da03ad4c80d358380bc6dbfb8e71ae8c ./db-dumps/tandoor-postgres.sql
e338ce10ddf13a7356dfe37adb520bb4c737588bb8c75149118b80d686f25830 ./manifest.json
c03824f1964f4660e6c9ceb6eda1803f4a301bd96b197cf0f49909df4d52a029 ./volume-dumps/tandoor_tandoor_media.tar
c8a062715a30be5b334a2968c9337d30a6121c745c4602bf301d23694d2979ae ./volume-dumps/tandoor_tandoor_postgres_data.tar
0d110353b2234c6a7a4aa0179e101c5513a1a0dde38a914875948970e75daaaf ./volume-dumps/tandoor_tandoor_static.tar
@@ -0,0 +1,9 @@
761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6 ./compose/.felhom.yml
2701229e98a7b0d269bd971e54d11015970af1df9c38cf90435d77e61c53a153 ./compose/app.yaml
851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25 ./compose/docker-compose.yml
ffc60018dd63fcc1aee0fc0348c601dc2b94536f44335f1a0df4313a93ed2952 ./data-stamps.json
e8a1bd7a175d4c1d021d4e5bdba2d0f9da03ad4c80d358380bc6dbfb8e71ae8c ./db-dumps/tandoor-postgres.sql
e338ce10ddf13a7356dfe37adb520bb4c737588bb8c75149118b80d686f25830 ./manifest.json
c03824f1964f4660e6c9ceb6eda1803f4a301bd96b197cf0f49909df4d52a029 ./volume-dumps/tandoor_tandoor_media.tar
c8a062715a30be5b334a2968c9337d30a6121c745c4602bf301d23694d2979ae ./volume-dumps/tandoor_tandoor_postgres_data.tar
0d110353b2234c6a7a4aa0179e101c5513a1a0dde38a914875948970e75daaaf ./volume-dumps/tandoor_tandoor_static.tar
@@ -0,0 +1,4 @@
761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6 ./compose/.felhom.yml
d38eec97a49c2b5b3f5041ef8b1436836136f220fc4d4c72700f17e3bb834d11 ./compose/app.yaml
851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25 ./compose/docker-compose.yml
57eb0d21fffe2116723d34cc742ae595e462fd956d061c9f2fa774cc66c903d1 ./manifest.json
@@ -0,0 +1,17 @@
09:03:52Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:06:02Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
09:06:02Z gate: recipes is gated — passed as the household (cookie set)
09:06:15Z tandoor: createsuperuser :: Running django-vite in production mode (no HMR) Superuser created successfully.
09:06:19Z tandoor: seeded superuser drill69d6f1
09:06:20Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:08:02Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
09:08:07Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:10:33Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
09:13:13Z set aside: 2026/10/25 09:13:03 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (77ea4da495f5cdbf; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T091303Z; this install (bf68aee48f37e20e) gets its own unit from its fi
09:13:20Z the set-aside unit is install A's unit byte for byte: True (9 files)
09:13:23Z [6] POST /backup/kept/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:14:08Z restore status: {"running": false, "op": "restore", "stack": "tandoor", "started_at": "2026-10-25T09:13:23.43182369Z", "last": {"op": "restore", "stack": "tandoor", "ok": true, "message": "A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-25T09:14:05.47622
09:14:38Z tandoor: readback of the seeded account found=True
09:14:41Z state after restore: running, item: True, db auth errors: tandoor: 0
tandoor-postgres: 0
09:15:19Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
@@ -0,0 +1,26 @@
09:15:44Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:18:15Z [1] deployed, controller state=unhealthy, pinned={'adventurelog': 'ghcr.io/seanmorley15/adventurelog-backend:v0.13.0', 'adventurelog-frontend': 'ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0', 'adventurelog-postgres': 'postgis/postgis:16-3.5-alpine'}
09:18:15Z [1] NOTE: the controller's own state is 'unhealthy', not 'running' — recorded, not treated as a failure; the fixture's front-door wait is the real gate
09:18:15Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:20Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:25Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:30Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:35Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:40Z gate: travel not routed right now (404) — not cached, asked again next call
09:18:45Z gate: travel is gated — passed as the household (cookie set)
09:18:53Z adventurelog: createsuperuser :: Superuser created successfully.
09:18:58Z adventurelog: seeded superuser drillf3d0fa
09:18:58Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:20:48Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'adventurelog', 'volumes_removed': ['adventurelog_adventurelog_media', 'adventurelog_adventurelog_postgres_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
09:20:53Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:23:24Z [1] deployed, controller state=unhealthy, pinned={'adventurelog': 'ghcr.io/seanmorley15/adventurelog-backend:v0.13.0', 'adventurelog-frontend': 'ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0', 'adventurelog-postgres': 'postgis/postgis:16-3.5-alpine'}
09:23:24Z [1] NOTE: the controller's own state is 'unhealthy', not 'running' — recorded, not treated as a failure; the fixture's front-door wait is the real gate
09:23:37Z set aside: 2026/10/25 09:23:03 unit_owner.go:182: [INFO] [backup] adventurelog: the recovery unit of an earlier install (39b3ae7e65d7c197; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/adventurelog/20261025T092303Z; this install (920d6a875fa0dadf) gets its own unit f
09:23:44Z the set-aside unit is install A's unit byte for byte: True (8 files)
09:23:47Z [6] POST /backup/kept/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:24:33Z restore status: {"running": false, "op": "restore", "stack": "adventurelog", "started_at": "2026-10-25T09:23:47.915496662Z", "last": {"op": "restore", "stack": "adventurelog", "ok": true, "message": "A(z) adventurelog: 2 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-2
09:25:03Z adventurelog: readback of the seeded account found=True
09:25:06Z state after restore: running, item: True, db auth errors: adventurelog: 0
adventurelog-frontend: 0
adventurelog-postgres: 0
09:25:44Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'adventurelog', 'volumes_removed': ['adventurelog_adventurelog_media', 'adventurelog_adventurelog_postgres_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
@@ -0,0 +1,21 @@
09:37:11Z [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
09:37:12Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:38:47Z [1] deployed, controller state=running, pinned={'romm': 'rommapp/romm:5.3.1', 'romm-db': 'mariadb:11.8', 'romm-redis': 'redis:7-alpine'}
09:38:47Z gate: arcade is gated — passed as the household (cookie set)
09:38:55Z romm: POST /api/users http=201
09:38:55Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:40:23Z forged a future-dated second-drive copy: "at": "2026-10-28T06:39:39Z"
"at": "2026-10-28T06:39:39Z" (own unit: /mnt/felhom-drives/scratch_hdd/backups/primary/romm)
09:41:09Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'romm', 'volumes_removed': ['romm_romm_assets', 'romm_romm_config', 'romm_romm_db_data', 'romm_romm_redis_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': ['/mn
09:41:14Z [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
09:41:14Z [question, own copy + future copy] 409 {"code": "kept_data_choice", "use_offered": true, "use_desc": "Az adatbázist innen töltjük vissza: saját mentés, 2026-10-25 10:38. A régi fájlokkal indítjuk az alkalmazást. Ami ezután változott, hiányozhat.", "use_future": null, "use_warn": null, "use_off": ""}
09:41:17Z [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
09:41:17Z [question, the future copy alone] 409 {"code": "kept_data_choice", "use_offered": true, "use_desc": "Az adatbázist innen töltjük vissza: második meghajtó, 2026-10-28 07:39. A régi fájlokkal indítjuk az alkalmazást. Ami ezután változott, hiányozhat.", "use_future": true, "use_warn": "Figyelem: ennek a mentésnek a dátuma (2026-10-28 07:39) a jövőben van. A mentést készítő doboz órája rosszul járhatott. Csak akkor válaszd, ha biztosan ez a ti mentésetek.", "use_off": ""}
09:41:21Z [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
09:41:21Z [1] deploy -> 202 {'ok': True, 'message': 'A(z) RomM megőrzött adatainak betöltése elindult.'}
09:42:26Z [1] deployed, controller state=degraded, pinned={'romm': 'rommapp/romm:5.3.1', 'romm-db': 'mariadb:11.8', 'romm-redis': 'redis:7-alpine'}
09:42:26Z [1] NOTE: the controller's own state is 'degraded', not 'running' — recorded, not treated as a failure; the fixture's front-door wait is the real gate
09:43:06Z load: {"running": false, "op": "restore", "stack": "romm", "started_at": "2026-10-25T09:41:21.356199563Z", "last": {"op": "restore", "stack": "romm", "ok": true, "message": "A(z) RomM a megőrzött adataival fut.", "finished_at": "2026-10-25T09:43:03.140979706Z"}, "last_recent": true}
09:43:15Z romm: login as the seeded user http=200 ok=True
09:43:15Z item after use-my-kept-data: True
09:43:57Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'romm', 'volumes_removed': ['romm_romm_assets', 'romm_romm_config', 'romm_romm_db_data', 'romm_romm_redis_data'], 'hdd_paths_removed': ['/mnt/felhom-drives/scratch_hdd
@@ -0,0 +1,27 @@
{
"app": "tandoor",
"install": true,
"seed": true,
"backup": {
"code": "200",
"seconds": 65,
"status": "{\"db_dump\": {\"count\": 3, \"duration\": \"1m2.524018956s\", \"last_run\": \"2026-10-25T08:53:09.393020557Z\", \"success\": true}, \"enabled\": true, \"running\": false}"
},
"reinstall": true,
"set_aside_line": "2026/10/25 08:57:40 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (8ea077d3afae87ba; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T085740Z; this install (1988e29c0b9f3611) gets its own unit from its first backup (D47)",
"set_aside_dirs": [
"20261025T085740Z"
],
"set_aside_byte_identical": true,
"restore_page_offers": false,
"controller_lines": "2026/10/25 08:52:40 recovery_unit.go:297: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable-carried=2/2, withheld=0)\n2026/10/25 08:53:09 recovery_unit.go:297: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable-carried=2/2, withheld=0)\n2026/10/25 08:57:40 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (8ea077d3afae87ba; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T085740Z; this install (1988e29c0b9f3611) gets its own unit from its \n2026/10/25 08:57:40 recovery_unit.go:297: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable-carried=2/2, withheld=0)\n",
"after_remove": {
"containers": "",
"volumes": "",
"appyaml": "",
"appdata": "",
"kept": "",
"subdomain": "404"
},
"set_aside_after_remove": "ls: cannot access '/mnt/sys_drive/felhom-data/backups/set-aside/tandoor': No such file or directory"
}
@@ -0,0 +1,9 @@
761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6 ./compose/.felhom.yml
0959651afb912a643091755a23f08dc6a2910e550afc1c6e2eb81d868f498af5 ./compose/app.yaml
851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25 ./compose/docker-compose.yml
02fd085023c374b9e06ac2f5db44ade05c315a779a5d403c61e8ed419053644e ./data-stamps.json
d58224a5607756e43ae669394c1257df369fc8137476d2962100a44dc531ce0d ./db-dumps/tandoor-postgres.sql
e5c5f9c4d7f236c6be033af3971fd228d485cc90e6218f69245063768b9380c5 ./manifest.json
88bbb249fd079e05bbf8154c43bf5df99112b8517e9df76394a6b3d519b22045 ./volume-dumps/tandoor_tandoor_media.tar
478ad409250e6a4233e6e50ae268af26a13dc25029670f8a4d20ded57a0c25f2 ./volume-dumps/tandoor_tandoor_postgres_data.tar
2235a32d5bda20a8a3ef2940c049aea79b009759223ad1ac6efa47ed72d7db4d ./volume-dumps/tandoor_tandoor_static.tar
@@ -0,0 +1,9 @@
761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6 ./compose/.felhom.yml
0959651afb912a643091755a23f08dc6a2910e550afc1c6e2eb81d868f498af5 ./compose/app.yaml
851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25 ./compose/docker-compose.yml
02fd085023c374b9e06ac2f5db44ade05c315a779a5d403c61e8ed419053644e ./data-stamps.json
d58224a5607756e43ae669394c1257df369fc8137476d2962100a44dc531ce0d ./db-dumps/tandoor-postgres.sql
e5c5f9c4d7f236c6be033af3971fd228d485cc90e6218f69245063768b9380c5 ./manifest.json
88bbb249fd079e05bbf8154c43bf5df99112b8517e9df76394a6b3d519b22045 ./volume-dumps/tandoor_tandoor_media.tar
478ad409250e6a4233e6e50ae268af26a13dc25029670f8a4d20ded57a0c25f2 ./volume-dumps/tandoor_tandoor_postgres_data.tar
2235a32d5bda20a8a3ef2940c049aea79b009759223ad1ac6efa47ed72d7db4d ./volume-dumps/tandoor_tandoor_static.tar
@@ -0,0 +1,4 @@
761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6 ./compose/.felhom.yml
ad9754c68958c76847c891b0b106d276ddc532788642c6c9cf861ca3e05fb91d ./compose/app.yaml
851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25 ./compose/docker-compose.yml
34699a9f0c9c642f80523e8bbee7127d699373590cfa977b626dfb110f9dbf4d ./manifest.json
@@ -0,0 +1,13 @@
08:49:25Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
08:51:50Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
08:51:50Z gate: recipes is gated — passed as the household (cookie set)
08:52:02Z tandoor: createsuperuser :: Running django-vite in production mode (no HMR) Superuser created successfully.
08:52:06Z tandoor: seeded superuser drilla8c353
08:52:06Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
08:53:49Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
08:53:54Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
08:56:10Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
08:57:42Z set aside: 2026/10/25 08:57:40 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (8ea077d3afae87ba; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T085740Z; this install (1988e29c0b9f3611) gets its own unit from its fi
08:57:49Z the set-aside unit is install A's unit byte for byte: True (9 files)
08:57:52Z the restore page offers NO restore of the set-aside unit
08:58:30Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
@@ -0,0 +1,13 @@
08:49:25Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
08:51:50Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
08:51:50Z gate: recipes is gated — passed as the household (cookie set)
08:52:02Z tandoor: createsuperuser :: Running django-vite in production mode (no HMR) Superuser created successfully.
08:52:06Z tandoor: seeded superuser drilla8c353
08:52:06Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
08:53:49Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
08:53:54Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
08:56:10Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
08:57:42Z set aside: 2026/10/25 08:57:40 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (8ea077d3afae87ba; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T085740Z; this install (1988e29c0b9f3611) gets its own unit from its fi
08:57:49Z the set-aside unit is install A's unit byte for byte: True (9 files)
08:57:52Z the restore page offers NO restore of the set-aside unit
08:58:30Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
@@ -0,0 +1,22 @@
{
"app": "vikunja",
"install": true,
"seed": true,
"backup": {
"code": "200",
"seconds": 60,
"status": "{\"db_dump\": {\"count\": 2, \"duration\": \"55.138182754s\", \"last_run\": \"2026-10-25T09:27:17.793830395Z\", \"success\": true}, \"enabled\": true, \"running\": false}"
},
"unit_files": 7,
"controller_lines": "",
"after_remove": {
"containers": "",
"volumes": "",
"appyaml": "",
"appdata": "",
"kept": "",
"subdomain": "404"
},
"test_copy_removed": "ls: cannot access '/mnt/sys_drive/felhom-data/backups/d48-test-original/vikunja': No such file or directory",
"restore_undo_left": "ls: cannot access '/mnt/sys_drive/felhom-data/backups/restore-undo/vikunja': No such file or directory"
}
@@ -0,0 +1,7 @@
09:26:12Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:26:22Z [1] deployed, controller state=running, pinned={'vikunja': 'vikunja/vikunja:2.6.0'}
09:26:22Z gate: tasks is gated — passed as the household (cookie set)
09:26:22Z vikunja: register http=200
09:26:22Z vikunja: create project http=201
09:26:22Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:28:09Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'vikunja', 'volumes_removed': ['vikunja_vikunja_data', 'vikunja_vikunja_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt
@@ -0,0 +1,15 @@
09:26:12Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:26:22Z [1] deployed, controller state=running, pinned={'vikunja': 'vikunja/vikunja:2.6.0'}
09:26:22Z gate: tasks is gated — passed as the household (cookie set)
09:26:22Z vikunja: register http=200
09:26:22Z vikunja: create project http=201
09:26:22Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:28:09Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'vikunja', 'volumes_removed': ['vikunja_vikunja_data', 'vikunja_vikunja_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt
Traceback (most recent call last):
File "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/restore-truth-2026-10-25/tools/pullprobe.py", line 121, in <module>
main(sys.argv[1], sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else "")
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/restore-truth-2026-10-25/tools/pullprobe.py", line 83, in main
say(" [D48] the unit's pin → " + repo + ":" + MISSING_TAG + " :: " + edit(f"{repo}:{MISSING_TAG}").replace("\n", " "))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'replace'
@@ -0,0 +1,17 @@
09:03:52Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:06:02Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
09:06:02Z gate: recipes is gated — passed as the household (cookie set)
09:06:15Z tandoor: createsuperuser :: Running django-vite in production mode (no HMR) Superuser created successfully.
09:06:19Z tandoor: seeded superuser drill69d6f1
09:06:20Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:08:02Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
09:08:07Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:10:33Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
09:13:13Z set aside: 2026/10/25 09:13:03 unit_owner.go:182: [INFO] [backup] tandoor: the recovery unit of an earlier install (77ea4da495f5cdbf; the install ids differ) is SET ASIDE whole → /mnt/sys_drive/felhom-data/backups/set-aside/tandoor/20261025T091303Z; this install (bf68aee48f37e20e) gets its own unit from its fi
09:13:20Z the set-aside unit is install A's unit byte for byte: True (9 files)
09:13:23Z [6] POST /backup/kept/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:14:08Z restore status: {"running": false, "op": "restore", "stack": "tandoor", "started_at": "2026-10-25T09:13:23.43182369Z", "last": {"op": "restore", "stack": "tandoor", "ok": true, "message": "A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-25T09:14:05.47622
09:14:38Z tandoor: readback of the seeded account found=True
09:14:41Z state after restore: running, item: True, db auth errors: tandoor: 0
tandoor-postgres: 0
09:15:19Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
@@ -0,0 +1,27 @@
09:28:46Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:28:56Z [1] deployed, controller state=running, pinned={'vikunja': 'vikunja/vikunja:2.6.0'}
09:28:56Z gate: tasks is gated — passed as the household (cookie set)
09:28:57Z vikunja: register http=200
09:28:57Z vikunja: create project http=201
09:28:57Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:30:08Z [D48] the unit's pin → vikunja/vikunja:0.0.0-felhom-d48-missing :: compose/docker-compose.yml:1 manifest.json:7 data-stamps.json:4
09:30:11Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:30:21Z d48_restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T09:30:11.335278772Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "Visszaállítás sikertelen: Ennek a mentésnek a 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. Válassz egy másik mentést.", "finished_at": "2026-10-
09:30:24Z [D48] containers never restarted: True (/vikunja 2026-10-25T09:29:54.57273795Z running)
09:30:25Z vikunja: readback of the seeded project http=200 ok=True
09:30:31Z the original unit is back byte for byte: True
09:30:31Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:30:41Z normal_restore_1: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T09:30:31.410646319Z", "last": {"op": "restore", "stack": "vikunja", "ok": true, "message": "A(z) vikunja: 2 adatkötet visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-25T09:30:38.26202133Z"}, "last_recent": true}
09:30:41Z vikunja: readback of the seeded project http=200 ok=True
09:30:41Z normal restore: item back True, state running
09:30:41Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
09:31:47Z [R-1270] the unit's pin → busybox:1.36.1 (pulls, exits at once) :: compose/docker-compose.yml:1 manifest.json:7 data-stamps.json:4
09:31:47Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:33:28Z r1270_restore: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T09:31:47.895007425Z", "last": {"op": "restore", "stack": "vikunja", "ok": false, "message": "A(z) vikunja: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk.", "finished_at": "2026-10-25T09:33:26.51097487Z", "not_started": true}, "last_re
09:33:31Z [R-1270] state restarting, kept undo copy: 20261025T093149Z
09:33:38Z the original unit is back byte for byte: True
09:33:38Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
09:33:48Z normal_restore_2: {"running": false, "op": "restore", "stack": "vikunja", "started_at": "2026-10-25T09:33:38.075945508Z", "last": {"op": "restore", "stack": "vikunja", "ok": true, "message": "A(z) vikunja: 2 adatkötet visszaállítva — az alkalmazás újraindult.", "finished_at": "2026-10-25T09:33:44.816871537Z"}, "last_recent": true}
09:33:48Z vikunja: readback of the seeded project http=200 ok=True
09:33:48Z normal restore 2: item back True, state running
09:34:24Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'vikunja', 'volumes_removed': ['vikunja_vikunja_data', 'vikunja_vikunja_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt
@@ -0,0 +1,33 @@
{
"app": "tandoor",
"install": true,
"seed": true,
"backup": {
"code": "200",
"seconds": 65,
"status": "{\"db_dump\": {\"count\": 3, \"duration\": \"1m2.207105181s\", \"last_run\": \"2026-10-25T07:53:46.828178317Z\", \"success\": true}, \"enabled\": true, \"running\": false}"
},
"reinstall": true,
"capture_after_reinstall": "2026/10/25 08:00:36 recovery_unit.go:289: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable-carried=2/2, withheld=0)",
"restore": {
"points": 1,
"location": [
"location: /backups/restore?flash=flash.restore.started"
],
"seconds": 116,
"status": "{\"running\": false, \"op\": \"restore\", \"stack\": \"tandoor\", \"started_at\": \"2026-10-25T08:00:41.705408544Z\", \"last\": {\"op\": \"restore\", \"stack\": \"tandoor\", \"ok\": true, \"message\": \"A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.\", \"finished_at\": \"2026-10-25T08:02:35.841483455Z\"}, \"last_recent\": true}",
"state": "starting"
},
"state_after_restore": "starting",
"item_after_restore": false,
"db_auth_errors_after_restore": "tandoor: 44\ntandoor-postgres: 23",
"controller_restore_lines": "2026/10/25 07:55:47 manager.go:1815: [INFO] [stacks] tandoor ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72 running Up 3 seconds (health: starting)\n2026/10/25 07:55:47 manager.go:1815: [INFO] [stacks] tandoor-postgres postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24 running Up 9 seconds (healthy)\n2026/10/25 07:56:56 healthprobe.go:187: [DEBUG] Health probe tandoor: HTTP GET :80/accounts/login/ → 400 (189ms)\n2026/10/25 07:57:06 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 10s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:57:16 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 20s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:57:26 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 30s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:57:36 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 40s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:57:46 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 50s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:57:56 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 1m0s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:58:06 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 1m10s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:58:16 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 1m20s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:58:26 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 1m30s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:58:36 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 1m40s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:58:46 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 1m50s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:58:56 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 2m0s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:59:06 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 2m10s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:59:16 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 2m20s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:59:26 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 2m30s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:59:36 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 2m40s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:59:46 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 2m50s ago, effective interval 5m0s, healthy=true\n2026/10/25 07:59:56 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 3m0s ago, effective interval 5m0s, healthy=true\n2026/10/25 08:00:06 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 3m10s ago, effective interval 5m0s, healthy=true\n2026/10/25 08:00:16 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 3m20s ago, effective interval 5m0s, healthy=true\n2026/10/25 08:00:26 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 3m30s ago, effective interval 5m0s, healthy=true\n2026/10/25 08:00:36 healthprobe.go:64: [DEBUG] [stacks] RunHealthProbes: skipping tandoor — last check 3m40s ago, effective interval 5m0s, healthy=true\n2026/10/25 08:00:36 recovery_unit.go:289: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable-carried=2/2, withheld=0)\n2026/10/25 08:00:41 handlers.go:1846: [DEBUG] [web] backupRestoreHandler: stack=tandoor snapshot=helyi from 172.18.0.4:33912\n2026/10/25 08:00:41 handlers.go:1888: [WARN] [web] Restore requested (async): stack=tandoor, snapshot=helyi from 172.18.0.4:33912\n2026/10/25 08:00:41 restore_dbcreds.go:114: [INFO] [backup] Restore tandoor: database password(s) [DB_PASSWORD] come from the backup, [] stay as installed (older than the data) — values not logged (R-1106)\n2026/10/25 08:00:41 restore_unit.go:410: [INFO] [backup] Restoring tandoor from recovery unit /mnt/sys_drive/felhom-data/backups/primary/tandoor: images=2, secrets recovered=2/2, data_keys=0\n2026/10/25 08:00:50 offbox_restore_undo.go:163: [INFO] [offbox] tandoor: the live volumes are copied before the restore Line truncated
"after_remove": {
"containers": "",
"volumes": "",
"appyaml": "",
"appdata": "",
"kept": "",
"subdomain": "404"
}
}
@@ -0,0 +1,126 @@
.:
total 32
drwxr-xr-x 5 root root 4096 2026-10-25T07:53:46 .
drwxr-xr-x 4 root root 4096 2026-10-25T07:52:44 ..
drwxr-xr-x 2 root root 4096 2026-10-25T07:53:46 compose
-rw-r--r-- 1 root root 2504 2026-10-25T07:53:40 data-stamps.json
drwxr-xr-x 2 root root 4096 2026-10-25T07:52:45 db-dumps
-rw-r--r-- 1 root root 4718 2026-10-25T07:53:46 manifest.json
drwxr-xr-x 2 root root 4096 2026-10-25T07:53:40 volume-dumps
db-dumps:
total 400
drwxr-xr-x 2 root root 4096 2026-10-25T07:52:45 .
drwxr-xr-x 5 root root 4096 2026-10-25T07:53:46 ..
-rw-r--r-- 1 root root 397902 2026-10-25T07:52:45 tandoor-postgres.sql
volume-dumps:
total 147340
drwxr-xr-x 2 root root 4096 2026-10-25T07:53:40 .
drwxr-xr-x 5 root root 4096 2026-10-25T07:53:46 ..
-rw-r--r-- 1 root root 1536 2026-10-25T07:53:36 tandoor_tandoor_media.tar
-rw-r--r-- 1 root root 56538624 2026-10-25T07:53:37 tandoor_tandoor_postgres_data.tar
-rw-r--r-- 1 root root 94322176 2026-10-25T07:53:39 tandoor_tandoor_static.tar
== manifest.json
{
"schema_version": 2,
"app_name": "tandoor",
"display_name": "Tandoor Recipes",
"controller_version": "0.342.0",
"created_at": "2026-10-25T07:53:46Z",
"drive": "/mnt/sys_drive",
"namespace_root": "/mnt/sys_drive/felhom-data",
"image_pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"secret_env_vars": [
"SECRET_KEY",
"DB_PASSWORD"
],
"data_key_env_vars": null,
"secret_source": "portable secrets (data keys, DB passwords, internal signing secrets) are IN this unit's compose/app.yaml (0600); internet-reachable admin logins are NOT, and come from the guest's app.yaml or are regenerated on restore",
"config_files": [
"docker-compose.yml",
".felhom.yml",
"app.yaml"
],
"db_dumps": [
"tandoor-postgres.sql"
],
"volume_dumps": [
"tandoor_tandoor_media.tar",
"tandoor_tandoor_postgres_data.tar",
"tandoor_tandoor_static.tar"
],
"checksums": {
".felhom.yml": "761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6",
"app.yaml": "cbfcb538053217d4a9ca395383648fb47b9449ffd23e818840ab277956098e96",
"docker-compose.yml": "851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25"
},
"portable_secret_env_vars": [
"SECRET_KEY",
"DB_PASSWORD"
],
"data": {
"at": "2026-10-25T07:52:45Z",
"image_pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
},
"files": {
"db-dumps/tandoor-postgres.sql": {
"at": "2026-10-25T07:52:45.200747249Z",
"size": 397902,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
},
"volume-dumps/tandoor_tandoor_media.tar": {
"at": "2026-10-25T07:53:36.966857429Z",
"size": 1536,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
},
"volume-dumps/tandoor_tandoor_postgres_data.tar": {
"at": "2026-10-25T07:53:37.559407527Z",
"size": 56538624,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
},
"volume-dumps/tandoor_tandoor_static.tar": {
"at": "2026-10-25T07:53:39.14242749Z",
"size": 94322176,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
}
}
}
}
@@ -0,0 +1,126 @@
.:
total 32
drwxr-xr-x 5 root root 4096 2026-10-25T07:53:46 .
drwxr-xr-x 4 root root 4096 2026-10-25T07:52:44 ..
drwxr-xr-x 2 root root 4096 2026-10-25T07:53:46 compose
-rw-r--r-- 1 root root 2504 2026-10-25T07:53:40 data-stamps.json
drwxr-xr-x 2 root root 4096 2026-10-25T07:52:45 db-dumps
-rw-r--r-- 1 root root 4718 2026-10-25T07:53:46 manifest.json
drwxr-xr-x 2 root root 4096 2026-10-25T07:53:40 volume-dumps
db-dumps:
total 400
drwxr-xr-x 2 root root 4096 2026-10-25T07:52:45 .
drwxr-xr-x 5 root root 4096 2026-10-25T07:53:46 ..
-rw-r--r-- 1 root root 397902 2026-10-25T07:52:45 tandoor-postgres.sql
volume-dumps:
total 147340
drwxr-xr-x 2 root root 4096 2026-10-25T07:53:40 .
drwxr-xr-x 5 root root 4096 2026-10-25T07:53:46 ..
-rw-r--r-- 1 root root 1536 2026-10-25T07:53:36 tandoor_tandoor_media.tar
-rw-r--r-- 1 root root 56538624 2026-10-25T07:53:37 tandoor_tandoor_postgres_data.tar
-rw-r--r-- 1 root root 94322176 2026-10-25T07:53:39 tandoor_tandoor_static.tar
== manifest.json
{
"schema_version": 2,
"app_name": "tandoor",
"display_name": "Tandoor Recipes",
"controller_version": "0.342.0",
"created_at": "2026-10-25T07:53:46Z",
"drive": "/mnt/sys_drive",
"namespace_root": "/mnt/sys_drive/felhom-data",
"image_pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"secret_env_vars": [
"SECRET_KEY",
"DB_PASSWORD"
],
"data_key_env_vars": null,
"secret_source": "portable secrets (data keys, DB passwords, internal signing secrets) are IN this unit's compose/app.yaml (0600); internet-reachable admin logins are NOT, and come from the guest's app.yaml or are regenerated on restore",
"config_files": [
"docker-compose.yml",
".felhom.yml",
"app.yaml"
],
"db_dumps": [
"tandoor-postgres.sql"
],
"volume_dumps": [
"tandoor_tandoor_media.tar",
"tandoor_tandoor_postgres_data.tar",
"tandoor_tandoor_static.tar"
],
"checksums": {
".felhom.yml": "761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6",
"app.yaml": "cbfcb538053217d4a9ca395383648fb47b9449ffd23e818840ab277956098e96",
"docker-compose.yml": "851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25"
},
"portable_secret_env_vars": [
"SECRET_KEY",
"DB_PASSWORD"
],
"data": {
"at": "2026-10-25T07:52:45Z",
"image_pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
},
"files": {
"db-dumps/tandoor-postgres.sql": {
"at": "2026-10-25T07:52:45.200747249Z",
"size": 397902,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
},
"volume-dumps/tandoor_tandoor_media.tar": {
"at": "2026-10-25T07:53:36.966857429Z",
"size": 1536,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
},
"volume-dumps/tandoor_tandoor_postgres_data.tar": {
"at": "2026-10-25T07:53:37.559407527Z",
"size": 56538624,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
},
"volume-dumps/tandoor_tandoor_static.tar": {
"at": "2026-10-25T07:53:39.14242749Z",
"size": 94322176,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
}
}
}
}
@@ -0,0 +1,126 @@
.:
total 32
drwxr-xr-x 5 root root 4096 2026-10-25T08:00:36 .
drwxr-xr-x 4 root root 4096 2026-10-25T07:52:44 ..
drwxr-xr-x 2 root root 4096 2026-10-25T08:00:36 compose
-rw-r--r-- 1 root root 2504 2026-10-25T07:53:40 data-stamps.json
drwxr-xr-x 2 root root 4096 2026-10-25T07:52:45 db-dumps
-rw-r--r-- 1 root root 4718 2026-10-25T08:00:36 manifest.json
drwxr-xr-x 2 root root 4096 2026-10-25T07:53:40 volume-dumps
db-dumps:
total 400
drwxr-xr-x 2 root root 4096 2026-10-25T07:52:45 .
drwxr-xr-x 5 root root 4096 2026-10-25T08:00:36 ..
-rw-r--r-- 1 root root 397902 2026-10-25T07:52:45 tandoor-postgres.sql
volume-dumps:
total 147340
drwxr-xr-x 2 root root 4096 2026-10-25T07:53:40 .
drwxr-xr-x 5 root root 4096 2026-10-25T08:00:36 ..
-rw-r--r-- 1 root root 1536 2026-10-25T07:53:36 tandoor_tandoor_media.tar
-rw-r--r-- 1 root root 56538624 2026-10-25T07:53:37 tandoor_tandoor_postgres_data.tar
-rw-r--r-- 1 root root 94322176 2026-10-25T07:53:39 tandoor_tandoor_static.tar
== manifest.json
{
"schema_version": 2,
"app_name": "tandoor",
"display_name": "Tandoor Recipes",
"controller_version": "0.342.0",
"created_at": "2026-10-25T08:00:36Z",
"drive": "/mnt/sys_drive",
"namespace_root": "/mnt/sys_drive/felhom-data",
"image_pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"secret_env_vars": [
"SECRET_KEY",
"DB_PASSWORD"
],
"data_key_env_vars": null,
"secret_source": "portable secrets (data keys, DB passwords, internal signing secrets) are IN this unit's compose/app.yaml (0600); internet-reachable admin logins are NOT, and come from the guest's app.yaml or are regenerated on restore",
"config_files": [
"docker-compose.yml",
".felhom.yml",
"app.yaml"
],
"db_dumps": [
"tandoor-postgres.sql"
],
"volume_dumps": [
"tandoor_tandoor_media.tar",
"tandoor_tandoor_postgres_data.tar",
"tandoor_tandoor_static.tar"
],
"checksums": {
".felhom.yml": "761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6",
"app.yaml": "e19d2610820c4c974335b7b3609330fb11a5a2a608c719150642a81997288ce6",
"docker-compose.yml": "851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25"
},
"portable_secret_env_vars": [
"SECRET_KEY",
"DB_PASSWORD"
],
"data": {
"at": "2026-10-25T07:52:45Z",
"image_pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
},
"files": {
"db-dumps/tandoor-postgres.sql": {
"at": "2026-10-25T07:52:45.200747249Z",
"size": 397902,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
},
"volume-dumps/tandoor_tandoor_media.tar": {
"at": "2026-10-25T07:53:36.966857429Z",
"size": 1536,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
},
"volume-dumps/tandoor_tandoor_postgres_data.tar": {
"at": "2026-10-25T07:53:37.559407527Z",
"size": 56538624,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
},
"volume-dumps/tandoor_tandoor_static.tar": {
"at": "2026-10-25T07:53:39.14242749Z",
"size": 94322176,
"pins": [
"ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
],
"images": {
"tandoor": "ghcr.io/tandoorrecipes/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72",
"tandoor-postgres": "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24"
}
}
}
}
}
@@ -0,0 +1,15 @@
07:50:12Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
07:52:27Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
07:52:27Z gate: recipes is gated — passed as the household (cookie set)
07:52:40Z tandoor: createsuperuser :: Running django-vite in production mode (no HMR) Superuser created successfully.
07:52:44Z tandoor: seeded superuser drill0dcaea
07:52:44Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
07:54:26Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
07:54:35Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
07:56:46Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
08:00:38Z capture after the reinstall: 2026/10/25 08:00:36 recovery_unit.go:289: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable
08:00:41Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
08:03:02Z tandoor: READBACK UNUSABLE — a username that cannot exist did not read as absent (None) :: n3.13/site-packages/psycopg2/__init__.py", line 122, in connect conn = _connect(dsn, connection_factory=connection_factory, **kwasync) django.db.utils.OperationalError: connection to server at "tandoo
08:03:08Z state after restore: starting, item: False, db auth errors: tandoor: 44
tandoor-postgres: 23
08:03:43Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
@@ -0,0 +1,15 @@
07:50:12Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
07:52:27Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
07:52:27Z gate: recipes is gated — passed as the household (cookie set)
07:52:40Z tandoor: createsuperuser :: Running django-vite in production mode (no HMR) Superuser created successfully.
07:52:44Z tandoor: seeded superuser drill0dcaea
07:52:44Z [4] Mentés most -> 200 {'ok': True, 'message': 'Mentés elindítva'}
07:54:26Z [5] stop 200, remove KEEPING data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
07:54:35Z [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
07:56:46Z [1] deployed, controller state=running, pinned={'tandoor': 'ghcr.io/tandoorrecipes/recipes:2.6.15', 'tandoor-postgres': 'postgres:17-alpine'}
08:00:38Z capture after the reinstall: 2026/10/25 08:00:36 recovery_unit.go:289: [INFO] [backup] Recovery unit captured for tandoor → /mnt/sys_drive/felhom-data/backups/primary/tandoor (images=2, secrets-referenced=2, data_keys=0, portable
08:00:41Z [6] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
08:03:02Z tandoor: READBACK UNUSABLE — a username that cannot exist did not read as absent (None) :: n3.13/site-packages/psycopg2/__init__.py", line 122, in connect conn = _connect(dsn, connection_factory=connection_factory, **kwasync) django.db.utils.OperationalError: connection to server at "tandoo
08:03:08Z state after restore: starting, item: False, db auth errors: tandoor: 44
tandoor-postgres: 23
08:03:43Z [7] stop 200, remove with data -> 200 {'ok': True, 'data': {'removed': 'tandoor', 'volumes_removed': ['tandoor_tandoor_media', 'tandoor_tandoor_postgres_data', 'tandoor_tandoor_static'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [],
@@ -0,0 +1,26 @@
# D47 red-proof: guardUnitDir returns nil at once (= e1613ae: no guard). 2026-10-25T08:02:42Z
=== RUN TestD47_TheSweepOrderNeverRewritesTheKeptUnit
unit_owner_test.go:117: install A's unit must be set aside whole (one folder), found []; primary app.yaml now: "# Captured by felhom-controller recovery unit.\n# This file CARRIES SECRETS (D5) so a Tier-1/2 restore needs the drive and nothing else:\n# data
--- FAIL: TestD47_TheSweepOrderNeverRewritesTheKeptUnit (0.00s)
=== RUN TestD47_AUnitFromBeforeD47IsSetAsideWhenItsPasswordDiffers
unit_owner_test.go:152: an unrecorded unit with another install's password must be set aside whole, found []
--- FAIL: TestD47_AUnitFromBeforeD47IsSetAsideWhenItsPasswordDiffers (0.00s)
=== RUN TestD47_TheSetAsideUnitRestoresWithItsOwnPassword
unit_owner_test.go:165: one kept copy expected, got []
--- FAIL: TestD47_TheSetAsideUnitRestoresWithItsOwnPassword (0.00s)
=== RUN TestD47_TheWriterRefusesWhenTheUnitCannotBeSetAside
unit_owner_test.go:199: the capture must refuse, got <nil>
--- FAIL: TestD47_TheWriterRefusesWhenTheUnitCannotBeSetAside (0.00s)
=== RUN TestD47_TheNightDatabaseDumpSetsTheEarlierUnitAsideFirst
unit_owner_test.go:236: install A's unit must be set aside whole before install B's dump is written, found []
--- FAIL: TestD47_TheNightDatabaseDumpSetsTheEarlierUnitAsideFirst (0.00s)
=== RUN TestD47_ASameInstallBackupStillRefreshesTheUnit
--- PASS: TestD47_ASameInstallBackupStillRefreshesTheUnit (0.00s)
=== RUN TestD47_NoSetAsideWhileTheAppIsRestored
unit_owner_test.go:275: a write during the app's restore must refuse, got <nil>
--- FAIL: TestD47_NoSetAsideWhileTheAppIsRestored (0.00s)
=== RUN TestD47_OwnerVerdict
--- PASS: TestD47_OwnerVerdict (0.00s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/backup 0.015s
FAIL
@@ -0,0 +1,10 @@
# D47 with the guard: 2026-10-25T08:02:42Z
--- PASS: TestD47_TheSweepOrderNeverRewritesTheKeptUnit (0.00s)
--- PASS: TestD47_AUnitFromBeforeD47IsSetAsideWhenItsPasswordDiffers (0.00s)
--- PASS: TestD47_TheSetAsideUnitRestoresWithItsOwnPassword (0.00s)
--- PASS: TestD47_TheWriterRefusesWhenTheUnitCannotBeSetAside (0.00s)
--- PASS: TestD47_TheNightDatabaseDumpSetsTheEarlierUnitAsideFirst (0.00s)
--- PASS: TestD47_ASameInstallBackupStillRefreshesTheUnit (0.00s)
--- PASS: TestD47_NoSetAsideWhileTheAppIsRestored (0.00s)
--- PASS: TestD47_OwnerVerdict (0.00s)
ok gitea.dooplex.hu/admin/felhom-controller/internal/backup 0.031s
@@ -0,0 +1,6 @@
# D47 web red-proof: the kept section only inside the off-site card (the old place). 2026-10-25T09:00:05Z
kept_copies_test.go:177: a box without off-site must list its kept copies once, with the restore
--- FAIL: TestD47_KeptCopiesAreListedWithoutOffsite (0.06s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/web 0.068s
FAIL
@@ -0,0 +1,9 @@
# D47 stacks red-proof: the restore never takes the unit's install (case disabled). 2026-10-25T08:03:15Z
install_id_test.go:21: the restored app must be the unit's install, got &{Deployed:true DeployedAt:2026-10-25T08:03:18Z Env:map[DOMAIN:example.hu SUBDOMAIN:gapp] LockedFields:[DOMAIN SUBDOMAIN] EmailEnabled:false DesiredState: InstalledImages:map[] PinnedImages:map[] LastUpdateUndone:<nil> FailedStep:<nil> LastAutoUpdate:<nil> ConversionCopy:<nil> EarlierConversionCopies:[] RestoredLogins:[] AfterInstall:<nil> SetupGate:<nil> FamilyGate:<nil> InstallHold:<nil> PreviousImages:map[] DefaultLogin:<nil> AfterSetup:<nil> AfterUncleanBoot:<nil> InstallID:bbbb000000000002}
--- FAIL: TestD47_ARestoreTakesTheUnitsInstall (0.00s)
--- PASS: TestD47_ARestoreOfAnUnrecordedUnitKeepsTheAppsInstall (0.00s)
--- PASS: TestD47_ARemovedAppRestoredGetsAnInstall (0.00s)
--- PASS: TestD47_EnsureInstallIDMintsOnce (0.00s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/stacks 0.016s
FAIL
@@ -0,0 +1,7 @@
# D47 web red-proof: the restore form never renders. 2026-10-25T08:13:32Z
kept_copies_test.go:112: exactly the set-aside unit's row offers the restore, found 0
--- FAIL: TestD47_RestorePageOffersRestoreOnlyForASetAsideUnit (0.06s)
--- PASS: TestD47_KeptUnitRestoreHandlerRefusals (0.00s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/web 0.071s
FAIL
@@ -0,0 +1,15 @@
# D48 red-proof: prepareRestoreImages does nothing (= e1613ae: no pull before the stop). 2026-10-25T08:36:14Z
restore_pull_test.go:36: an unpullable pin must refuse, got <nil> (pulled [])
--- FAIL: TestD48_AnUnpullablePinIsRefusedBeforeTheStop (0.00s)
restore_pull_test.go:58: the restore never asked for the image (err <nil>)
--- FAIL: TestD48_AnUnreachableRegistryReadsDifferently (0.00s)
restore_pull_test.go:80: the pull must come before the stop, calls [stop recreate startsvc:immich-postgres start]
--- FAIL: TestD48_APullablePinIsPulledFirstAndTheRestoreRuns (0.00s)
restore_pull_test.go:105: the check must refuse an unpullable pin, got <nil>
--- FAIL: TestD48_TheCheckOnlyPassCarriesThePull (0.00s)
restore_pull_test.go:118: an off-site restore with an unpullable image must refuse, got <nil>
--- FAIL: TestD48_TheOffsiteRestoreToo (0.00s)
--- PASS: TestD48_PullGone (0.00s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/backup 0.016s
FAIL
@@ -0,0 +1,12 @@
# R-1270 red-proof: the health timeout back to a WARN (REDPROOF=1 skips restoreNotStarted). 2026-10-25T08:30:44Z
restore_verdict_test.go:48: a restore whose app did not start must not be a success, got <nil>
--- FAIL: TestR1270_AUnitRestoreWhoseAppDoesNotStartIsNotASuccess (0.05s)
--- PASS: TestR1270_AHealthyRestoreIsASuccessAsBefore (0.00s)
restore_verdict_test.go:87: precondition: not started, got <nil>
--- FAIL: TestR1270_ALateStartTurnsTheRecordIntoASuccess (0.05s)
restore_verdict_test.go:121: an off-site restore whose app did not start must not be a success, got <nil>
--- FAIL: TestR1270_TheOffsiteRestoreToo (0.05s)
--- PASS: TestR1270_MarkLateStartTouchesOnlyItsOwnRecord (0.00s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/backup 0.170s
FAIL
@@ -0,0 +1,6 @@
# R-1270 web red-proof: endRestoreFailed treats a not-started restore as a failure. 2026-10-25T08:31:17Z
r1270_not_started_test.go:29: a not-started restore is recorded not ok and not started: &{Op:restore Stack:tandoor OK:false Message:Visszaállítás sikertelen: A(z) tandoor: az adatokat visszatöltöttük, de az alkalmazás még nem indult el. Várj pár percet, majd nyisd meg újra. Ha így marad, szólj nekünk. FinishedAt:2026-10-25 09:31:23.202219096 +0100 CET m=+0.013726945 Interrupted:false PutBack:false PutBackWaiting:false NotStarted:false}
--- FAIL: TestR1270_TheRecordOfANotStartedRestore (0.01s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/web 0.021s
FAIL
@@ -0,0 +1,16 @@
# R-1271 red-proof: KeptFuture always false (= e1613ae: the newest date wins, future or not). 2026-10-25T08:40:34Z
kept_pick_test.go:21: the household's own copy must win over a future-dated off-site one, got {UnitDir: Tier:3 Time:2026-10-28 06:39:00 +0000 UTC DriveLabel: SnapshotID:abba81ba UnitPath: Future:false}
--- FAIL: TestR1271_AFutureOffsiteCopyNeverBeatsTheOwnCopy (0.00s)
kept_pick_test.go:29: a future-dated copy alone is offered and marked Future (not recommended), got {UnitDir: Tier:3 Time:2026-10-28 06:39:00 +0000 UTC DriveLabel: SnapshotID:abba81ba UnitPath: Future:false} ok=true
--- FAIL: TestR1271_AFutureCopyAloneIsOfferedButMarked (0.00s)
--- PASS: TestR1271_AnOlderOffsiteCopyAloneIsChosenAsBefore (0.00s)
--- PASS: TestR1271_ANewerTrueOffsiteCopyStillWins (0.00s)
kept_pick_test.go:64: a copy beyond the skew is future-dated and never the newest, got {UnitDir: Tier:3 Time:2026-10-24 19:33:00 +0000 UTC DriveLabel: SnapshotID:f UnitPath: Future:false}
--- FAIL: TestR1271_TheClockSkewAllowance (0.00s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/backup 0.006s
r1271_future_copy_test.go:83: hu: a future-dated only copy is offered, marked and said: offered=true future=<nil> warn=""
--- FAIL: TestR1271_AFutureCopyAloneIsSaidAndNotRecommended (0.01s)
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/api 0.019s
FAIL
@@ -0,0 +1,5 @@
--- FAIL: TestR474_RemoveHandlerDeletesUnitMirrorAndPrefs (0.00s)
r474_remove_wiring_test.go:41: removeStack must call RestoreUndoDir
FAIL
FAIL gitea.dooplex.hu/admin/felhom-controller/internal/api 0.010s
FAIL
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""keptprobe.py — R-1271 live, on a throwaway drive-folder app this run installs (never a household's unit).
The measured case (catalog sweep 2026-10-24): a copy dated in the future beat the family's own copy. Here the
future-dated copy is FORGED on 9202 in a scratch place every box looks at — a second-drive copy of the app's own unit
under the system drive's backups/secondary/<app>/recovery-unit, its data time set to 2026-10-28T06:39:39Z (the
rehearsal box's clock), the only hand-made change. Then the household's order:
install, seed, „Mentés most", remove keeping data, install again → the question (expect: the OWN copy named, no
future flag); the own unit moved aside for a moment → the question again (expect: the forged copy offered, marked
future, with the sentence, not recommended); the own unit back → „A megőrzött adataimat használom" → the item back.
Then remove with data (the forged copy goes with the app's second-drive copies).
usage: SC=… SWEEP_EV=… TARGET=9202 DRIVE_ROOT=… python3 keptprobe.py <app>
"""
import json, os, sys, time
TOOLS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../catalog-sweep-2026-10-24/tools")
sys.path.insert(0, TOOLS)
import sweep as sw # noqa: E402
w, fxm = sw.w, sw.fxm
FUTURE = "2026-10-28T06:39:39Z"
def ask(app, sub):
os.environ.pop("KEPT", None)
c, d = w.ctl("POST", f"/api/stacks/{app}/deploy", {"values": dict(w.deploy_values(app, sub, dict(w.GENERATED.get(app) or {})))})
data = (d.get("data") or {}) if isinstance(d, dict) else {}
return c, {k: data.get(k) for k in ("code", "use_offered", "use_desc", "use_future", "use_warn", "use_off")}
def main(app):
d = os.path.join(sw.EV, "_keptprobe", app)
os.makedirs(d, exist_ok=True)
log = open(os.path.join(d, "walk.log"), "a")
def say(*a):
line = time.strftime("%H:%M:%SZ", time.gmtime()) + " " + " ".join(str(x) for x in a)
print(line, flush=True)
log.write(sw.scrub(line) + "\n")
log.flush()
w.say = say
fx = fxm.FIXTURES[app]
sub = getattr(fx, "sub", app)
rec = {"app": app}
forged = f"/mnt/sys_drive/felhom-data/backups/secondary/{app}/recovery-unit"
t_start = int(time.time())
unit = ""
w.login()
try:
rec["install"] = w.deploy(app, sub) and w.wait_app(sub, "/", tries=120, delay=5)
tok = fx.seed(w, sub, say)
rec["seed"] = bool(tok)
sw.backup_now(rec, say)
unit = sw.g(f"for p in /mnt/felhom-drives/*/backups/primary/{app} /mnt/felhom-drives/*/felhom-data/backups/primary/{app}; do [ -f $p/manifest.json ] && echo $p; done | head -1").strip()
rec["own_unit"] = unit
if not unit:
say(" no unit of the app on its drive — stop")
return
sw.g(f"rm -rf /mnt/sys_drive/felhom-data/backups/secondary/{app}; mkdir -p /mnt/sys_drive/felhom-data/backups/secondary/{app} && cp -a {unit} {forged} && "
f"sed -i -E 's/\"at\": \"[^\"]+\"/\"at\": \"{FUTURE}\"/' {forged}/manifest.json")
rec["forged_at"] = sw.g(f"grep -o '\"at\": \"[^\"]*\"' {forged}/manifest.json | head -2").strip()
say(f" forged a future-dated second-drive copy: {rec['forged_at']} (own unit: {unit})")
sw.remove(app, keep=True, say=say)
rec["q1_code"], rec["q1"] = ask(app, sub)
say(f" [question, own copy + future copy] {rec['q1_code']} {json.dumps(rec['q1'], ensure_ascii=False)[:400]}")
sw.g(f"mv {unit} {unit}.r1271-away")
rec["q2_code"], rec["q2"] = ask(app, sub)
say(f" [question, the future copy alone] {rec['q2_code']} {json.dumps(rec['q2'], ensure_ascii=False)[:500]}")
sw.g(f"mv {unit}.r1271-away {unit}")
os.environ["KEPT"] = "use"
w.DEPLOYED_THIS_RUN.discard(app)
rec["use_ok"] = w.deploy(app, sub, dict(w.GENERATED.get(app) or {}))
os.environ.pop("KEPT", None)
t0, last = time.time(), None
while time.time() - t0 < 1200:
time.sleep(5)
c, st = w.ctl("GET", "/api/backup/restore-status")
last = (st.get("data") or {}) if isinstance(st, dict) else {}
if not last.get("running") and time.time() - t0 > 10:
break
rec["load_status"] = json.dumps(last, ensure_ascii=False)[:600]
say(" load: " + rec["load_status"][:300])
w.wait_app(sub, "/", want=("200", "301", "302", "303", "307", "308", "401", "403"), tries=90, delay=5)
rec["item_after_load"] = bool(tok) and bool(fx.verify(w, sub, tok, say))
say(" item after use-my-kept-data: %s" % rec["item_after_load"])
finally:
os.environ.pop("KEPT", None)
if unit:
sw.g(f"[ -d {unit}.r1271-away ] && [ ! -e {unit} ] && mv {unit}.r1271-away {unit}")
rec["controller_lines"] = sw.scrub(sw.g(
f"docker logs --since {t_start} felhom-controller 2>&1 | grep -E '{app}' | grep -iE 'kept|USE MY|asking|future|restor' | cut -c1-300"))
if w.stack(app).get("deployed"):
sw.remove(app, keep=False, say=say)
rec["after_remove"] = sw.leftovers(app, sub)
rec["forged_left"] = sw.g(f"ls -d /mnt/sys_drive/felhom-data/backups/secondary/{app} 2>&1").strip()
json.dump(rec, open(os.path.join(d, "record.json"), "w"), ensure_ascii=False, indent=1)
if __name__ == "__main__":
main(sys.argv[1])
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""pullprobe.py — D48 and R-1270 live, on a throwaway app this run installs (never a household's unit).
1. install <app>, seed one item through the front door, „Mentés most";
2. keep a byte copy of the app's unit beside it (backups/d48-test-original/<app>, outside backups/primary);
3. D48: the unit's pin edited to a tag that does not exist (compose, manifest, data stamps — the same ref everywhere,
so the version check passes and the pull is what decides) → „Visszaállítás": refused, the app's containers never
restarted (StartedAt the same before and after), the household's sentence recorded;
4. the original unit back (sha256 of every file equal), a normal restore → success, the item reads back;
5. R-1270: the unit's pin edited to an image that pulls but exits at once (busybox) → „Visszaállítás": the record is
NOT ok, `not_started`, the household's sentence; the undo copy kept; then the original back and a normal restore;
6. remove with data; the test copy removed.
Endpoint-level for every household act; the unit edits are the only hand-made change (`pct exec`, files only).
usage: SC=… SWEEP_EV=… TARGET=9202 DRIVE_ROOT=… python3 pullprobe.py <app> <image-repo> [<busybox-ref>]
"""
import json, os, sys, time
TOOLS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../catalog-sweep-2026-10-24/tools")
sys.path.insert(0, TOOLS)
import sweep as sw # noqa: E402
w, fxm = sw.w, sw.fxm
NS = "/mnt/sys_drive/felhom-data"
MISSING_TAG = "0.0.0-felhom-d48-missing"
def started(app):
return sw.g(f"for c in $(docker ps -a --filter label=com.docker.compose.project={app} -q); do "
"docker inspect -f '{{.Name}} {{.State.StartedAt}} {{.State.Status}}' $c; done").strip()
def sums(path):
return sw.g(f"cd {path} 2>/dev/null && find . -type f | sort | xargs -r sha256sum")
def restore(app, rec, key, say):
sw.restore_unit(app, rec, say)
rec[key] = rec.pop("restore")
say(f" {key}: " + rec[key].get("status", "")[:400])
def main(app, repo, exiting):
d = os.path.join(sw.EV, "_pullprobe", app)
os.makedirs(d, exist_ok=True)
log = open(os.path.join(d, "walk.log"), "a")
def say(*a):
line = time.strftime("%H:%M:%SZ", time.gmtime()) + " " + " ".join(str(x) for x in a)
print(line, flush=True)
log.write(sw.scrub(line) + "\n")
log.flush()
w.say = say
fx = fxm.FIXTURES[app]
sub = getattr(fx, "sub", app)
rec = {"app": app}
unit = f"{NS}/backups/primary/{app}"
orig = f"{NS}/backups/d48-test-original/{app}"
w.login()
t_start = int(time.time())
try:
rec["install"] = w.deploy(app, sub) and w.wait_app(sub, "/", tries=90, delay=5)
tok = fx.seed(w, sub, say)
rec["seed"] = bool(tok)
sw.backup_now(rec, say)
sw.g(f"rm -rf {orig}; mkdir -p {NS}/backups/d48-test-original && cp -a {unit} {orig}")
s0 = sums(unit)
rec["unit_files"] = len(s0.splitlines())
def edit(to_ref):
# the same ref in the definition, the manifest (pins + data block) and the data stamps — files only
return sw.g(f"cd {unit} && sed -i -E 's#{repo}:[^\"[:space:]]*#{to_ref}#g' compose/docker-compose.yml manifest.json data-stamps.json "
f"&& grep -c '{to_ref}' compose/docker-compose.yml manifest.json data-stamps.json")
def put_original():
sw.g(f"rm -rf {unit} && cp -a {orig} {unit}")
same = sums(unit) == s0
say(f" the original unit is back byte for byte: {same}")
return same
# 3. D48
say(" [D48] the unit's pin → " + repo + ":" + MISSING_TAG + " :: " + edit(f"{repo}:{MISSING_TAG}").replace("\n", " "))
before = started(app)
restore(app, rec, "d48_restore", say)
after = started(app)
rec["d48_started_before"], rec["d48_started_after"] = before, after
rec["d48_app_untouched"] = bool(before) and before == after
say(f" [D48] containers never restarted: {rec['d48_app_untouched']} ({before})")
rec["d48_item_still_there"] = bool(tok) and bool(fx.verify(w, sub, tok, say))
# 4. normal restore
rec["original_back_1"] = put_original()
restore(app, rec, "normal_restore_1", say)
rec["item_after_normal_restore_1"] = bool(tok) and bool(fx.verify(w, sub, tok, say))
say(f" normal restore: item back {rec['item_after_normal_restore_1']}, state {w.stack(app).get('state')}")
# 5. R-1270
if exiting:
sw.backup_now(rec, say) # the unit again after the restore (its data is the app's now)
sw.g(f"rm -rf {orig}; cp -a {unit} {orig}")
s0 = sums(unit)
say(" [R-1270] the unit's pin → " + exiting + " (pulls, exits at once) :: " + edit(exiting).replace("\n", " "))
restore(app, rec, "r1270_restore", say)
rec["r1270_state"] = w.stack(app).get("state")
rec["r1270_undo_copies"] = sw.g(f"ls -1 {NS}/backups/restore-undo/{app} 2>&1").strip()
say(f" [R-1270] state {rec['r1270_state']}, kept undo copy: {rec['r1270_undo_copies']}")
rec["original_back_2"] = put_original()
restore(app, rec, "normal_restore_2", say)
rec["item_after_normal_restore_2"] = bool(tok) and bool(fx.verify(w, sub, tok, say))
say(f" normal restore 2: item back {rec['item_after_normal_restore_2']}, state {w.stack(app).get('state')}")
finally:
rec["controller_lines"] = sw.scrub(sw.g(
f"docker logs --since {t_start} felhom-controller 2>&1 | grep -E '{app}' | grep -iE 'D48|R-1270|refused|downloaded|restor|not start|late' | cut -c1-320"))
sw.remove(app, keep=False, say=say)
rec["after_remove"] = sw.leftovers(app, sub)
rec["test_copy_removed"] = sw.g(f"rm -rf {orig}; rmdir {NS}/backups/d48-test-original 2>/dev/null; ls -d {orig} 2>&1").strip()
rec["restore_undo_left"] = sw.g(f"ls -1 {NS}/backups/restore-undo/{app} 2>&1").strip()
json.dump(rec, open(os.path.join(d, "record.json"), "w"), ensure_ascii=False, indent=1)
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else "")
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""rows.py [--apply] — this run's register changes in OPEN-ITEMS.md: R-1269/R-1270/R-1271 to VERIFY (fixed on main,
wait for the release), two new rows (R-1278, R-1279) in severity order with the heading counts, and a cross-reference
on R-1206. Without --apply it prints and changes nothing."""
import os, re, sys
FE = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../.."))
OPEN = os.path.join(FE, "documentation/backlog/OPEN-ITEMS.md")
CLOSED = os.path.join(FE, "documentation/backlog/CLOSED-ITEMS.md")
EV = "`audits/restore-truth-2026-10-25/"
SEV = {"P1": 1, "P2": 2, "P3": 3, "P4": 4}
VERIFY = ("**VERIFY** — fixed on main, waits for the release", "the next controller release")
FIXED = {
"R-1269": (" **2026-10-25 (D47, controller `02daebe` + `1f9c863`):** every install has an identity, the unit records it, and "
"every unit writer sets another install's unit aside whole (`backups/set-aside/<app>/<stamp>`, listed with „Visszaállítás”). "
f"Proven on 9202 ({EV}after/`): the old unit set aside byte for byte, restored with its own password, the account back.",
"release the controller (Monday list); re-run `tools/unitprobe2.py tandoor` on the released image"),
"R-1270": (" **2026-10-25 (controller `b680db8`):** a restore whose app does not reach running is never a success on any path; "
"the household reads that the app has not started yet; the undo copy is kept; a late start turns it into a success. "
f"Proven on 9202 ({EV}after/_pullprobe/`).",
"release the controller (Monday list); D49 asks whether to pour the old data back instead"),
"R-1271": (" **2026-10-25 (controller `bd9f4f7`):** cause — the off-site copy's date was the other box's snapshot time, "
"uncapped, and „newer than every local copy” took it; the „another drive” check compares a mount path (the same on "
"every box). Now a copy more than 10 minutes in the future never counts as the newest; alone it is offered, said "
f"and not recommended. Immich's 404 was the copy's old subdomain. Proven on 9202 ({EV}after/_keptprobe/`).",
"release the controller (Monday list); re-run `tools/keptprobe.py` on the released image"),
}
NEW = [
("R-1278", "Backup & restore", "P3",
"**A volume app's reinstall does not offer its kept backup.** „Remove — keep my data” removes a volume app's named "
"volumes and keeps only its backup; the kept-data question is about drive folders only (`07` §6.5, decision 36), so "
"the reinstall starts empty and says nothing. Since D47 (2026-10-25) the earlier install's unit is set aside and listed "
f"on the restore page („Megőrzött másolatok”, „Visszaállítás”) — but the install page does not point to it ({EV}README.md`).",
"OPEN", "—", "Design: at install, when a set-aside or removed-app unit of the app exists, say so and offer it beside a fresh "
"start; a test", "CC"),
("R-1279", "Backup & restore", "P3",
"**The `.fab` import removes a running app's volumes before its images are pulled — D48 does not cover it.** "
"`felhom-controller/controller/internal/appexport/restore.go` removes the live volumes (`RemoveStackVolumes`, step 1) "
"and pulls at the database start later; a bundle whose version cannot be downloaded then leaves the app gone (the "
"bundle itself stays, so a later import works). Found reading every restore path for D48 (2026-10-25).",
"OPEN", "—", "Pull the bundle's images before step 1 (the D48 rule and its two sentences); a test with an unpullable image", "CC"),
]
R1206_NOTE = (" **2026-10-25:** the kept-data choice no longer takes such a future-dated copy as the newest (R-1271, "
"controller `bd9f4f7`); retention is unchanged — this row.")
def main(apply):
text = open(OPEN).read()
ids = [int(x) for x in re.findall(r"\bR-(\d+)\b", text + open(CLOSED).read())]
for rid, *_ in NEW:
assert int(rid[2:]) > max(i for i in ids if i < int(rid[2:])) and f"**{rid}**" not in text, rid
lines = text.split("\n")
for rid, (note, action) in FIXED.items():
i = next(i for i, l in enumerate(lines) if l.startswith(f"| **{rid}** |"))
cells = lines[i].split(" | ")
assert len(cells) == 8, (rid, len(cells))
if "2026-10-25" not in cells[3]:
cells[3] += note
cells[4], cells[5], cells[6] = VERIFY[0], VERIFY[1], action
lines[i] = " | ".join(cells)
i = next(i for i, l in enumerate(lines) if l.startswith("| **R-1206** |"))
cells = lines[i].split(" | ")
if "R-1271" not in lines[i]:
cells[4] = cells[4] + R1206_NOTE
lines[i] = " | ".join(cells)
for rid, cat, sev, what, state, blocked, action, owner in NEW:
row = f"| **{rid}** | {cat} | {sev} | {what} | {state} | {blocked} | {action} | {owner} |"
h = next(i for i, l in enumerate(lines) if re.match(rf"^## {re.escape(cat)} — \d+ rows", l))
end = next((i for i in range(h + 1, len(lines)) if lines[i].startswith("## ")), len(lines))
rows_at = [i for i in range(h, end) if lines[i].startswith("| **R-")]
pos = next((i for i in rows_at if SEV.get(lines[i].split(" | ")[2].strip(), 9) > SEV[sev]), rows_at[-1] + 1)
lines.insert(pos, row)
m = re.match(r"^(## .+? — )(\d+) rows \((.*)\)$", lines[h])
counts = {p: int(n) for p, n in re.findall(r"(P\d) (\d+)", m.group(3))}
counts[sev] = counts.get(sev, 0) + 1
lines[h] = f"{m.group(1)}{int(m.group(2)) + 1} rows ({', '.join(f'{k} {counts[k]}' for k in sorted(counts))})"
out = "\n".join(lines)
print("rows:", sum(1 for l in out.split("\n") if l.startswith("| **R-")))
if apply:
open(OPEN, "w").write(out)
if __name__ == "__main__":
main("--apply" in sys.argv)
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""unitprobe2.py — R-1269 / D47 AFTER the fix: the sweep's order (catalog-sweep-2026-10-24/tools/unitprobe.py), then
the household's way back: „Visszaállítás" on the set-aside unit of the earlier install (restore page, „Megőrzött
másolatok"). Endpoint-level, the requests the pages send; nothing is planted by hand, no secret is read or printed
(units are compared by sha256 of every file).
usage: SC=… SWEEP_EV=<evidence dir> TARGET=9202 DRIVE_ROOT=… python3 unitprobe2.py <app>
"""
import json, os, re, sys, time
TOOLS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../catalog-sweep-2026-10-24/tools")
sys.path.insert(0, TOOLS)
import sweep as sw # noqa: E402
w, fxm = sw.w, sw.fxm
UNIT = "/mnt/sys_drive/felhom-data/backups/primary/{app}"
ASIDE = "/mnt/sys_drive/felhom-data/backups/set-aside/{app}"
def sums(path):
return sw.g(f"cd {path} 2>/dev/null && find . -type f | sort | xargs -r sha256sum")
def page(path):
sess = open(f"{w.SC}/sess{os.getpid()}.txt").read().strip()
r = w.sh(["curl", "-sk", "-H", w.HOSTHDR, "-H", f"Cookie: {sess}", f"{w.BASE}{path}"])
return r.stdout or ""
def post_form(path, fields):
sess = open(f"{w.SC}/sess{os.getpid()}.txt").read().strip()
csrf = open(f"{w.SC}/csrf{os.getpid()}.txt").read().strip()
args = ["curl", "-sk", "-D", "-", "-o", "/dev/null", "-H", w.HOSTHDR, "-H", f"Cookie: {sess}", "-X", "POST",
"--data-urlencode", f"_csrf={csrf}"]
for k, v in fields.items():
args += ["--data-urlencode", f"{k}={v}"]
r = w.sh(args + [f"{w.BASE}{path}"])
head = (r.stdout or "").splitlines()
return (head[0] if head else "?"), [l for l in head if l.lower().startswith("location")]
def wait_restore(rec_key, rec):
t0, last = time.time(), None
while time.time() - t0 < 1800:
time.sleep(5)
c, d = w.ctl("GET", "/api/backup/restore-status")
dd = (d.get("data") or {}) if isinstance(d, dict) else {}
last = dd
if not dd.get("running") and time.time() - t0 > 10:
break
rec[rec_key] = {"seconds": round(time.time() - t0), "status": json.dumps(last, ensure_ascii=False)[:700]}
def main(app):
d = os.path.join(sw.EV, "_unitprobe2", app)
os.makedirs(d, exist_ok=True)
log = open(os.path.join(d, "walk.log"), "a")
def say(*a):
line = time.strftime("%H:%M:%SZ", time.gmtime()) + " " + " ".join(str(x) for x in a)
print(line, flush=True)
log.write(sw.scrub(line) + "\n")
log.flush()
w.say = say
fx = fxm.FIXTURES[app]
sub = getattr(fx, "sub", app)
rec = {"app": app}
w.login()
t_start = int(time.time())
try:
rec["install"] = w.deploy(app, sub) and w.wait_app(sub, "/", tries=90, delay=5)
tok = fx.seed(w, sub, say)
rec["seed"] = bool(tok)
sw.backup_now(rec, say)
s1 = sums(UNIT.format(app=app))
open(os.path.join(d, "unit-1-after-backup.sha256"), "w").write(s1)
sw.remove(app, keep=True, say=say)
t_re = time.time()
rec["reinstall"] = w.deploy(app, sub, dict(w.GENERATED.get(app) or {}))
aside = ""
for _ in range(60):
time.sleep(10)
aside = sw.g(f"docker logs --since {int(t_re)} felhom-controller 2>&1 | grep -E '{app}: the recovery unit of an earlier install' | cut -c1-400")
if aside.strip():
break
rec["set_aside_line"] = aside.strip()
say(" set aside: " + (aside.strip()[:300] or "NONE within 10 min"))
dirs = sw.g(f"ls -1 {ASIDE.format(app=app)} 2>/dev/null").split()
rec["set_aside_dirs"] = dirs
s2 = sums(ASIDE.format(app=app) + "/" + dirs[0]) if dirs else ""
open(os.path.join(d, "unit-2-set-aside.sha256"), "w").write(s2)
rec["set_aside_byte_identical"] = bool(s1.strip()) and s1 == s2
say(f" the set-aside unit is install A's unit byte for byte: {rec['set_aside_byte_identical']} ({len(s1.splitlines())} files)")
open(os.path.join(d, "unit-3-primary-now.sha256"), "w").write(sums(UNIT.format(app=app)))
html = page("/backups/restore")
m = re.search(r'action="/backup/kept/restore".*?name="id" value="([0-9a-f]{16})"', html, re.S)
rec["restore_page_offers"] = bool(m)
sec = html[html.find("Megőrzött másolatok"):][:1500] if "Megőrzött másolatok" in html else ""
open(os.path.join(d, "restore-page-kept-section.html"), "w").write(sw.scrub(sec))
if not m:
say(" the restore page offers NO restore of the set-aside unit")
return
head, loc = post_form("/backup/kept/restore", {"id": m.group(1), "confirm": "1"})
say(f" [6] POST /backup/kept/restore -> {head} {loc[:1]}")
wait_restore("restore", rec)
say(" restore status: " + rec["restore"]["status"][:300])
time.sleep(20)
rec["state_after_restore"] = w.stack(app).get("state")
rec["item_after_restore"] = bool(tok) and bool(fx.verify(w, sub, tok, say))
rec["db_auth_errors_after_restore"] = sw.g(
f"for c in $(docker ps -a --filter label=com.docker.compose.project={app} --format '{{{{.Names}}}}'); do "
f"docker logs --since 5m $c 2>&1 | grep -ciE 'password authentication failed|access denied for user' | sed \"s/^/$c: /\"; done").strip()
say(" state after restore: %s, item: %s, db auth errors: %s" % (rec["state_after_restore"], rec["item_after_restore"],
rec["db_auth_errors_after_restore"]))
finally:
rec["controller_lines"] = sw.scrub(sw.g(
f"docker logs --since {t_start} felhom-controller 2>&1 | grep -E '{app}' | grep -iE 'set aside|restor|captured|install id|D47|D48|R-1270|refused' | cut -c1-300"))
sw.remove(app, keep=False, say=say)
rec["after_remove"] = sw.leftovers(app, sub)
rec["set_aside_after_remove"] = sw.g(f"ls -1 {ASIDE.format(app=app)} 2>&1 | head -3").strip()
json.dump(rec, open(os.path.join(d, "record.json"), "w"), ensure_ascii=False, indent=1)
if __name__ == "__main__":
main(sys.argv[1])
+10 -9
View File
@@ -158,20 +158,20 @@ stopping line that lies.
| **R-683** | App updates | P3 | **[P3-LOW] Watch: after a power cut during an update's health check, the hold named an HOUR-OLD second-drive copy, not the one the update's own backup should have just made.** 2026-09-24 chaos round 3 (nextcloud, `backup_max_age: 1m`): no `backing-up` phase was seen and the hold named Tier 2 at 13:04 for an update pressed at 14:04; the pre-cut controller log was lost with the container (the runner now saves it at arm time — R-320). Round 11, the same action without a power cut, named a fresh 14:34 copy and logged the Tier-2 copy. The sentence was TRUE (it named the copy it offered); the question is why the update did not back up first. Not reproduced; watch the next power-cut drill. `audits/night-2026-09-24/E/round-03*.json`, `E/round-11-controller-pre.log` | **OPEN — P3; owner: CC (watch)** | — | — | CC |
| **R-785** | App updates | P3 | **[P3-LOW] SparkyFitness is pinned 11 releases and a major behind upstream (v0.17.3; upstream v1.7.3, v1.6.0 dated 2026-07-24).** READ 2026-10-01 (`audits/visitors-2026-10-01/C/bench/C1-previous-tag.txt`). **Needs:** an update walk 0.17 → 1.x through the ladder (bench + box), after R-784 is decided. | **OPEN — rank P3-LOW; owner: CC (after R-784)** | — | — | CC |
## Backup & restore — 56 rows (P1 1, P2 14, P3 23, P4 18)
## Backup & restore — 58 rows (P1 1, P2 14, P3 25, P4 18)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
| **R-1251** | Backup & restore | P1 | **A power cut during a restore of a DRIVE-backed app: the app then runs on the cut restore's data, and a later controller restart pours the pre-restore copy back over everything written since — the D22 promise is false for every drive-backed app.** Found 2026-10-24 (review round 2, HUNT-1 H1). Read at `file:line` (controller `321df1f`): the undo copy lives on the app's drive (`internal/backup/offbox_restore_undo.go:47-48`); `AppStopGuard.Recover` runs at start BEFORE the drive is bound, finds the copy „gone” and restarts „as before” (`appstop_marker.go:362-367`); the gated start refuses (drive absent) → marker and Undo KEPT (`:383-387,408-411`); the drive binds and the boot recreate starts the app checking only „held” (`internal/web/intermediary.go:470-476`); the household writes; the next controller start (e.g. the nightly self-update) finds the copy present and pours it back. **Proven with the real `Recover` code** in a lean clone (`audits/review-round-2-2026-10-24/``proofs/H1-drive-cut-restore/`); NOT reproduced on 9202 (its drive is mounted at boot and its agent API is off, so the recreate never runs there). Same family: H2 — a FAILED crash put-back leaves the app stopped and unheld, so Start or the recreate runs it and a later restart pours an old copy back (`appstop_marker.go:368-371`; `api/router.go:690-709`); H7 — Stop pressed during a restore skips the put-back after a cut (`appstop_marker.go:356-361`). **Fixed on main 2026-10-24 night** (controller `bb2809d` + `062f9b4`; restore-holes brief Part 1): a copy on an unbound drive WAITS (restore hold `putback_pending`), every start path honours the hold, the put-back runs first when the drive binds; H2 a failed put-back holds (D20); H7 Stop still gets the put-back; the copy's folders are synced before the marker names it (run 1 of the live proof found `undo.json.tmp` after the cut). **Proven live on Tester 1** (`audits/restore-holes-2026-10-24/P1-tester1/`). | **VERIFY** — fixed on main, waits for the release | the next controller release | release the controller (Monday list); re-read P1's two observables on the released image (a waiting hold at start, the put-back before the start) | CC |
| **R-232** | Backup & restore | P2 | **DooPlex's backup makes every copy inside the same box — and nothing tells anyone when it fails.** Surveyed read-only 2026-08-06 (`audits/RECON-dooplex-backup-2026-08-06.md`). **What works:** five sets, 14/14 successful runs in 14 days; a file was restored from the `data` repo and matched the live original **byte for byte**; every set except two is cross-disk; k3s is integrity-checked on every run. **What the matrix exposes, ranked:** (a) **`notify_failure` is a no-op** — `NOTIFY_ON_FAILURE=true` but `NOTIFY_WEBHOOK_URL` is commented out, so a failed backup notifies **nobody**; the project already has a working Resend path that CI uses. Cheapest item, and it makes every other failure visible. (b) **Nothing leaves the box** — no rclone, no remote repo, no off-site target anywhere; Longhorn's target is `nfs://192.168.0.180:` pointing at DooPlex itself, and the only outbound-looking cron pulls *inbound* from Hetzner for a different project. The machine that runs the hub managing the customers' off-site chain has no off-site copy of its own. (c) **The backup tree is a single writable path** and the restic repos are not append-only — one bad script or ransomware destroys every copy at once. (d) **Two same-disk sets**: `.claude-memory` and the PostgreSQL dumps, whose source directory sits *inside* the backup tree. (e) **Longhorn `retain=1`** — one generation per volume, so a corruption noticed a day late has no earlier copy. (f) **`/opt/backup/docs/BACKUP-RESTORE.md` does not exist** though the systemd unit advertises it. (g) **`secrets/restic-repo` has never held a snapshot** — `backup-secrets.sh` contains no `restic` call; the secrets are GPG files on `sda1` only. (h) **No restore has ever been run** beyond today's single-file probe — the matrix's "ever demonstrated?" column is otherwise entirely empty. **Not a finding:** the restic passphrase. The on-box copy is on `sdb1`, a different disk from the backups, and the **operator holds an offline copy out of band** — so a disk loss is recoverable. The narrow residual is that it is operator-held rather than system-held, unlike the customer case's hub-vaulted escrow, so it should be confirmed current and findable by someone else. **Nothing was changed by the recon.** | NARROWED — **2026-10-23 (operator yes D33, D34):** the hub DB now leaves every 6 hours (hub 0.167.0 + `scripts/hub-db-backup/`, alarm at 13 h; restored back byte-identical) and the released images and agent packages leave nightly (`scripts/release-copy/`, R-1227 closed). (c)–(g) unchanged. **NARROWED 2026-10-08 — owner Viktor.** (a) DONE with the operator's yes in chat: `notify_failure` now mails admin@felhom.eu through Resend; proven by one test mail that reached the inbox (`audits/day-2026-10-08/r232/`; no backup was started). (b) partly: the hub database leaves DooPlex nightly to ep0 (R-173); everything else stays on the box. (c)–(h) unchanged. **2026-10-09 (operator yes in chat): (b) and (h) DONE for Gitea and the secrets.** Nightly 00:20 `felhom-dooplex-offsite` pushes Gitea (614 MB of repositories, the `gitea` database dump taken first, `app.ini`) and the nightly GPG secrets export, encrypted with a NEW key, to ep0 `operator` (`host/dooplex-gitea`) on the hub-DB write-only token — no change on ep0; first push 57 s; restore test weekly (manifest, `git fsck` every repo, `pg_restore --list`), run once: 27 805 files, 10 repos OK; `DooplexGiteaOffsiteStale`/`DooplexGiteaRestoreTestStale` in force (`absent()` included, promtool + 2 red-proofs); every backup unit now mails on failure (`OnFailure=`, dry run reached the inbox). **First real Gitea restore:** into a throwaway on the bench, 10/10 repos, product `main` = live, a file byte for byte, a login (`runbooks/gitea-restore.md`). The hub came back too (R-173, closed). **The registry is left out on purpose:** its images rebuild from the code. **Correction to the recon §7:** the API mirror holds ONE repo (`homelab-manifests`), not all — Gitea had a single copy, on DooPlex. `audits/dooplex-survival-2026-10-09/` **LEFT:** (c) append-only for the on-box tree, (d)–(g) as written; the paper copy of the new key (operator). **READY** for the rest **2026-10-24 (attended): `DooplexGiteaOffsiteStale` fired from 00:57Z — not this path's fault.** The 00:20 push refused, as designed, a 12 h old Gitea dump: the PostgreSQL dump CronJob had failed every run since 2026-10-23 16:00Z (`28P01`) because the window reboot restarted the database and CNPG put the bootstrap secret's password in force (homelab `531023d`, operator's pick: the CronJob only). Fixed; dump, push and restore test run by hand (26 456 files, 12 repos `git fsck`, `gitea.dump` 1114 entries); cleared 10:51Z (`audits/dooplex-alarms-2026-10-24/`). Backup alarms now arrive as ONE mail every 4 h (homelab `22d7697`). **2026-10-23 night — survey again, read only (`audits/dooplex-survival-2026-10-22/PLAN.md`):** both nightly off-site jobs Line truncated
| **R-232** | Backup & restore | P2 | **DooPlex's backup makes every copy inside the same box — and nothing tells anyone when it fails.** Surveyed read-only 2026-08-06 (`audits/RECON-dooplex-backup-2026-08-06.md`). **What works:** five sets, 14/14 successful runs in 14 days; a file was restored from the `data` repo and matched the live original **byte for byte**; every set except two is cross-disk; k3s is integrity-checked on every run. **What the matrix exposes, ranked:** (a) **`notify_failure` is a no-op** — `NOTIFY_ON_FAILURE=true` but `NOTIFY_WEBHOOK_URL` is commented out, so a failed backup notifies **nobody**; the project already has a working Resend path that CI uses. Cheapest item, and it makes every other failure visible. (b) **Nothing leaves the box** — no rclone, no remote repo, no off-site target anywhere; Longhorn's target is `nfs://192.168.0.180:` pointing at DooPlex itself, and the only outbound-looking cron pulls *inbound* from Hetzner for a different project. The machine that runs the hub managing the customers' off-site chain has no off-site copy of its own. (c) **The backup tree is a single writable path** and the restic repos are not append-only — one bad script or ransomware destroys every copy at once. (d) **Two same-disk sets**: `.claude-memory` and the PostgreSQL dumps, whose source directory sits *inside* the backup tree. (e) **Longhorn `retain=1`** — one generation per volume, so a corruption noticed a day late has no earlier copy. (f) **`/opt/backup/docs/BACKUP-RESTORE.md` does not exist** though the systemd unit advertises it. (g) **`secrets/restic-repo` has never held a snapshot** — `backup-secrets.sh` contains no `restic` call; the secrets are GPG files on `sda1` only. (h) **No restore has ever been run** beyond today's single-file probe — the matrix's "ever demonstrated?" column is otherwise entirely empty. **Not a finding:** the restic passphrase. The on-box copy is on `sdb1`, a different disk from the backups, and the **operator holds an offline copy out of band** — so a disk loss is recoverable. The narrow residual is that it is operator-held rather than system-held, unlike the customer case's hub-vaulted escrow, so it should be confirmed current and findable by someone else. **Nothing was changed by the recon.** | NARROWED — **2026-10-23 (operator yes D33, D34):** the hub DB now leaves every 6 hours (hub 0.167.0 + `scripts/hub-db-backup/`, alarm at 13 h; restored back byte-identical) and the released images and agent packages leave nightly (`scripts/release-copy/`, R-1227 closed). (c)–(g) unchanged. **NARROWED 2026-10-08 — owner Viktor.** (a) DONE with the operator's yes in chat: `notify_failure` now mails admin@felhom.eu through Resend; proven by one test mail that reached the inbox (`audits/day-2026-10-08/r232/`; no backup was started). (b) partly: the hub database leaves DooPlex nightly to ep0 (R-173); everything else stays on the box. (c)–(h) unchanged. **2026-10-09 (operator yes in chat): (b) and (h) DONE for Gitea and the secrets.** Nightly 00:20 `felhom-dooplex-offsite` pushes Gitea (614 MB of repositories, the `gitea` database dump taken first, `app.ini`) and the nightly GPG secrets export, encrypted with a NEW key, to ep0 `operator` (`host/dooplex-gitea`) on the hub-DB write-only token — no change on ep0; first push 57 s; restore test weekly (manifest, `git fsck` every repo, `pg_restore --list`), run once: 27 805 files, 10 repos OK; `DooplexGiteaOffsiteStale`/`DooplexGiteaRestoreTestStale` in force (`absent()` included, promtool + 2 red-proofs); every backup unit now mails on failure (`OnFailure=`, dry run reached the inbox). **First real Gitea restore:** into a throwaway on the bench, 10/10 repos, product `main` = live, a file byte for byte, a login (`runbooks/gitea-restore.md`). The hub came back too (R-173, closed). **The registry is left out on purpose:** its images rebuild from the code. **Correction to the recon §7:** the API mirror holds ONE repo (`homelab-manifests`), not all — Gitea had a single copy, on DooPlex. `audits/dooplex-survival-2026-10-09/` **LEFT:** (c) append-only for the on-box tree, (d)–(g) as written; the paper copy of the new key (operator). **READY** for the rest **2026-10-24 (attended): `DooplexGiteaOffsiteStale` fired from 00:57Z — not this path's fault.** The 00:20 push refused, as designed, a 12 h old Gitea dump: the PostgreSQL dump CronJob had failed every run since 2026-10-23 16:00Z (`28P01`) because the window reboot restarted the database and CNPG put the bootstrap secret's password in force (homelab `531023d`, operator's pick: the CronJob only). Fixed; dump, push and restore test run by hand (26 456 files, 12 repos `git fsck`, `gitea.dump` 1114 entries); cleared 10:51Z (`audits/dooplex-alarms-2026-10-24/`). Backup alarms now arrive as ONE mail every 4 h (homelab `22d7697`). **2026-10-23 night — survey again, read only (`audits/dooplex-survival-2026-10-22/PLAN.md`):** both nightly off-site jobs Line truncated
| **R-895** | Backup & restore | P2 | **The hub's clean-up-window check trusts the snapshot counts the box sends, so a broken-into box (or past-dated fakes added through the add-only key) can shrink the real off-site history without an alarm.** READ 2026-10-06 night in source (R-822's design): the before/after comparison uses counts the box itself reports (`hub/internal/offsitekeys/service.go:284`, `:343`); new fakes keep the count level. Decision 68 already accepts a box-trusted count. | **NARROWED 2026-10-18 — slice 2 built, not deployed** (hub v0.158.0): the 07:10 audit compares the `snapshots/` listing with yesterday's by name — files gone that no clean-up window explains → `offsite_snapshots_vanished` (error), files no reported box run explains → `offsite_snapshots_unexplained` (warning); operator-only, hub-origin (not in `allowedEventTypes`). First listing = baseline; a move-aside or RESET drops it; vanishing judged on a pinned tier only; files younger than 1 h judged the next day. Red-proofed twice (`snapshot_watch_test.go`). **Not built:** the window's before/after on the hub's own count (design §3.1) and refusing a window while unexplained files exist (§3.2). Unmeasured: the provider shell's `ls -l` time zone (± 3 h slack until then). **Deployed 2026-10-18 12:23 (hub v0.158.0).** The first listing (tomorrow 07:10) is a baseline; the first verdict is 2026-10-20 07:10. **Known limit (independent review 2026-10-18, F2):** `_unexplained` is explained by the box's OWN reported `offsite.last_run`, so whoever holds a box's key can silence it; `_vanished` is judged on the hub's own listing and is not affected. **2026-10-20 03:5x UTC: due date moved 10-20 → 10-21** — the first verdict lands at 07:10 hub time (05:10 UTC), after the night session that reached it ended; that brief did not include this read. Nothing is lost: the hub keeps its listing; read the 2026-10-20 07:10 lines on 10-21 (or later on 10-20). **FIRST VERDICT READ 2026-10-21 (rehearsal session, read-only):** the 07:10 CEST run judged three tiers against yesterday — demo-felhom 5, demo-hp 14, tester-1 4 new snapshot files, „each inside a reported box run"; nothing vanished, nothing unexplained; Tester4 stored its first baseline (44 files); Tester-2's own listing failed (no `snapshots/` dir — the box has never pushed). No `offsite_snapshots_vanished` / `_unexplained` mail in the operator mailbox (second channel). Evidence `audits/due-checks-2026-10-21/R-895-hub-0710.txt`. The positive alarm path is still proven only by the unit red-proofs. | the operator's yes to deploy hub v0.158.0 | Deploy; read the first 07:10 lines (newest upload vs the box's last run = the time-zone calibration), shrink the slack; then decide §3.1/§3.2 | CC |
| **R-923** | Backup & restore | P2 | **The password manager (Vaultwarden) runs on DooPlex and holds the keys that open DooPlex's own off-site copies — a loss of DooPlex loses the keys with the copies.** Operator finding, 2026-10-09 11:19: "the password manager also runs on DooPlex (Vaultwarden), so I probably need to back up its DB somewhere too." Measured: `vaultwarden-system/vaultwarden` (1.37.3), PVC `vaultwarden-data` 7.4 MB (`db.sqlite3` + WAL, `rsa_key.pem`, no attachments); its only backups were Longhorn on DooPlex. **A key in a password manager that runs on DooPlex is not off DooPlex** — R-173's "both keys are off DooPlex" (2026-10-05) and the hub-DB runbook Step 0 were wrong in that sense. Also seen: Vaultwarden warns its `ADMIN_TOKEN` is plain text (an Argon2 hash is its guidance). | NARROWED — **2026-10-23 (D32):** the sheet is current — names and places only, one A4, HU + EN (`runbooks/break-glass-sheet.md`); every place checked to exist. LEFT: the operator prints it, fills it by hand, keeps it outside the home. **NARROWED 2026-10-09 (operator yes in chat):** Vaultwarden's own `backup` copy + `rsa_key.pem` ride the nightly encrypted DooPlex→ep0 job (`scripts/dooplex-offsite/`), checked on every push and every Sunday (integrity, users, items — rows only). That copy opens only with the DooPlex off-site key, and the vault only with the master password — so the circle is broken only once both are on paper outside DooPlex: `runbooks/break-glass-sheet.md`, `runbooks/total-loss-of-dooplex.md`. **LEFT:** the operator prints the sheet and stores it away from home; the `ADMIN_TOKEN` hash (a DooPlex change, operator's word). **2026-10-23 night — survey (`audits/dooplex-survival-2026-10-22/PLAN.md`, G1/G3/G10):** unchanged; the sheet is still the one act that breaks the circle, and S5's offline form is unknown to CC — on the sheet too (decide list in STATUS). | operator: print the sheet | Operator: print + store the sheet; then close | operator |
| **R-1252** | Backup & restore | P2 | **One rejected backup block in an app's `.felhom.yml` makes that night's second-drive copy DELETE the app's copies of the household's files.** REPRODUCED on 9202 2026-10-24 (`audits/review-round-2-2026-10-24/``proofs/H2-bad-backup-block/`): audiobookshelf with a marker file in `media/audiobooks`; two unchanged runs kept the copy (the positive control); one bogus path in the block → „backup block rejected … matches no compose bind” → the legacy legs (appdata only) → „Tier 2 reconcile: removed stale dest dir userdata/media”; the original on the drive untouched. Mechanism: `internal/stacks/metadata.go:550-557` (any defect rejects the WHOLE block) → `appbackup/classify.go:217-221` → `backup/tier2_capture.go:84-100` → `tier2.go:492` `tier2Reconcile` (`:325-329`), against D25's own header (`tier2_kept.go:17-18`). Same family (static): a D25 kept copy is reconciled away when its leg leaves the declared set and its record stays (HUNT-2 H2, `tier2.go:485-492`); Remove with `remove_backups` deletes kept copies and leaves their records (HUNT-2 H9, `api/router.go:1086-1087`). **Fixed on main 2026-10-24 night** (controller `518a135`; Part 2): reconcile only under a valid classification; a rejected block is said on the app's status and once a day to the operator (`backup_run_failures`, leg `tier2-settings-unreadable`); kept copies stay in the keep set (H2); Remove drops the kept records it deleted (H9). **Proven live on 9202** (`audits/restore-holes-2026-10-24/P2-9202/`: two control runs, then the bogus path → „REJECTED … every existing copy … KEPT”). | **VERIFY** — fixed on main, waits for the release | the next controller release | release the controller (Monday list) | CC |
| **R-1253** | Backup & restore | P2 | **An export with „stop the app first” during another app's restore overwrites the ONE app-stop marker file — the restore's crash record and its Undo are lost; a power cut then leaves the restored app on mixed data with no put-back.** Found 2026-10-24 (HUNT-1 H3): `AppStopGuard.Begin` writes unconditionally (`internal/backup/appstop_marker.go:279-292`) and `End()` removes the file (`:306-320`); the exporter takes the guard (`internal/appexport/export.go:139-143,262-266`) and neither `StartExport` (`:173-178`) nor the web handlers (`internal/web/handler_export*.go`, grep: no restore check) look for a running restore. Static. **Fixed on main 2026-10-24 night** (controller `c9b4b82`; Part 3): an export that stops its app refuses (409, „err.appexport.another_operation_running”) while another operation of this process holds the marker (`BusyWith`, `BeginExclusive`); picked over one marker per app (smaller: the exporter and one guard method). **Proven live on 9202** (`audits/restore-holes-2026-10-24/P3-9202/`: refused during a MeTube restore, uptime-kuma never stopped; the same export after it ran). | **VERIFY** — fixed on main, waits for the release | the next controller release | release the controller (Monday list) | CC |
| **R-1254** | Backup & restore | P2 | **The hub saves a household's new escrow package in TWO writes; a failure between them leaves the new repository hash beside the OLD identity package, and the box then marks itself escrowed — on a total loss the written code opens nothing.** Found 2026-10-24 (HUNT-3 H1), read at `file:line`: `hub/internal/api/handler.go:1415` `SaveHostEscrow` commits blob + hash, `:1445` `SaveHostDRBundle` sets `identity_blob` in a second statement (`internal/store/store.go:3788-3796`; the upsert `:3490-3511` never touches it); the controller's confirm reads the hash (`felhom-controller/controller/internal/report/escrow_confirm.go:195-217`); the agent sends once. Same family (H2): a lost or late answer (30 s) tells the household „the previous code still works” when the hub may already hold the new package (`felhom-agent/internal/localapi/escrow_ceremony.go:79,532-534,639-641`). Static. **The hub half is fixed on main 2026-10-24 night** (felhom.eu `dcf4f84c`; Part 3): key, hash and identity blob in ONE transaction (`SaveHostEscrowWithIdentity`), a bad identity refused before any write; hub image `0.170.0-rc.r1254` built locally (`67db584a45d7`), NOT pushed, NOT deployed. The agent ceremony half (H2: „the previous code still works” after a lost answer) is still open. | **NARROWED** — the hub half fixed on main (waits for the hub release); the ceremony half open | the hub release (attended); the agent half | deploy hub v0.170.0 (Monday list); then the ceremony asks the hub what it holds before it says „the previous code still works” | CC |
| **R-1269** | Backup & restore | P2 | **A reinstall's own capture rewrites the unit that „remove — keep backups” kept, so restoring that unit breaks the app and the household's item is gone.** Catalog sweep 2026-10-24, reproduced on its own (`audits/catalog-sweep-2026-10-24/_unitprobe/tandoor/`): install Tandoor, one account, „Mentés most”, remove keeping data and backups, install again (a volume app: no kept-data question) → 19:50:32 „Recovery unit captured for tandoor” rewrote ONLY the unit's `app.yaml` (checksum `0f366c97…` → `4438c1c5…`, `created_at` 19:45:26 → 19:50:32) beside the OLD data → „Visszaállítás” paired the NEW install's database password with the OLD data: „password authentication failed” ×42 (app) / ×22 (postgres), state `starting`, the account not there. Seen three times (the sweep's Tandoor cycle, this probe, AdventureLog's re-run 20:03:18 → 20:05:32 → restore); AdventureLog's first run worked only because no capture fell between. The comment at `felhom-controller/controller/internal/backup/recovery_unit.go:101-103` says the refresh never rewrites the definition away from its data. Household order: „I reinstalled, my recipes are gone — restore the backup”. The data itself is not deleted (the old volumes are in the unit). | OPEN | — | Design + fix in the controller: a capture of a NEW install must not adopt a unit whose data belongs to an earlier install (a new unit, or the old one set aside); a test in this order that fails today; then `tools/unitprobe.py` again | CC |
| **R-1270** | Backup & restore | P2 | **A unit restore reports success while the app never came up.** Catalog sweep 2026-10-24: the controller logged „tandoor restored but health check failed: stack tandoor did not reach running state within 1m30s after restore”, and the status the restore page reads said `ok: true` — „A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.” (17:32:41; again in the unitprobe, 19:52:38). The household is told it worked (`audits/catalog-sweep-2026-10-24/tandoor/`, `_unitprobe/tandoor/record.json`). | OPEN | — | The restore's verdict consults the health result; the message says the app did not start and what to do; a test that fails today | CC |
| **R-1271** | Backup & restore | P2 | **„Use my kept data” loaded ANOTHER box's database — a future-dated off-site copy of the rehearsal box — under today's kept files.** Catalog sweep 2026-10-24: Tester4's off-site store holds copies from the 2026-10-20 rehearsal, whose box clock was set to 2026-10-28. On the new box the question said „Az adatbázist innen töltjük vissza: távoli mentés, 2026-10-28 07:39” for Paperless-ngx, Nextcloud and Immich, and loaded it: the seeded account / user / album was not there, and Immich never answered again (404 for 20 min). RomM, whose newest copy was its own („saját mentés, 2026-10-24 19:17”), got its item back. `07` §6.5 says the load is refused when the unit „was taken of another drive” — the rehearsal box's drive was another disk, so that check did not hold here (which field it compares: not measured). The household's own local unit stays (nothing deleted). Same future-dated copies as R-1206 (that row: retention only). Evidence `audits/catalog-sweep-2026-10-24/paperless-ngx/`, `nextcloud/`, `immich/`. | OPEN | — | Read which drive identity the load compares and why the rehearsal unit passed; refuse a unit of another drive or one dated after now (or say so); a test with both; then the sweep's kept-data cycle on one app | CC |
| **R-1269** | Backup & restore | P2 | **A reinstall's own capture rewrites the unit that „remove — keep backups” kept, so restoring that unit breaks the app and the household's item is gone.** Catalog sweep 2026-10-24, reproduced on its own (`audits/catalog-sweep-2026-10-24/_unitprobe/tandoor/`): install Tandoor, one account, „Mentés most”, remove keeping data and backups, install again (a volume app: no kept-data question) → 19:50:32 „Recovery unit captured for tandoor” rewrote ONLY the unit's `app.yaml` (checksum `0f366c97…` → `4438c1c5…`, `created_at` 19:45:26 → 19:50:32) beside the OLD data → „Visszaállítás” paired the NEW install's database password with the OLD data: „password authentication failed” ×42 (app) / ×22 (postgres), state `starting`, the account not there. Seen three times (the sweep's Tandoor cycle, this probe, AdventureLog's re-run 20:03:18 → 20:05:32 → restore); AdventureLog's first run worked only because no capture fell between. The comment at `felhom-controller/controller/internal/backup/recovery_unit.go:101-103` says the refresh never rewrites the definition away from its data. Household order: „I reinstalled, my recipes are gone — restore the backup”. The data itself is not deleted (the old volumes are in the unit). **2026-10-25 (D47, controller `02daebe` + `1f9c863`):** every install has an identity, the unit records it, and every unit writer sets another install's unit aside whole (`backups/set-aside/<app>/<stamp>`, listed with „Visszaállítás”). Proven on 9202 (`audits/restore-truth-2026-10-25/after/`): the old unit set aside byte for byte, restored with its own password, the account back. | **VERIFY** — fixed on main, waits for the release | the next controller release | release the controller (Monday list); re-run `tools/unitprobe2.py tandoor` on the released image | CC |
| **R-1270** | Backup & restore | P2 | **A unit restore reports success while the app never came up.** Catalog sweep 2026-10-24: the controller logged „tandoor restored but health check failed: stack tandoor did not reach running state within 1m30s after restore”, and the status the restore page reads said `ok: true` — „A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.” (17:32:41; again in the unitprobe, 19:52:38). The household is told it worked (`audits/catalog-sweep-2026-10-24/tandoor/`, `_unitprobe/tandoor/record.json`). **2026-10-25 (controller `b680db8`):** a restore whose app does not reach running is never a success on any path; the household reads that the app has not started yet; the undo copy is kept; a late start turns it into a success. Proven on 9202 (`audits/restore-truth-2026-10-25/after/_pullprobe/`). | **VERIFY** — fixed on main, waits for the release | the next controller release | release the controller (Monday list); D49 asks whether to pour the old data back instead | CC |
| **R-1271** | Backup & restore | P2 | **„Use my kept data” loaded ANOTHER box's database — a future-dated off-site copy of the rehearsal box — under today's kept files.** Catalog sweep 2026-10-24: Tester4's off-site store holds copies from the 2026-10-20 rehearsal, whose box clock was set to 2026-10-28. On the new box the question said „Az adatbázist innen töltjük vissza: távoli mentés, 2026-10-28 07:39” for Paperless-ngx, Nextcloud and Immich, and loaded it: the seeded account / user / album was not there, and Immich never answered again (404 for 20 min). RomM, whose newest copy was its own („saját mentés, 2026-10-24 19:17”), got its item back. `07` §6.5 says the load is refused when the unit „was taken of another drive” — the rehearsal box's drive was another disk, so that check did not hold here (which field it compares: not measured). The household's own local unit stays (nothing deleted). Same future-dated copies as R-1206 (that row: retention only). Evidence `audits/catalog-sweep-2026-10-24/paperless-ngx/`, `nextcloud/`, `immich/`. **2026-10-25 (controller `bd9f4f7`):** cause — the off-site copy's date was the other box's snapshot time, uncapped, and „newer than every local copy” took it; the „another drive” check compares a mount path (the same on every box). Now a copy more than 10 minutes in the future never counts as the newest; alone it is offered, said and not recommended. Immich's 404 was the copy's old subdomain. Proven on 9202 (`audits/restore-truth-2026-10-25/after/_keptprobe/`). | **VERIFY** — fixed on main, waits for the release | the next controller release | release the controller (Monday list); re-run `tools/keptprobe.py` on the released image | CC |
| **R-314** | Backup & restore | P3 | **`StopAbandon` has no web route — a customer who telephones is served by a command line.** `--abandon-stop` exists on the controller binary (`cmd/controller/main.go:86`) and refuses rather than silently no-opping, which is right. But there is no handler: the only in-product way to cancel a countdown is the customer finding their recovery code (Scenario G cancels it at the moment the code proves they still have it). **An operator who is telephoned instead has to reach a shell on the customer's machine.** Used this session on the operator's ruling, container stopped first so the running controller could not overwrite `settings.json` from memory — a sequencing subtlety that is itself an argument for a route **-- 2026-10-08 design:** `audits/day-2026-10-08/design-R-314-279-177.md` — the box's own 14-day countdown has no operator door (the hub's 7-day phase does, decision 74); one door for R-314 + R-279: a fixed list of operator actions carried in the report reply. Question D1 on STATUS's decision sheet. **-- 2026-10-08 14:16 operator ruling D1 (`09` §3 decision 185):** yes — option A, a fixed list of operator actions carried in the box's report reply. | **VERIFY — 2026-10-09: DELIVERED (hub 0.144.0 + controller 0.304.0); `run_job fill-watch` proven live on the Tester 1 box (hub row „done — ran to its end"; control: the box log „checked 3 filesystem(s)"; an unknown action is refused 400). `abandon_stop` / `abandon_extend` NOT yet proven — no box has a deletion countdown; prove them on the first one.** **VERIFY — built on main 2026-10-08 (D1, decision 185), ships with tomorrow's controller + hub releases; closes after the live proof on 9202** (`abandon_stop` / `abandon_extend` through the hub's buttons). **READY (S) — NEW 2026-08-12, RANK 3** **2026-10-05 (burn-down night): NEEDS A DESIGN.** There is no operator door into a RUNNING controller (its HTTP surface is the household's session; the CLI runs in a separate process and cannot safely trigger a job in the running one). Same design as R-177 and R-279. | R-241, R-307 | An operator-authenticated POST that calls the same `StopAbandon`, so the telephone path and the code path converge | CC |
| **R-401** | Backup & restore | P3 | **Revisit the off-site integrity depth when a real store is LARGE — the default rests on ONE measurement, on ONE 134 MB store.** Controller v0.228.0 (R-399) made `--read-data-subset=100%` the default for every box. The whole justification is a single data point: `demo-hp`, 2026-08-30, 140 829 678 B / 2 651 blobs / 67 snapshots, structure 35.0 s vs 100% **39.2 s** — four seconds. Re-proven live 2026-08-31 at 38.7 s. **It does not extrapolate, and the reason is structural: the structure check's cost tracks the INDEX, a read-data run's tracks the DATA.** A 50 GB store is ~370x the data and this curve says nothing about it. **Nothing was invented from that one point** — no rotation schedule, no size threshold, no bandwidth budget — because four production designs in this project were specced against unvalidated mechanisms and all four were wrong. **`readDataSubsetRe` already accepts `n/m`**, so a rotating schedule (`1/7` on a different seventh each week) needs no parser work when the time comes; the missing input is a measurement on a large store, not code. **THE TRIGGER IS AN EVENT, NOT A DATE:** the slow-check WARN from v0.228.0 firing on any box (`integritySlowNoticeThreshold`, 5 min) — that line names the duration, the depth and this row. **WHAT HAPPENS IF NOBODY ACTS:** every box re-reads its entire store every week, however large it grows, and the first person to notice is a customer whose upload is saturated. **Whoever acts must also revisit `integrityCheckTimeout` (30 min)**, which is now the number a large store meets first. | **OPEN — WATCHING** | — | When the WARN fires: measure the curve on that store, then choose between a rotation (`n/m`), a size-conditional default, or leaving it. Do NOT choose from this row's numbers — they are the small-store case. | CC |
| **R-409** | Backup & restore | P3 | **Nothing in the product can vouch for the bytes of a restored recovery unit — the only hash record covers 0.002 % of it.** MEASURED on demo-hp 2026-08-31 against kimai's restored unit: `manifest.json`'s `checksums` object carries sha256 for `.felhom.yml` (2 235 B), `app.yaml` (488 B) and `docker-compose.yml` (2 195 B) — **4 918 bytes of a 213 231 242-byte unit**. The database dump (48 217 B) and the two named-volume tars (160 331 776 B + 52 845 056 B) — the recoverable data, 99.998 % of the bytes — have no recorded hash anywhere. **And nothing else supplies one:** restic 0.14.0's `restore --verify` is a size-and-mtime reconciliation (a size-and-mtime-preserving one-byte corruption of a 160 MB tar passed clean, red-proofed), and `restic ls --json` file nodes in 0.14.0 carry name, size, mode, uid/gid and three timestamps and **no content hash**. **So "the restore produced correct files" is currently unanswerable by any automated means.** **What is NOT claimed here:** `restic check --read-data-subset=100%` already proves the STORE's packs, and the config files that ARE hashed are the ones a wrong-content failure would be hardest to spot in. | **OPEN — MEDIUM** | R-87, R-361 | Cheapest fix, and it is already half-built: extend the capture's `checksums` to cover `db_dumps` and `volume_dumps` — R-361 already computes a canonical dump sha256 to prove itself, so the value exists at capture time. Then a restore-test has a real reference and R-87's narrow version becomes a content check rather than a completeness one. Evidence: `audits/SPIKE-restic-restore-test-2026-08-31.md` §Q2, §Q3. | CC |
@@ -183,6 +183,8 @@ stopping line that lies.
| **R-1256** | Backup & restore | P3 | **Restore edge cases (review round 2, HUNT-1 H4/H5/H6/H8):** (H4) an off-site restore whose definition write fails AFTER the compose was written restarts the app on the snapshot's compose (`internal/backup/offbox_reconstitute.go:979-986`; `cmd/controller/main.go:3318-3331`); (H5) a put-back whose stop fails goes on and can merge (`restore_crash_putback.go:155-158`; `restore.go:218` ignores `volume rm -f`), then removes the copy; (H6) a second-drive mirror can start between the web refusal and the restore's own record (`web/restore_wizard.go:226`; `opstatus.go:56-66`), and the install-time kept-data load checks only RestoreStatus/IsRunning (`api/kept_install.go:89-92`); (H8) an unused restore copy is never cleaned up. Static. | OPEN | — | one attended pass over the restore paths with these four as tests | CC |
| **R-1259** | Backup & restore | P3 | **The DooPlex→ep0 copy jobs: three edges and tomorrow's clock change (review round 2, HUNT-2 H6–H8, HUNT-4 H1).** The release copy keeps the newest 5 agent versions whether vouched or not, so five unvouched releases drop the vouched one from ep0 (`scripts/release-copy/felhom-release-copy:199,218,237`); its `rsync --delete` removes old versions during the transfer and mirrors a shrinking registry as deletions (`:190,237`); the hub-DB push has no lower age bound (`scripts/hub-db-backup/felhom-hub-db-backup:41,49-50`). VERIFIED on DooPlex: on 2026-10-25 the 02:30 CEST push runs at 00:30Z (`systemd-analyze calendar`) and the hub's 02:00 slot at 01:00Z (Go takes the repeated hour's second pass), so the push re-sends the 20:00 snapshot and the off-site hub DB is ~13.5 h old until 08:30. Operator data only. | OPEN | — | always keep the vouched agent; prune after a verified copy; a future-age bound; the push slot after the hub's in UTC | CC |
| **R-1274** | Backup & restore | P3 | **The restore refusal tells the household to „turn on remote backup, or connect a second drive” when the box has both and their first copy has not run yet.** Catalog sweep 2026-10-24, Grimmory and Calibre-Web: „Mentés most” makes the local copy only (`07` §8, by design), so the drive files had no copy yet; the refusal's sentence is `restore.refuse.files.none` (`felhom-controller/controller/internal/web/handlers.go:1835`) whatever the box has. The Hungarian sentence is frozen by `i18n_go_parity`, so the fix is a NEW key („the first copy is made in tonight's backup”) chosen when a tier is set up but holds nothing yet. | OPEN | — | The new key + the branch + a test | CC |
| **R-1278** | Backup & restore | P3 | **A volume app's reinstall does not offer its kept backup.** „Remove — keep my data” removes a volume app's named volumes and keeps only its backup; the kept-data question is about drive folders only (`07` §6.5, decision 36), so the reinstall starts empty and says nothing. Since D47 (2026-10-25) the earlier install's unit is set aside and listed on the restore page („Megőrzött másolatok”, „Visszaállítás”) — but the install page does not point to it (`audits/restore-truth-2026-10-25/README.md`). | OPEN | — | Design: at install, when a set-aside or removed-app unit of the app exists, say so and offer it beside a fresh start; a test | CC |
| **R-1279** | Backup & restore | P3 | **The `.fab` import removes a running app's volumes before its images are pulled — D48 does not cover it.** `felhom-controller/controller/internal/appexport/restore.go` removes the live volumes (`RemoveStackVolumes`, step 1) and pulls at the database start later; a bundle whose version cannot be downloaded then leaves the app gone (the bundle itself stays, so a later import works). Found reading every restore path for D48 (2026-10-25). | OPEN | — | Pull the bundle's images before step 1 (the D48 rule and its two sentences); a test with an unpullable image | CC |
| **R-91** | Backup & restore | P4 | Old 13 GB datastore copy at `/srv/pbs-felhom` on ep0's root disk **Checked from source 2026-10-05 (burn-down round 2):** Gate is long past (row waits on demo-felhom's first post-migration PBS backup, migration 2026-07-27). Last positive record of the copy: audits/CAMPAIGN-9-restore-proof-2026-07-28.md:759 'ep0 : /srv/pbs-felhom rollback copy intact (13G)'; CONTEXT.md:3666 still says it is 13 G of dead weight awaiting R-91. No later record of deletion found (grep srv/pbs-felhom across felhom.eu). Deleting is on ep0 (protected) and needs an operator word. | WATCHING | demo-felhom's first **post-migration** PBS backup | Delete once it lands; fix `CONTEXT.md:1018` same commit | CC |
| **R-164** | Backup & restore | P4 | **C2's chain: the DB volume tar cannot be dropped until a SOUND dump predicate exists.** The unit carries both a volume tar and a SQL dump; the restore uses **both** — the dump is authoritative and replayed *after* the tar so it WINS (F17), with only the DB service up (R-47) — `internal/backup/restore_unit.go:262-266`. Dropping the DB container's tar would halve DB-app units **and** close the R-127(b) initdb-skip password trap (restored PGDATA ⇒ `POSTGRES_PASSWORD` ignored). | **BLOCKED** — on the predicate | a dump-validity predicate that is not `accounts has rows` | **The obvious gate is DEAD, measured:** `ValidateDump` warns when the `accounts` table is empty, and that warning was **correct** — the live DB genuinely had 0 accounts, and seeding one stopped the warning and put the row in the dump. But **a fresh appliance legitimately has zero accounts**, so promoting that predicate to a gate would **block every new customer's first backup**. Order: (1) a sound predicate — dump vs **live** per-table counts, not an absolute expectation; (2) warn→gate; (3) tar-drop. **Until (1), the tar is load-bearing** — not because dumps are bad, but because nothing can yet prove one is good. Pairs with **R-127** | CC |
| **R-213** | Backup & restore | P4 | **Putting files back in place — the half the recovery screen deliberately does not do.** The screen (R-193, controller v0.200.0) unlocks the repository and LISTS what is in it; restoring is per-app and lives in the backups area, and the operator ruled the two separate on 2026-08-05: *a screen that unlocks and then offers to overwrite is two decisions wearing one button*. What is missing is the step after the listing — a customer who can now SEE their files still has to work out, per app, which restore to choose. **The operator named its requirement: a live-versus-backup comparison** — the customer must be able to see what would change before anything is overwritten | **DEFERRED** — operator ruling 2026-10-12 (S18, `09` §3 decision 213): designed after the first customers. Stays P4; re-dated: look again when the first paying household has run 30 days. **OPEN — not started, deliberately** | the comparison design (nothing exists for it yet) | After the first customers | Operator + CC |
@@ -206,7 +208,7 @@ stopping line that lies.
| **R-1190** | Backup & restore | P3 | **Key and retention candidates from the data-loss hunt — what is left:** (c) PLAUSIBLE: after a reuse-path reinstall the new PBS key K is never escrowed and nothing compares fingerprints (`felhom-pbs-apply:99`, `escrow_confirm.go:182-205`); (d) whole-guest off-site copies run before any escrow exists — a design question against `07` §6.1's Tier-3 gate (the operator's); (e) the escrow PUT is two statements; (f) the operator's off-site re-issue resets the provider password before saving it (hub `offsite.go:217-245`); (h) a unit restore can point an app back at an old drive and F5 then prunes the current drive's unit (`backup.go:1517-1574`) — (e), (f), (h) not re-read. **Moved out 2026-10-22 night:** (a) → R-1218, (b) → R-1219, (g) → R-1220. Evidence `audits/dataloss-hunt-2026-10-20/C1-keys-candidates.md`, `C1-delete-retention-candidates.md`, `audits/night-2026-10-22/P3-candidates/analysis.md`. | OPEN | — | (c) a read-only fingerprint compare on a reinstalled test box; (e), (f), (h) re-read, then unit tests; (d) to the operator at the next design review | CC |
| **R-1198** | Backup & restore | P3 | **A backup disk the family adds later is never offered as the place for the whole-box backup; the page keeps offering the disk that holds the apps' own data.** Rehearsal 2026-10-20 B6 #13 (`audits/rehearsal-2026-10-20/B6-failures/s13-second-drive/`): a 50 GB disk plugged in and initialised as „Mentolemez" → the backups page still offers only „Családi adatlemez — Kijelölöm"; `internal/web/backup_target_offer.go:101` `firstOfferableDrive` offers the FIRST eligible drive, never a choice, and the drive's card has no „use for backups". | OPEN | — | Offer every eligible drive (largest empty first) and let the household pick | CC |
| **R-1200** | Backup & restore | P3 | **A box installed in the afternoon gets no whole-box backup on its first night: the install's own backup starts the 24 h clock, so the family's whole first day reaches a whole-box copy only on the SECOND night (~38 h after install).** Rehearsal 2026-10-20 B5 (`audits/rehearsal-2026-10-20/B5-night/README.md`): Tester4's install backup at 12:09Z; at the 02:30–06:30Z window the agent answered „within cadence window" (`felhom-agent internal/localapi/server.go:1187`, `age >= tier.Cadence`, default 24 h). R-899 (closed) fixed the same shape for a daytime PRESS only. The other tiers did run that night (database copy, second drive, off-site — all PASS), so app data was covered; the whole-box (DR) tier was not. | **OPEN** — a design question: should the first scheduled night always run (like a press), or is one tier enough on night one? | — | decide; if „always run": treat the install backup like a press in the due rule, with a test on the first night | CC |
| **R-1206** | Backup & restore | P3 | **Backups written while the box's clock ran ahead stay „the latest" after the clock is fixed.** The rehearsal's scenario #10 (clock set to 2026-10-28) left off-site snapshots dated 2026-10-28; on 2026-10-21 the rebuilt box's recovery page listed every app's „Legutóbbi mentés: 2026. 10. 28. 07:39", and the full restore said „mentés: 2026. 10. 28. 07:37" — a date in the future (`audits/fixes-2026-10-21/P1-live/07-unlock-right.txt`, `11-nextcloud-full-restore.txt`). restic orders snapshots by their own time, so the future ones are „newest" to every retention rule for a week. NOT measured: whether the box's keep-daily prune then drops a real snapshot. Related R-1202, R-1204 (clock). | NARROWED — the NAS tier only. **2026-10-22 night (read, `backup/offbox_window.go:22-24, 130-137`):** on the Felhom off-site tier no real snapshot is lost — the guard refuses a prune while any snapshot is future-dated (R-822, by design). The NAS tier prunes without that guard: open. | — | A pure unit test with the policy simulator on the NAS tier and one future-dated snapshot; if a real one is dropped, give the NAS tier the same guard | CC |
| **R-1206** | Backup & restore | P3 | **Backups written while the box's clock ran ahead stay „the latest" after the clock is fixed.** The rehearsal's scenario #10 (clock set to 2026-10-28) left off-site snapshots dated 2026-10-28; on 2026-10-21 the rebuilt box's recovery page listed every app's „Legutóbbi mentés: 2026. 10. 28. 07:39", and the full restore said „mentés: 2026. 10. 28. 07:37" — a date in the future (`audits/fixes-2026-10-21/P1-live/07-unlock-right.txt`, `11-nextcloud-full-restore.txt`). restic orders snapshots by their own time, so the future ones are „newest" to every retention rule for a week. NOT measured: whether the box's keep-daily prune then drops a real snapshot. Related R-1202, R-1204 (clock). | NARROWED — the NAS tier only. **2026-10-22 night (read, `backup/offbox_window.go:22-24, 130-137`):** on the Felhom off-site tier no real snapshot is lost — the guard refuses a prune while any snapshot is future-dated (R-822, by design). The NAS tier prunes without that guard: open. **2026-10-25:** the kept-data choice no longer takes such a future-dated copy as the newest (R-1271, controller `bd9f4f7`); retention is unchanged — this row. | — | A pure unit test with the policy simulator on the NAS tier and one future-dated snapshot; if a real one is dropped, give the NAS tier the same guard | CC |
| **R-1212** | Backup & restore | P4 | **Two household texts on the backup path do not follow the box's language:** the share-restore summary (`SharesRestoreResult.FlashMessage`, `controller/internal/backup/shares_restore.go`) is built from Hungarian Go literals, and the off-site `LastError` for an interrupted run is the Hungarian literal „megszakadt futás (a vezérlő újraindult futás közben)" (`controller/internal/backup/backup.go`). An English household reads Hungarian there. Found by the night helper 2026-10-21. | OPEN | — | Bundle keys for both (rendered in the box language when written, R-557), with parity fixtures | CC |
| **R-1218** | Backup & restore | P2 | **A recovery ceremony done while off-site is switched off (or cleared) seals no off-site repository password, and the hub then demotes the GOOD older package to operator-only custody: the household's own recovery code no longer opens its off-site copy.** REAL in code (night 2026-10-22, read; split from R-1190 (a)): `web/escrow_handlers.go:217-224` stages the password only if `OffboxConfigured()` (`backup/offbox.go:695-710`), false when the target is cleared or disabled — although `ClearOffboxTarget` keeps the password file on purpose (`offbox_clear.go` header); the agent then seals no password (`cmd/felhom-agent/main.go:2981-3003`) and uploads sha ""; hub `SaveHostEscrow` sees `curSHA != ""` and moves the good package to retained custody; `OffsiteRecoveryOffer` reads "" as „covers no key” (`offbox.go:1855-1873`) and says nothing. Evidence `audits/night-2026-10-22/P3-candidates/analysis.md` §R-1190(a). | OPEN — proposed fix, not built (key-handling code: a fix needs a red-proof AND a live proof, and 9202 has no hub to prove it with) | — | Fix (~6 lines): stage when the password FILE exists (`PushOffboxPasswordForEscrow`, `offbox.go:800`, reads only the file). Red-proof: a web test that the stage runs with off-site cleared; a hub store test pinning „abc”→"" ⇒ superseded (and refuse that demotion). Live proof on a test box with a hub (demo-hp 9201) | CC |
| **R-1219** | Backup & restore | P2 | **During a recovery offer the backups page hides the „create your recovery code” ceremony (it would replace the key that opens the old off-site copy), but the global top bar still links to it and the start handler does not refuse.** REAL in code (night 2026-10-22, read; split from R-1190 (b)): `web/escrow_banner.go:23-35` ignores `recoveryOffer()`; `templates/backups_remote.html:144-160` hides it and its own comment names the harm (R-199); `escrow_handlers.go:180-245` starts anyway; reachable in offer shape (c), `offbox.go:1866-1870`. Evidence `audits/night-2026-10-22/P3-candidates/analysis.md`. | OPEN — proposed fix, not built (key-handling code) | — | Fix: the bar hides its link during an offer, and the start handler answers 409 with a Hungarian sentence; red-proof with a handler test; live proof with an offer state on a test box with a hub | CC |
@@ -257,7 +259,7 @@ stopping line that lies.
| **R-908** | Security & access | P3 | **An old Resend API key is still in the history of the `homelab-manifests` repository; it was replaced on 2026-06-29, but whether it is also revoked at Resend is unknown.** FOUND 2026-10-08 by the contact-mailer source search (`audits/mailer-source-2026-10-08/SEARCH.md`, „A side finding"): commit c9648cd (2026-02-05, „added mailer pod") put a key literal in the old `felhom-system/contact-mailer.yaml` comment; the file was deleted in ee93b50 but the history keeps it. Compared by hash only, never printed: it is NOT today's key (`Secret/resend-api`, rotated 2026-06-29, felhom.eu feea0606). If the old key still works at Resend, anyone with read access to that repository can send mail as felhom.eu. | **OPEN — owner: operator** **Note 2026-10-12 (security review):** gitleaks 8.24.3 with its default rules did NOT flag this key in the full history scan, so a scanner gate alone would not have caught it (R-986). | — | In the Resend console, check that the old key is revoked (revoke it if not); rewriting the repository's history is NOT needed once it is revoked | operator |
| **R-525** | Security & access | P4 | **[P3-LOW] FileBrowser has its own login; putting it behind the dashboard session (traefik forwardAuth or Quantum proxy auth) is a new mechanism nobody has measured.** Filed 2026-09-15 by the P1-fixes task (B.5). R-513 closed the default-password hole with a generated password; a household still has two logins. **What it needs:** a spike on a scratch guest — forwardAuth to the controller session, and what FileBrowser Quantum does with a trusted header. | **READY — rank P3-LOW; owner: CC (spike)** **Re-ranked 2026-10-03: P3->P4: comfort feature needing a new unmeasured mechanism; the default password hole is closed.** | — | — | CC |
| **R-925** | Security & access | P1 | **`manifests/felhom.secret.yaml` carries REAL secret values, and the Gitea repo that holds it is readable by anyone on the internet with no login.** FOUND 2026-10-09 by a background security review of the legal-pages commit, which flagged the served `<!-- source -->` comments; chasing what those comments pointed at found this instead. **MEASURED, in this order:** (1) `scripts/manifest_bearer_gate.py` itself prints `manifests/felhom.secret.yaml:39 KNOWN-BACKLOG committed secret 65cee3c4…7a86`; (2) the file holds **live-shaped values, not placeholders** — `SECRET_KEY` (69 chars), `SUPERUSER_EMAIL`/`SUPERUSER_PASSWORD` (18), Umami `APP_SECRET` (66) and `POSTGRES_PASSWORD` (34), and a `username`/`password` pair (18); values were never printed, only measured by length; (3) `gitea.dooplex.hu` resolves to **37.191.56.193**, the same public address as the website; (4) an **anonymous** `curl` returns HTTP 200 and 1686 bytes for that exact file, and 200 for `hub/internal/store/store.go` and `manifests/hub.yaml`; (5) **the off-network control**, which is what makes (4) mean anything — fetched from outside the operator's network entirely: a real path returns the file's first line, a nonsense path returns 404, so the 200 is genuine anonymous read from the internet and not a LAN-only ACL. **This contradicts the project's own written model**: `documentation/runbooks/secrets.md:3-4` says *“Secret values are never committed to git. The manifests in `manifests/` carry only placeholders + comments that point here.”* That promise is false today, which is the P1 wording in this register's own scale — see the ranking note below. The same runbook already states the rule that governs the fix: *“The git-history copy stays alive until the value is ROTATED — de-git alone kills nothing.”* **Known neighbours, none of which cover this:** R-887 records that an outside crawler walks the public Gitea pages and leaves *“Gitea's exposure to the crawler”* as the operator's undone item; R-580 still owes a Gitea admin token rotation. Neither says a secret file is world-readable. **Why P2 and not P1, stated so the operator can overrule it:** the exposed credentials guard **analytics and an undeployed healthchecks instance, not household data** — measured: `umami-db` is a **ClusterIP** service on 5432 with no external IP, so the Postgres password is not reachable from the internet; the realistic harm is session forgery against the public `stats.felhom.eu` via `APP_SECRET`, and reuse of those passwords anywhere else. No customer box, hub token or escrow key is in this file. **CC did NOT change anything**: making the repo private could break the public day-0 path (the installer is fetched from a public tag, R-110), and rotation plus repository visibility are operator decisions on production infrastructure. **PARTLY FIXED 2026-10-09, and RE-RANKED P2 -> P1 on what it turned out to be.** The exposed value was not an analytics password: it was the **Gitea `admin` account password** (`is_admin: true`; `/api/v1/admin/users` answered 200), published in a repo `gitea.dooplex.hu` serves anonymously to the internet. That is push access to every repo — including the one whose `website/` is git-synced live and whose `scripts/` is published by tag to **every new box installer** (R-110), i.e. a supply-chain path onto customer hardware. My first ranking said P2 because I had only measured the analytics blast radius; the credential test is what corrected it. **DONE (CC, operator-instructed):** (1) `umami-config` `APP_SECRET` + `POSTGRES_PASSWORD` rotated — the password changed **inside Postgres** too (`ALTER USER`), because the env var is only read at first init; verified by a real beacon returning 200 with a bogus-site-id control still returning 400. (2) `healthchecks-config` `SECRET_KEY` + `SUPERUSER_PASSWORD` rotated (nothing consumes them — there is no healthchecks Deployment). (3) `gitea-creds` no longer holds the admin password at all: it holds a **scoped token** (`read:package` + `read:repository`). **Both scopes are load-bearing and the second was found by breaking it** — a package-only token made the hub log `Template fetch: unexpected status 403`, because the template fetcher reads a raw file out of the `felhom-controller` repo. (4) The Gitea **admin password** was changed and `gitea-system/gitea-admin` updated; **verified: new password 200, old published password 401 — the leak is dead.** (5) The file is removed from git and `.gitignore`'s `*secret*` now applies to it; `manifest_bearer_gate.py`'s `KNOWN_BACKLOG` carve-out is gone, red-proofed with a decoy (exit 1 with, 0 without). **AN INCIDENT CAUSED BY THE FIX, recorded because it is the useful part:** the `rollout restart` needed to pick up the new umami secret put umami into **CrashLoopBackOff and took `stats.felhom.eu` down (503)**. Cause was not the rotation — at `memory: 512Mi` the pod runs for months but **cannot restarLine truncated
| **R-1243** | Security & access | P2 | **The hub's Longhorn backups sit unencrypted in the NFS folder any DooPlex pod can read (R-1241) — the two there now (2026-10-18, 2026-10-23) hold every household's Cloudflare and tunnel tokens in plaintext, because they predate hub 0.168.0.** Measured 2026-10-23 night (`audits/hub-token-seal-2026-10-23/` §3): the hub volume is `encrypted=false` and in Longhorn's `default` backup group (daily 04:00 + weekly Sunday 05:00, retain 1 each); the hub's own copy to ep0 is encrypted at rest (16/16 snapshots `crypt-mode: encrypt`, key on DooPlex's root disk and on paper) and in transit (SSH tunnel + pinned PBS TLS), every 6 h, restore-tested weekly. DECIDE: (a) an encrypted Longhorn volume for the hub (~5–10 min downtime, a homelab StorageClass + key in the cluster) or (b) take the hub volume out of the Longhorn backup — ep0's copy is the backup; the access log then has no backup until it rides the ep0 push. **CC's pick: (b).** And: delete the two plain copies (the operator's call; with (b) they never rotate out). | **VERIFY** — **2026-10-24: D40 put into effect** (operator ruling 07:22): the hub PVC `hub-data` carries `recurring-job-group.longhorn.io/default: disabled` (felhom.eu `3363ffa8`, ArgoCD sync of the PVC only) and the Longhorn volume `pvc-486c9809…` reads `disabled` too (labelled live to match, unchanged after 2 min); the NFS folder holds no copy of the volume (no `pvc-486c9809*` directory, no backup config naming it; Longhorn lists no backup volume for it; 53 other volumes backed up this morning). Closes after the dated check: tonight's 04:00 job makes no new hub backup while the others run. WAITING-ON-OPERATOR | the operator's pick; a homelab change for either option | Operator: pick (a) or (b), and say whether to delete the two plain copies after hub 0.168.0 is live | CC (the dated check) |
| **R-1243** | Security & access | P2 | **The hub's Longhorn backups sit unencrypted in the NFS folder any DooPlex pod can read (R-1241) — the two there now (2026-10-18, 2026-10-23) hold every household's Cloudflare and tunnel tokens in plaintext, because they predate hub 0.168.0.** Measured 2026-10-23 night (`audits/hub-token-seal-2026-10-23/` §3): the hub volume is `encrypted=false` and in Longhorn's `default` backup group (daily 04:00 + weekly Sunday 05:00, retain 1 each); the hub's own copy to ep0 is encrypted at rest (16/16 snapshots `crypt-mode: encrypt`, key on DooPlex's root disk and on paper) and in transit (SSH tunnel + pinned PBS TLS), every 6 h, restore-tested weekly. DECIDE: (a) an encrypted Longhorn volume for the hub (~5–10 min downtime, a homelab StorageClass + key in the cluster) or (b) take the hub volume out of the Longhorn backup — ep0's copy is the backup; the access log then has no backup until it rides the ep0 push. **CC's pick: (b).** And: delete the two plain copies (the operator's call; with (b) they never rotate out). | **VERIFY** — **2026-10-24: D40 put into effect** (operator ruling 07:22): the hub PVC `hub-data` carries `recurring-job-group.longhorn.io/default: disabled` (felhom.eu `3363ffa8`, ArgoCD sync of the PVC only) and the Longhorn volume `pvc-486c9809…` reads `disabled` too (labelled live to match, unchanged after 2 min); the NFS folder holds no copy of the volume (no `pvc-486c9809*` directory, no backup config naming it; Longhorn lists no backup volume for it; 53 other volumes backed up this morning). Closes after the dated check: tonight's 04:00 job makes no new hub backup while the others run. WAITING-ON-OPERATOR **2026-10-25 dated check DONE (read-only, the restore-truth session):** `kubectl -n longhorn-system get backups.longhorn.io` lists 106 backups made today (the newest 04:08:28Z — the job ran) and none of `pvc-486c9809` — the hub volume is out of the Longhorn backup as D40 meant. | the operator's pick; a homelab change for either option | Operator: pick (a) or (b), and say whether to delete the two plain copies after hub 0.168.0 is live | CC (the dated check) |
| **R-779** | Security & access | P4 | **[P3-LOW] Part A's "two outside addresses seen as two" is proven through the simulated tunnel only; on the REAL tunnel the second outside address (ep0, one request allowed) was refused by Cloudflare's edge with 403 and never reached the box.** Measured 2026-10-01 19:51 UTC (`audits/visitors-2026-10-01/A/L2-demo-hp-real-tunnel.txt`): no log line on demo-hp; demo-hp's box has no geo restriction in its settings, so a Cloudflare ZONE rule (country or bot, not read) refused a German datacenter address. DooPlex's own address on the real tunnel was seen as itself. **Needs:** one sign-in from a second Hungarian address (the operator's phone off wifi) while DooPlex is locked out — 2 minutes; and say which Cloudflare rule refused ep0. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (a phone), CC reads the logs** **Re-ranked 2026-10-03: P3→P4: a proof gap on the real tunnel; operator-only follow-up.** | — | — | CC + operator |
| **R-904** | Security & access | P4 | **Cloudflare can read every household's app traffic; replacing it with our own relay is a later item.** Facts (reviewer discussion 2026-10-08; `01`): app traffic and the dashboard reach the box through the Cloudflare Tunnel (`01` §5 trust table, rows end-user ↔ apps and customer ↔ controller UI; §7), and the tunnel's public end is Cloudflare's edge, where TLS ends — so Cloudflare can technically read that traffic (the FAQ says so since 2026-10-08, R-900; „TLS ends at the edge" is not written in `01` — add it there). What Cloudflare gives today, free: inbound reach with no router setup, the CGNAT answer (`01` §4, §7); certificates (`01` §7, the free tier covers one level below a zone); the geo-WAF the hub enforces (`01` §5 last row, §7); flood protection (not in `01`). The alternative named: our own EU relay over WireGuard with TLS passthrough by SNI, certificates on the box, the geo-block on the relay. Its costs: one more machine the operator keeps up, and a single point of reach for every box; weaker flood protection; about a week of work after a spike. Operator ruling 2026-10-08 09:07 (`09` §3 decision 184): a later item. | **DEFERRED** — **2026-10-10: the asked sentence is in `01` §7** („TLS ends at Cloudflare's edge …”); the spike stays deferred. **DEFERRED — after the first customers (operator ruling 2026-10-08 09:07)** | — | A spike after the first customers (the relay's reach, cost and flood behaviour, measured) | operator |
| **R-1040** | Security & access | P4 | **Until the household makes Sonarr's or Radarr's first admin, any container on the box can read the app's API key.** Measured on the bench 2026-10-14 with the R-1030 seed in place: `AuthenticationRequired=Enabled`, `AuthenticationMethod=None` — `/initialize.json` from another container answers 200 with `apiKey`. Outsiders are blocked by the box's setup gate in that window; a compromised other app is not. | **OPEN** — filed 2026-10-14 | — | Seed the API key at install (`SONARR__AUTH__APIKEY`, measured to work) so the box knows it, and close the window by telling the household in the first steps; or seed a Forms user | CC |
@@ -397,8 +399,7 @@ stopping line that lies.
the R-row. Duplicating them here would create the second source this design avoids. -->
| item | due (UTC) | what to measure |
|---|---|---|
| R-232 | 2026-10-25 | the 03:14 local restic sweep (`dooplex-backup`, `pgdump` repo) took a snapshot of `/mnt/5_hdd/backup/postgresql/dumps` that holds a folder with `SUCCESS` from 2026-10-24 (the CronJob's 00:00/06:00 local runs on `postgres-superuser`), and `PostgresqlDumpStale` / `DooplexGiteaOffsiteStale` did not fire overnight |
| R-1243 | 2026-10-25 | no new Longhorn backup of the hub volume pvc-486c9809 after the 02:00Z daily job while the other volumes got theirs; detail in the R-1243 row |
| R-232 | 2026-10-26 | the 03:14 local restic sweep (`dooplex-backup`, `pgdump` repo) took a snapshot of `/mnt/5_hdd/backup/postgresql/dumps` that holds a folder with `SUCCESS` from 2026-10-25 (the CronJob's 00:00/06:00 local runs on `postgres-superuser`), and `PostgresqlDumpStale` / `DooplexGiteaOffsiteStale` did not fire overnight |
| R-435 | 2026-10-27 | Tester 1's weekly off-site clean-up ran inside a hub window with no drop alarm (box log + mailbox); detail in the R-435 row |
| R-1183 | 2026-10-26 | every hub daily job ran ONCE on 2026-10-25 (hub log + one snapshot file per slot — four slots since hub 0.167.0, D34, the 02:00 one not twice); detail in the R-1183 row |
| R-1228 | 2026-10-30 | D35 (yes, after the release week): start moving the website + installer to Cloudflare; detail in the R-1228 row |
+4
View File
@@ -5,6 +5,10 @@ Moved here byte for byte on 2026-10-14 (night session), newest first, so that `S
**Updated 2026-10-13 (night): demo-hp, demo-felhom and Tester 1 run agent 0.155.0; demo-felhom runs controller 0.310.0, the other two 0.309.0 until the floor is raised. The open-items list is at 127. Reports: `REPORT-poweroff.md`, `REPORT-plugmeter.md`, `REPORT-usenet-2026-10-12.md`, `REPORT-newapps-2026-10-12.md`, `REPORT-disk-evening-2026-10-12.md`, `REPORT-answers-2026-10-12.md`, `REPORT-m3-2026-10-11.md`, `REPORT-redesign-2026-10-10.md`, `REPORT-testers-2026-10-10.md`, `REPORT-register-shrink-2026-10-10.md`, `REPORT-new-apps-2026-10-10.md`, `REPORT-release-2026-10-10.md`, `REPORT-break-the-circle-2026-10-09.md`, `REPORT-dooplex-survival-2026-10-09.md`, `REPORT-day4-2026-10-09.md`.**
## Moved from STATUS on 2026-10-25 (Sunday)
**Updated 2026-10-25 (night).** 65 catalog apps walked on a fresh box: 52 work, 10 with a problem, 0 broken; worst: a restore after a reinstall breaks an app (R-1269).
## Moved from STATUS on 2026-10-25 (night)
**Updated 2026-10-24 (night, unattended fix run).** Four restore and backup holes are fixed on main (three proven on test boxes); BYO hosts keep their SSH settings (D43 = A). Nothing is released — see Monday's list.