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
gates / gates (push) Successful in 13m44s
This commit is contained in:
1 parent
67053335d1
commit
9647f1d158
58 files changed
+1834
-14
No files matched your search
+10
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
+34
@@ -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"
|
||||
}
|
||||
+20
@@ -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"
|
||||
|
||||
+8
@@ -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
|
||||
+8
@@ -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
|
||||
+4
@@ -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"
|
||||
}
|
||||
+20
@@ -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"
|
||||
|
||||
+9
@@ -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
|
||||
+9
@@ -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
|
||||
+4
@@ -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
|
||||
+27
@@ -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"
|
||||
}
|
||||
Whitespace-only changes.
+9
@@ -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
|
||||
+9
@@ -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
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
761d5637752bf2695aaff064bcd7973fd5301bff2536e067a762e348a58cfad6 ./compose/.felhom.yml
|
||||
ad9754c68958c76847c891b0b106d276ddc532788642c6c9cf861ca3e05fb91d ./compose/app.yaml
|
||||
851523d2779a6739bd464fd2dc415a191164afcd7aae9d195fbc482a28ec8b25 ./compose/docker-compose.yml
|
||||
34699a9f0c9c642f80523e8bbee7127d699373590cfa977b626dfb110f9dbf4d ./manifest.json
|
||||
+13
@@ -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': [],
|
||||
+22
@@ -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"
|
||||
}
|
||||
+7
@@ -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"
|
||||
}
|
||||
}
|
||||
+126
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+126
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+126
@@ -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
|
||||
+6
@@ -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])
|
||||
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user