Controller v0.314.0 released under decision 250: floor 0.314.0 raised by CC's token at 13:11 UTC after the three test boxes
gates / gates (push) Successful in 6m45s
gates / gates (push) Successful in 6m45s
- 09 §3 decision 253: a restore puts the setup gate back as it was (R-1057 P2); 07 (what a per-app backup carries); 15 §6 (Prowlarr links itself; Plex by address). - Evidence audits/gate-restore-2026-10-15/: 9202 live proofs (R-1057, R-1058), the release (per-customer floors, Tester 1 rc.1 -> 0.314.0 by itself, box walk, global floor, read-back), Plex fake-code check on 9201, red-proofs. - Register: R-1023, R-1053, R-1057, R-1058 closed; R-1061 filed (P4); R-1060 narrowed; R-1052 blocked (customer creation is session only). STATUS, CONTEXT, the build skill's release steps, Prowlarr's website card. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -25,6 +25,14 @@
|
||||
> while a release is ahead. CC has no scoped route to the global floor (R-1051) — only the operator page with the
|
||||
> operator's password. A box on an `-rc` tag is never moved by the floor (R-1053).
|
||||
|
||||
> **2026-10-15 (evening, later) — controller v0.314.0, the first release under decision 250.** The floor 0.314.0 (MinAgent
|
||||
> 0.155.0) was raised at 13:11 UTC by CC's token (`HUB_TOKEN` in `~/.config/credentials`, not `~/.felhom-hub/cc-token`)
|
||||
> after Tester 1, demo-hp and demo-felhom ran it through per-customer floors and the box walk's checks were green; the
|
||||
> per-customer floors were cleared after. `09` §3 decision 253: a restore puts the setup gate back as it was (R-1057).
|
||||
> A box on `X.Y.Z-rc.N` is below `X.Y.Z` for the floor (R-1053; a pre-0.314.0 rc still needs a hand move). Prowlarr's
|
||||
> start runs the wiring (R-1058). Plex by address (R-1060; the real claim waits on the operator's code). R-1052 (new-box
|
||||
> proof) is blocked: creating a customer is session only (decision 252). Evidence `audits/gate-restore-2026-10-15/`.
|
||||
|
||||
> **2026-10-15 (evening) — Prowlarr in the live catalog (catalog `73b45a6`, R-1048 closed).** Config volume only,
|
||||
> setup gate, `AuthenticationRequired=Enabled` + `AnalyticsEnabled=False` seeded by the template's entrypoint on the first
|
||||
> start. The box links it to Sonarr/Radarr only in a wiring run, and a run starts when a `media_library` app comes up —
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# REPORT — a restored app never comes back open; test builds move with the floor; Prowlarr links itself; the first release under the new rule (2026-10-15)
|
||||
|
||||
Written in ASD-STE100 Simplified Technical English. Design read first: `09-update-architecture.md` §3 decisions 46–49 and
|
||||
245–253, `07-backup-architecture.md`, `15-media-library.md` §6. Evidence: `documentation/audits/gate-restore-2026-10-15/`.
|
||||
|
||||
## What waits on you
|
||||
|
||||
1. **Plex on demo-hp:** open plex.tv/claim and save the code to `~/.felhom-demohp/plex-claim` on DooPlex (`chmod 600`).
|
||||
A watcher claims it through the dashboard at once and records the result. If you do nothing: that Plex stays locked
|
||||
(safe), and the Plex fix has no real-claim proof (R-1060).
|
||||
2. **A throwaway customer for the new-box proof (R-1052).** Make it in the hub in your browser, or name one I may reuse.
|
||||
Since hub 0.153.0, a customer can be created only from your browser session (decision 252), so my token cannot do it.
|
||||
If you do nothing: nobody has seen a brand-new box reach the floor by itself.
|
||||
|
||||
## 1. A restored app: closed until the household is set up? — Yes. Proven.
|
||||
|
||||
- The per-app backup now records the setup gate: closed or open, and by whom.
|
||||
- A restore puts the gate back as it was:
|
||||
- closed then → closed now;
|
||||
- open then → open;
|
||||
- no record (a backup made before 0.314.0) → closed until the app's own check or „Kész" opens it.
|
||||
- Never open by default.
|
||||
- **Live proof on scratch guest 9202:**
|
||||
- Prowlarr was installed. No account was made. The update took the app's backup. The app was removed, keeping its
|
||||
backups, and then restored.
|
||||
- Then a stranger got the gate: `/ping` 401, `/` 302 to the gate page, `/initialize.json` 401.
|
||||
- The household got through: 200.
|
||||
- The box's log says: „restored with its setup gate CLOSED — it was closed when the backup was taken".
|
||||
- „Kész, beállítottam" then opened it.
|
||||
- The same walk on the old code gave the stranger 200.
|
||||
- **Tests:** ten deliberate breaks of the code; each one made a test fail.
|
||||
- **Plex:** it follows the same rule. A unit test proves it with Plex's gate shape. I did not restore a live Plex.
|
||||
|
||||
## 2. Did Tester 1 leave the test build by itself? — Yes, from the new test build.
|
||||
|
||||
- **It could not leave `rc.4` by itself.** The floor check runs in the code the box runs, and `rc.4` has the old check.
|
||||
- So I put Tester 1 on `0.314.0-rc.1` by hand. Then the floor 0.314.0 moved it to the release by itself, at 13:09:38 UTC:
|
||||
„update successful (0.314.0-rc.1 → 0.314.0)".
|
||||
- It moved at its next regular report, 14 minutes after the floor was set. The other two boxes moved within 13 seconds.
|
||||
That difference is row R-1061 (P4).
|
||||
|
||||
## 3. Prowlarr: linked with no button? — Yes.
|
||||
|
||||
- On 9202: Sonarr first, then Prowlarr. The box ran its link step when Prowlarr came up. Sonarr appeared in Prowlarr's
|
||||
Apps about 40 seconds later. A second run wrote nothing („unchanged"). Prowlarr's own connection test: 200.
|
||||
- The catalog text and the website card no longer mention the button (catalog `e67aad4`).
|
||||
|
||||
## 4. The release
|
||||
|
||||
- **Version:** controller **0.314.0**, MinAgent 0.155.0. No golden (decision 250).
|
||||
- **Three test boxes:** each got a per-customer floor of 0.314.0 through my token (the normal update path).
|
||||
- demo-hp and demo-felhom ran 0.314.0 within 13 seconds.
|
||||
- Tester 1 followed at 13:09:38 UTC, by itself.
|
||||
- **Box walk checks, all three green:**
|
||||
- https-only: every public name redirects to https;
|
||||
- 0 unhealthy, 0 restarting and 0 stopped containers;
|
||||
- the controller is healthy, and the dashboard answers 200.
|
||||
- **Global floor raised by CC with its token at 13:11:04 UTC:** 303 `floor_set`. Read back from the hub database (with
|
||||
its WAL): floor `0.314.0`, declared MinAgent `0.155.0`. The three per-customer floors were cleared afterwards.
|
||||
demo-hp's old 0.311.0 floor was already gone.
|
||||
- **Plex (R-1060):** every call to Plex now goes to the container's address. On 9201 a fake code through the dashboard
|
||||
was taken as local: Plex asked plex.tv and got 500 after 938 ms. Before the fix it was 401 in 0 ms. The real claim
|
||||
waits on your code.
|
||||
- **A new box reaching the floor (R-1052):** NOT done. It needs a throwaway customer (see above).
|
||||
|
||||
## 5. Wrong claims in the prompt; commits; register; STATUS
|
||||
|
||||
**Claims in the prompt that turned out wrong:**
|
||||
|
||||
1. „Tester 1 must leave `rc.4` by itself": impossible. `rc.4` runs the old floor check (§2).
|
||||
2. „CC's token (`~/.felhom-hub/cc-token`)": the token is `HUB_TOKEN` in `~/.config/credentials`. The path does not exist.
|
||||
3. „Claimed as a throwaway customer … customer deletion is session only": customer *creation* is session only too
|
||||
(decision 252). CC cannot start R-1052 at all.
|
||||
4. „Every controller call to Plex uses the host name `plex` (four calls)": there are five. The fifth is the general
|
||||
health check's `GET /identity`. Plex answers that page to anyone (200), so I left it as it is.
|
||||
|
||||
**One slip of my own:** the previous task's brief said „No Co-Authored-By", but my commits that day still carried that
|
||||
line (felhom.eu `2f1f6c80`, `02be5006`, `0cd9fa32`, `fb2e6b69`, `d23df1a2`; catalog `73b45a6`). The commits of this task
|
||||
do not carry it.
|
||||
|
||||
**Commits:**
|
||||
|
||||
- felhom-controller: `17863fe` (code + CHANGELOG + README + REUSE), `97c992a` (a missed test file), `85f65c7`
|
||||
(CONTEXT, REPORT).
|
||||
- app-catalog-felhom.eu: `e67aad4` (Prowlarr's text).
|
||||
- felhom.eu: this commit — decision 253 in `09`, `07`, `15` §6, the skill's release steps, the register, STATUS, CONTEXT,
|
||||
the evidence.
|
||||
- Drill catalog (test only): `fd4b9f1`, `c42d92b`.
|
||||
|
||||
**Images:** `felhom-controller:0.314.0-rc.1` (test) and `felhom-controller:0.314.0` (release).
|
||||
|
||||
**Register:** before **157** · opened **1** (R-1061) · closed **4** (R-1023, R-1053, R-1057, R-1058) · after **154**.
|
||||
R-1060 narrowed (the real claim). R-1052 blocked on you.
|
||||
|
||||
**STATUS.md:** 60 lines. **Not-walked claims:** 35 of 55 (no change).
|
||||
|
||||
**Teardown:**
|
||||
|
||||
- Machines:
|
||||
- 9202 is back on the live catalog, and its test mirror is deleted. It runs 0.314.0, with no test apps left.
|
||||
- 9201: no test apps left. Plex still waits for its claim.
|
||||
- Tester 1 runs 0.314.0. Its media rig was kept.
|
||||
- Hub: only the floors changed. The per-customer floors are empty again.
|
||||
- Nothing ran on ep0.
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
**Ready for the first real tester (Tester-2): yes. Tester 2 (a laptop) is off; nothing was sent to it.**
|
||||
|
||||
**Updated 2026-10-15 (evening).** Agent 0.156.1 + its bundle on all three boxes; controller 0.313.0 is the floor (vouched golden 0.313.0) — demo-felhom and demo-hp run it, Tester 1 still runs the test build rc.4 (R-1053); hub 0.153.0 (access tokens — CC uses its own token now, not your password). Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day:
|
||||
**Updated 2026-10-15 (evening).** Agent 0.156.1 + its bundle on all three boxes; controller 0.314.0 is the floor since 13:11 UTC — raised by CC with its own token after all three test boxes ran it (the first release with no golden; vouched golden 0.313.0); hub 0.153.0 (access tokens — CC uses its own token now, not your password). Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day:
|
||||
`documentation/backlog/STATUS-HISTORY.md`. Reports: `REPORT-night-2026-10-14.md`, `REPORT-immich-rollout-2026-10-14.md`, `REPORT-seo-felho-tarhely-2026-10-14.md`, `REPORT-immich-repair-2026-10-14.md`, `REPORT-power-cut-fixes-2026-10-14.md`, `REPORT-night-power-2026-10-13.md`, `REPORT-poweroff.md`.
|
||||
|
||||
## What works
|
||||
- A restored app comes back exactly as safe as it was at the backup: closed to strangers until its first account is made (0.314.0, proven on the scratch box). A box on a test build now moves with the floor (Tester 1 did, by itself).
|
||||
- Scripts use the hub with a scoped token, not your password (hub 0.153.0): CC sets the floor with its own token; no token can see a secret, delete anything, or send, make or show a household's password or link. Your new hub password is yours alone (changed 2026-10-15).
|
||||
- Every public name of the three test boxes answers only over https: plain http gets a 301 to the same https address, and browsers are told to remember it for one day (29 names checked, 2026-10-14 night).
|
||||
- Media apps work together on one drive: a download reaches Sonarr/Radarr as one file, no copy; the download apps see only their own folder (live catalog, proven on demo-hp).
|
||||
@@ -15,7 +16,7 @@
|
||||
- A real power cut on demo-hp: it started by itself after all 8 cuts of the night (with the BIOS set), every app back in ~4 min; stale and recovered mails true and paired.
|
||||
- A page for people searching „felhő tárhely" is live (/sajat-felho-tarhely, /en/own-cloud-storage): what cloud storage is, what to do when Google or iCloud fills up, a five-year cost table with prices read 2026-10-14, and who should NOT buy this.
|
||||
- The machine estimator is public (/gepigeny, /en/sizing), with figures from two machines (N100 and HP t740; raised only, 2026-10-14).
|
||||
- 65 apps in the catalog. New today: Prowlarr — you enter your download sources once, and Sonarr and Radarr get them (the website lists 63 apps — R-1036). OS updates, the kernel lane too, run at night.
|
||||
- 65 apps in the catalog. New today: Prowlarr — you enter your download sources once, and Sonarr and Radarr get them, linked by the box with no button (the website lists 63 apps — R-1036). OS updates, the kernel lane too, run at night.
|
||||
- The hub is closed again after the 2026-10-12 security review; one box cannot read another's data.
|
||||
|
||||
## What is broken
|
||||
@@ -23,10 +24,7 @@
|
||||
- DooPlex: a failed disk with its alarms silenced (R-934); CI jobs still get lost (R-887); something sets the shared
|
||||
checkout to „bare" (R-935); a Longhorn volume at 91 %.
|
||||
- Tester 2 has never made an off-site check (R-862). The scratch box cannot read the drill catalog (R-992).
|
||||
- A removed app that is restored from a backup made BEFORE its first sign-in comes back open, with no account (R-1057).
|
||||
Prowlarr installed after Sonarr/Radarr needs one press of „Bekötés most” to link; the app's text says so (R-1058).
|
||||
- **Plex cannot be claimed through the box on 0.313.0:** Plex refuses the code because of how the box addresses it (R-1060).
|
||||
A new Plex stays locked behind its gate. Safe, but unusable until a controller fix.
|
||||
- Plex claim: fixed in 0.314.0 and checked with a fake code; the real claim on demo-hp waits on your code (R-1060).
|
||||
|
||||
## What we work on
|
||||
- The power-cut findings still open (R-1004, R-1005, R-1010, R-1012, R-1013).
|
||||
@@ -46,6 +44,8 @@
|
||||
| D8 | R-206 | Install DooPlex's Docker settings from git (restarts Docker), and prune the build cache weekly: yes or no? | **Yes, in a quiet window** | — | a rebuild of DooPlex still has the settings in git | — |
|
||||
|
||||
## Waiting on you — do
|
||||
- **Plex on demo-hp: open plex.tv/claim and save the code to `~/.felhom-demohp/plex-claim` on DooPlex (`chmod 600`).** A watcher claims it through the dashboard at once. If not: that Plex stays locked (safe) and the fix has no real-claim proof (R-1060).
|
||||
- **Make a throwaway customer in the hub (browser only), or name one I may reuse,** for the new-box proof (R-1052). If not: nobody has seen a brand-new box reach the floor by itself.
|
||||
- **Hub 0.153.0 (your rulings 13:20 + R-1059 „A"): no token can resend a claim code, create or edit a customer, make a passphrase or a link, or re-issue an off-site password.** Live since 13:39 (checked with CC's token: all six refused).
|
||||
- **Remove the test server „plex-k6” from your Plex account** (plex.tv → Settings → Authorized Devices). The claim test passed and the server is gone from Tester 1. If not: a dead server stays listed in your Plex apps.
|
||||
- System poster needs a refresh: every public name answers only over https (http → 301).
|
||||
|
||||
@@ -886,6 +886,8 @@ both. Files written under different pins (a failed leg) make the unit `mixed`.
|
||||
|
||||
**[DESIGN] What a restore keeps from the app it replaces (controller v0.276.0, R-697).** The restore writes a fresh `app.yaml` from the unit — env, locked fields, and the pin from the unit's definition — but keeps the records of the app's life on this box: the household's `desired_state`, the kept pre-conversion copies (`conversion_copy`, `earlier_conversion_copies` — a restore does not remove the volume, so it must not forget it), and the ladder history (`failed_update_step`, `last_update_undone`, `last_auto_update`). Not kept: `installed_images` (what ran before, maybe another version). **A drive move is not a restore:** it changes `HDD_PATH` and nothing else (R-700 — before v0.276.0 it dropped the pin, and the syncer then gave the app the catalog's newest version at its next start). Pinned by `internal/stacks/r700_records_carried_test.go`.
|
||||
|
||||
**[DESIGN] The setup gate travels with the backup (controller v0.314.0, R-1057, `09` §3 decision 253).** The recovery unit's `app.yaml` carries `setup_gate:` (state, and who opened it) as it stood at the backup — the only life record the unit carries, because it decides who may reach the restored data. The restore's gate step runs inside the same write, before anything starts: closed then → closed now (the traefik gate file first, also over an installed app); open then → open; a unit with no record (made before v0.314.0) of a removed gated app → closed until the app's probe or the household's „Kész" opens it. Never open by default. An ungated app's unit bytes are unchanged (the checksum-skip guard). Code: `internal/stacks/restore_gate.go`; pinned by `restore_gate_test.go` and `internal/backup/r1057_unit_gate_test.go`.
|
||||
|
||||
**[DESIGN] The jump guard.** An installed version that matches no ladder entry's `from` jumps to the catalog's
|
||||
current definition when a PERSON presses Update (`ladder.go`, measured live on vikunja 2.5.0, `09` §6.4 part 5);
|
||||
the AUTOMATIC leg never presses it (`LegSkipOlderThanLadder`, and a template without a ladder is
|
||||
|
||||
@@ -948,6 +948,24 @@ its length, and both fixes cost something the household would notice — operato
|
||||
**Extended the same day (operator, R-1059 = A):** the off-site and PBS-DR re-issues (they mint a household
|
||||
credential for the box) are session only too; „Off-site: write" keeps the windows and freeze.
|
||||
|
||||
### 2026-10-15 (afternoon) — a restore puts the setup gate back as it was (operator brief, R-1057 raised to P2)
|
||||
|
||||
253. **A restore puts the setup gate back exactly as it was when the backup was taken.** *Operator brief 2026-10-15
|
||||
(„A restored app never comes back open"); R-1057 raised to P2 by the reviewer the same day, seen by the operator.*
|
||||
Narrows decision 46's restore clause („no prior record = no gate: the data comes back with its admin"), whose
|
||||
assumption fails for a backup made before the household's first setup (measured on 9202: a stranger's `/ping` 200
|
||||
after the restore of a Prowlarr backed up before its setup).
|
||||
1. The per-app backup records the gate (`setup_gate:` in the unit's `app.yaml`: closed / open, and by whom; an app
|
||||
installed before gates existed is recorded open, as it was).
|
||||
2. **Closed then → closed now**, also over an installed app (the data it gets back has no admin); the gate file is
|
||||
written BEFORE anything starts (decision 46 F2); the probe or „Kész, beállítottam" opens it as at install.
|
||||
3. **Open then → open** (the rule's intent, unchanged); an app re-gated since (a reinstall) is opened again.
|
||||
4. **No record** (a backup made before controller 0.314.0) of a REMOVED gated app → closed; the gate's loop opens it at
|
||||
once if the app's own probe says the setup is done; without a probe (Plex) the household's „Kész" opens it.
|
||||
**Never open by default.** An installed app restored from a no-record backup keeps its own record.
|
||||
Built in controller v0.314.0 (`internal/stacks/restore_gate.go`); pinned by `restore_gate_test.go` and
|
||||
`r1057_unit_gate_test.go` (ten mutations each seen failing). Proof: `audits/gate-restore-2026-10-15/9202/`.
|
||||
|
||||
### 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,
|
||||
|
||||
@@ -141,6 +141,13 @@ directory) with that tag. Facts the link code needs:
|
||||
name (`http://sonarr:8989`, `http://radarr:7878`, `prowlarrUrl http://prowlarr:9696`); both entries pass Prowlarr's own
|
||||
connection test; the sync command completes (measured with zero indexers — the push of a real one waits for the
|
||||
household's own). It needs no drive and no media folder. FlareSolverr only for an indexer that asks for it.
|
||||
**When the link is made (controller v0.314.0, R-1058):** in every wiring run — a `media_library` app coming up, the media
|
||||
restart, „Bekötés most" — and, since v0.314.0, Prowlarr's own start (`linkOnlyApps`): installed after Sonarr/Radarr it
|
||||
links itself within the 60 s debounce, with no button (before: no link until a press, measured on demo-hp 9201,
|
||||
`audits/prowlarr-2026-10-15/box9201/`). Idempotent: a link already there is not written again.
|
||||
**Plex is called by its ADDRESS (v0.314.0, R-1060)**: Plex refuses the Host name `plex` as non-local (its claim
|
||||
answered 401), so every call — claim, identity, library wiring, sessions — goes to the container's address, resolved at
|
||||
each call (`mediawire.PlexURL`).
|
||||
- **Call the apps with `Host: <container name>`**: once the household sets „Allowed hosts”, Sonarr/Radarr answer a bare
|
||||
IP with 400 „Invalid Hostname” (measured).
|
||||
- **A setting made through Transmission's RPC is lost on a power cut** (it writes `settings.json` only on a clean exit):
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
before: gitea.dooplex.hu/admin/felhom-controller:0.304.0
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.314.0-rc.1
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.314.0-rc.1 Up 25 seconds (healthy)
|
||||
2026/10/15 12:35:53 main.go:354: [INFO] felhom-controller 0.314.0-rc.1 starting (customer: demo-hp, domain: enkisfelhom.hu)
|
||||
2026/10/15 12:35:53 undo.go:676: [INFO] [stacks] applied-meta backfill (R-646): recorded 0 []; skipped 0 [] — not current with the catalog, their pinned version's .felhom.yml is no longer on the box
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
drill repo main: ebf3668
|
||||
mirror main ebf3668 DRILL prowlarr: the step -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (R-1048, checklist 2.5)
|
||||
cache HEAD ebf3668 DRILL prowlarr: the step -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (R-1048, checklist 2.5)
|
||||
file:///opt/docker/felhom-controller/drill-catalog.git
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
2026/10/15 12:38:45 setup_gate.go:295: [INFO] [stacks] prowlarr: setup gate CLOSED before the first start — only the household reaches [prowlarr.enkisfelhom.hu] until the first setup is done
|
||||
2026/10/15 12:40:07 recovery_unit.go:289: [INFO] [backup] Recovery unit captured for prowlarr → /mnt/sys_drive/felhom-data/backups/primary/prowlarr (images=1, secrets-referenced=0, data_keys=0, portable-carried=0/0, withheld=0)
|
||||
2026/10/15 12:41:36 restore_unit.go:381: [INFO] [backup] Restore prowlarr: the data belongs to [lscr.io/linuxserver/prowlarr:version-2.5.2.5491] (written 2026-10-15T12:40:06Z); the app ran [lscr.io/linuxserver/prowlarr:version-2.6.5.5623] — it comes back at
|
||||
2026/10/15 12:41:36 restore_unit.go:393: [INFO] [backup] Restoring prowlarr from recovery unit /mnt/sys_drive/felhom-data/backups/primary/prowlarr: images=1, secrets recovered=0/0, data_keys=0
|
||||
2026/10/15 12:41:37 setup_gate.go:295: [INFO] [stacks] prowlarr: setup gate CLOSED before the first start — only the household reaches [prowlarr.enkisfelhom.hu] until the first setup is done
|
||||
2026/10/15 12:41:37 restore_gate.go:86: [INFO] [stacks] prowlarr: restored with its setup gate CLOSED — it was closed when the backup was taken (R-1057)
|
||||
2026/10/15 12:41:54 restore_unit.go:487: [INFO] [backup] Restore-from-unit completed: prowlarr — 1 volume(s) of 1 listed, 0 database(s) of 0 listed
|
||||
@@ -0,0 +1,7 @@
|
||||
27: sync_interval: 15m
|
||||
-rw------- 1 root root 1944 Oct 12 20:52 /var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml.pre-prowlarr1015
|
||||
mirror main c42d92b DRILL prowlarr: the step -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (R-1048, checklist 2.5)
|
||||
cache HEAD 73b45a6 Prowlarr into the live catalog, with its complete onboarding record (R-1048)
|
||||
https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git
|
||||
|
||||
repo_url: https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git
|
||||
@@ -0,0 +1,2 @@
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.314.0 Up 25 seconds (healthy)
|
||||
0
|
||||
@@ -0,0 +1,17 @@
|
||||
14:49:28 [0] controller: gitea.dooplex.hu/admin/felhom-controller:0.314.0-rc.1
|
||||
14:49:28 deploy sonarr {"DOMAIN": "enkisfelhom.hu", "SUBDOMAIN": "sonarr", "HDD_PATH": "/mnt/felhom-drives/scratch_hdd"} -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"}
|
||||
14:49:53 sonarr: deployed, running, pinned={'sonarr': 'lscr.io/linuxserver/sonarr:4.0.20'}
|
||||
14:51:15 [1] wiring after Sonarr came up:
|
||||
2026/10/15 12:50:37 media_events.go:69: [INFO] [media] wiring the drives into the media apps (a media app came up: sonarr)
|
||||
2026/10/15 12:50:38 mediawire.go:154: [INFO] [mediawire] 1 drive(s) wired in 984ms: folders=done(0) sonarr=done(3)
|
||||
14:51:15 deploy prowlarr {"DOMAIN": "enkisfelhom.hu", "SUBDOMAIN": "prowlarr"} -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"}
|
||||
14:51:45 prowlarr: deployed, running, pinned={'prowlarr': 'lscr.io/linuxserver/prowlarr:version-2.6.5.5623'}
|
||||
14:52:42 [2] R-1058 Sonarr appeared in Prowlarr's Apps with NO button, ~40 s after Prowlarr ran: [('Sonarr', 'fullSync', 'http://sonarr:8989')]
|
||||
14:52:46 [2] wiring runs since Prowlarr's install:
|
||||
2026/10/15 12:52:27 media_events.go:69: [INFO] [media] wiring the drives into the media apps (a media app came up: prowlarr)
|
||||
2026/10/15 12:52:28 mediawire.go:154: [INFO] [mediawire] 1 drive(s) wired in 1.817s: folders=done(0) sonarr=unchanged(0) prowlarr=done(1)
|
||||
14:52:46 [3] a second run („Bekötés most”) -> 200; prowlarr result: [{"app": "prowlarr", "state": "unchanged"}] (expected: unchanged, nothing written)
|
||||
14:52:52 [4] Prowlarr's connection test for Sonarr -> 200
|
||||
14:53:28 [X] prowlarr: remove (keep drive data, drop backups) -> 200 {"ok": true, "data": {"removed": "prowlarr", "volumes_removed": ["prowlarr_prowlarr_config"], "hdd_paths_removed": [], "hdd_paths_preserved": [], "userdata_kept": [], "verified": true}, "message": "St
|
||||
14:54:03 [X] sonarr: remove (keep drive data, drop backups) -> 200 {"ok": true, "data": {"removed": "sonarr", "volumes_removed": ["sonarr_sonarr_config"], "hdd_paths_removed": [], "hdd_paths_preserved": [], "userdata_kept": ["/mnt/felhom-drives/scratch_hdd/userdata/m
|
||||
14:54:12 [X] left: containers=0 volumes=0
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"linked_after_s": 40,
|
||||
"second_run": [
|
||||
{
|
||||
"app": "prowlarr",
|
||||
"state": "unchanged"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
[0] controller: gitea.dooplex.hu/admin/felhom-controller:0.314.0-rc.1
|
||||
deploy sonarr {"DOMAIN": "enkisfelhom.hu", "SUBDOMAIN": "sonarr", "HDD_PATH": "/mnt/felhom-drives/scratch_hdd"} -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"}
|
||||
sonarr: deployed, running, pinned={'sonarr': 'lscr.io/linuxserver/sonarr:4.0.20'}
|
||||
[1] wiring after Sonarr came up:
|
||||
2026/10/15 12:50:37 media_events.go:69: [INFO] [media] wiring the drives into the media apps (a media app came up: sonarr)
|
||||
2026/10/15 12:50:38 mediawire.go:154: [INFO] [mediawire] 1 drive(s) wired in 984ms: folders=done(0) sonarr=done(3)
|
||||
deploy prowlarr {"DOMAIN": "enkisfelhom.hu", "SUBDOMAIN": "prowlarr"} -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"}
|
||||
prowlarr: deployed, running, pinned={'prowlarr': 'lscr.io/linuxserver/prowlarr:version-2.6.5.5623'}
|
||||
[2] R-1058 Sonarr appeared in Prowlarr's Apps with NO button, ~40 s after Prowlarr ran: [('Sonarr', 'fullSync', 'http://sonarr:8989')]
|
||||
[2] wiring runs since Prowlarr's install:
|
||||
2026/10/15 12:52:27 media_events.go:69: [INFO] [media] wiring the drives into the media apps (a media app came up: prowlarr)
|
||||
2026/10/15 12:52:28 mediawire.go:154: [INFO] [mediawire] 1 drive(s) wired in 1.817s: folders=done(0) sonarr=unchanged(0) prowlarr=done(1)
|
||||
[3] a second run („Bekötés most”) -> 200; prowlarr result: [{"app": "prowlarr", "state": "unchanged"}] (expected: unchanged, nothing written)
|
||||
[4] Prowlarr's connection test for Sonarr -> 200
|
||||
[X] prowlarr: remove (keep drive data, drop backups) -> 200 {"ok": true, "data": {"removed": "prowlarr", "volumes_removed": ["prowlarr_prowlarr_config"], "hdd_paths_removed": [], "hdd_paths_preserved": [], "userdata_kept": [], "verified": true}, "message": "St
|
||||
[X] sonarr: remove (keep drive data, drop backups) -> 200 {"ok": true, "data": {"removed": "sonarr", "volumes_removed": ["sonarr_sonarr_config"], "hdd_paths_removed": [], "hdd_paths_preserved": [], "userdata_kept": ["/mnt/felhom-drives/scratch_hdd/userdata/m
|
||||
[X] left: containers=0 volumes=0
|
||||
@@ -0,0 +1,60 @@
|
||||
14:37:21 drill commit fd4b9f1541db: -> lscr.io/linuxserver/prowlarr:version-2.5.2.5491 (push rc=0)
|
||||
mirror main fd4b9f1 DRILL prowlarr: back to the previous image for a step -> lscr.io/linuxserver/prowlarr:version-2.5.2.5491 (R-1048, checklist 2.5)
|
||||
|
||||
drill repo main: fd4b9f1
|
||||
14:38:45 [sync] the badge NEVER caught up to lscr.io/linuxserver/prowlarr:version-2.5.2.5491 in 78.5s — catalog_images = None
|
||||
14:38:45 [1] deploy -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"}
|
||||
14:39:03 gate: prowlarr is gated — passed as the household (cookie set)
|
||||
14:39:07 [1] through traefik: 401 (gated: 302 to the gate is the healthy answer for a stranger)
|
||||
14:39:10 [1] stack .felhom.yml bytes: 3948
|
||||
14:39:13 [1] running: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy
|
||||
14:39:22 prowlarr: POST /tag felhom20fd668a http=201
|
||||
14:39:26 prowlarr: POST /appprofile felhom-profile-c7f82e http=201
|
||||
14:39:39 prowlarr: tag kept, app profile present=True
|
||||
14:39:39 [2] seed {'tag': 'felhom20fd668a', 'profile': 'felhom-profile-c7f82e'} read back: True
|
||||
14:39:42 [2] 2.7 config volume size after the seed: 5.4M
|
||||
14:39:48 [2] 4.4 negative control, the same probe on a port nothing serves: rc=7 ; the real probe: rc=0
|
||||
14:39:51 [2] 1.8 the app's own start lines: [Info] Microsoft.Hosting.Lifetime: Application started. Press Ctrl+C to shut down. | [Info] Microsoft.Hosting.Lifetime: Hosting environment: Production
|
||||
14:39:52 drill commit c42d92bc425d: -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (push rc=0)
|
||||
mirror main c42d92b DRILL prowlarr: the step -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (R-1048, checklist 2.5)
|
||||
|
||||
drill repo main: c42d92b
|
||||
14:40:02 [6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'}
|
||||
14:40:02 + 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None
|
||||
14:40:07 + 5.1s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None
|
||||
14:40:09 + 6.2s phase=pulling label=Új verzió letöltése… err=None hold=None
|
||||
14:40:17 + 14.4s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None
|
||||
14:40:22 + 19.5s phase=verifying label=Működés ellenőrzése… err=None hold=None
|
||||
14:40:32 + 29.7s phase=done label=Frissítve err=None hold=None
|
||||
14:40:32 [3] guarded Update (its backing-up phase is the per-app backup): final=done phases=['backing-up', 'safety-dump', 'pulling', 'copying', 'verifying', 'done']
|
||||
14:40:46 prowlarr: tag kept, app profile present=True
|
||||
14:40:46 [3] seed read back after the Update: True
|
||||
14:40:49 [3] after: {"pinned_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "installed_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "catalog_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "live_compose_image_lines": ["image: lscr.io/linuxserver/prowlarr:version-2.6.5.5623"], "docker_inspect": ["prowlarr lscr.io/linuxserver/prowlarr:ve
|
||||
14:40:52 [3] R-1057 the per-app backup's unit app.yaml, its gate lines:
|
||||
14:40:52 [4] restore points offered BEFORE the remove: [{"time": "2026-10-15T12:40:06Z", "short_id": "helyi", "tier": 1, "drive_label": "Belső SSD (rendszer)"}]
|
||||
14:40:56 [4] stop -> 200
|
||||
14:41:27 [4] remove KEEPING the backups -> 200 {"ok": true, "data": {"removed": "prowlarr", "volumes_removed": ["prowlarr_prowlarr_config"], "hdd_paths_removed": [], "hdd_paths_preserved": [], "userdata_kept": [], "verified": true}, "message": "Stack prowlarr removed"}
|
||||
14:41:36 [4] left after remove: containers=0 volumes=0
|
||||
14:41:36 [R] restoring prowlarr from snapshot 'helyi' (of 1 offered)
|
||||
14:41:36 [R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
14:41:36 + 0.0s restore (True, None, None)
|
||||
14:41:54 + 18.2s restore (False, None, None)
|
||||
14:41:54 [R] after restore: state=running hold=None phase=None
|
||||
14:41:58 [5] restore: {"ok": true, "snapshot_id": "helyi", "http": "HTTP/2 302", "seconds": 18.3, "state_after": "running", "hold_after": null, "why": null}
|
||||
14:42:11 prowlarr: tag kept, app profile present=True
|
||||
14:42:11 [5] 2.5 the seed reads back after remove + restore: True
|
||||
14:42:11 [6] R-1057 a STRANGER after the restore: GET /ping -> 401
|
||||
14:42:11 [6] R-1057 a STRANGER after the restore: GET / -> 302 loc=https://felhom.enkisfelhom.hu/__gate/start?rd=https%3A%2F%2Fprowlarr.e
|
||||
14:42:11 [6] R-1057 a STRANGER after the restore: GET /initialize.json -> 401
|
||||
14:42:11 [6] the HOUSEHOLD (gate cookie) after the restore: GET /ping -> 200
|
||||
14:42:11 [6] gate record after the restore: {"state": "closed", "since": "2026-10-15T12:41:37Z", "hosts": ["prowlarr.enkisfelhom.hu"]}
|
||||
14:42:14 [6] gate file on the box: /opt/docker/stacks/traefik/dynamic/setup-gate-prowlarr.yml
|
||||
14:42:14 [7] the household's „Kész, beállítottam” -> 200 {"data": {"opened": true}, "error": "", "ok": true}
|
||||
14:42:22 [7] a stranger after „Kész”: GET /ping -> 200 (the app itself answers now)
|
||||
14:42:26 [5] through traefik after restore: 200
|
||||
14:42:29 [5] app.yaml env keys after restore: DOMAIN SUBDOMAIN
|
||||
14:42:32 [5] image after restore: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy
|
||||
14:42:35 [X] stop -> 200 {'ok': True, 'message': 'Stack prowlarr stop completed'}
|
||||
14:43:07 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'prowlarr', 'volumes_removed': ['prowlarr_prowlarr_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'A
|
||||
14:43:16 [X] after remove: deployed=False leftovers='/opt/docker/stacks/prowlarr'
|
||||
14:43:19 [9] after remove: containers=0 volumes=0
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"seed": {
|
||||
"tag": "felhom20fd668a",
|
||||
"profile": "felhom-profile-c7f82e"
|
||||
},
|
||||
"seed_before": true,
|
||||
"after_update": true,
|
||||
"after_restore": true,
|
||||
"update_final": "done",
|
||||
"restore": {
|
||||
"ok": true,
|
||||
"snapshot_id": "helyi",
|
||||
"http": "HTTP/2 302",
|
||||
"seconds": 18.3,
|
||||
"state_after": "running"
|
||||
},
|
||||
"measured_at": "2026-10-15T12:43:19Z"
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
drill commit fd4b9f1541db: -> lscr.io/linuxserver/prowlarr:version-2.5.2.5491 (push rc=0)
|
||||
[sync] the badge NEVER caught up to lscr.io/linuxserver/prowlarr:version-2.5.2.5491 in 78.5s — catalog_images = None
|
||||
[1] deploy -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"}
|
||||
gate: prowlarr is gated — passed as the household (cookie set)
|
||||
[1] through traefik: 401 (gated: 302 to the gate is the healthy answer for a stranger)
|
||||
[1] stack .felhom.yml bytes: 3948
|
||||
[1] running: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy
|
||||
prowlarr: POST /tag felhom20fd668a http=201
|
||||
prowlarr: POST /appprofile felhom-profile-c7f82e http=201
|
||||
prowlarr: tag kept, app profile present=True
|
||||
[2] seed {'tag': 'felhom20fd668a', 'profile': 'felhom-profile-c7f82e'} read back: True
|
||||
[2] 2.7 config volume size after the seed: 5.4M
|
||||
[2] 4.4 negative control, the same probe on a port nothing serves: rc=7 ; the real probe: rc=0
|
||||
[2] 1.8 the app's own start lines: [Info] Microsoft.Hosting.Lifetime: Application started. Press Ctrl+C to shut down. | [Info] Microsoft.Hosting.Lifetime: Hosting environment: Production
|
||||
drill commit c42d92bc425d: -> lscr.io/linuxserver/prowlarr:version-2.6.5.5623 (push rc=0)
|
||||
[6] Update -> 202 {'ok': True, 'data': {'accepted': True, 'completed': False}, 'message': 'Frissítés elindult – az állapot a kártyán követhető'}
|
||||
+ 0.0s phase=backing-up label=Biztonsági mentés készül a frissítés előtt… err=None hold=None
|
||||
+ 5.1s phase=safety-dump label=Adatbázis pillanatkép… err=None hold=None
|
||||
+ 6.2s phase=pulling label=Új verzió letöltése… err=None hold=None
|
||||
+ 14.4s phase=copying label=Az adatok másolása a frissítés előtt… err=None hold=None
|
||||
+ 19.5s phase=verifying label=Működés ellenőrzése… err=None hold=None
|
||||
+ 29.7s phase=done label=Frissítve err=None hold=None
|
||||
[3] guarded Update (its backing-up phase is the per-app backup): final=done phases=['backing-up', 'safety-dump', 'pulling', 'copying', 'verifying', 'done']
|
||||
prowlarr: tag kept, app profile present=True
|
||||
[3] seed read back after the Update: True
|
||||
[3] after: {"pinned_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "installed_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "catalog_images": {"prowlarr": "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"}, "live_compose_image_lines": ["image: lscr.io/linuxserver/prowlarr:version-2.6.5.5623"], "docker_inspect": ["prowlarr lscr.io/linuxserver/prowlarr:ve
|
||||
[3] R-1057 the per-app backup's unit app.yaml, its gate lines:
|
||||
[4] restore points offered BEFORE the remove: [{"time": "2026-10-15T12:40:06Z", "short_id": "helyi", "tier": 1, "drive_label": "Belső SSD (rendszer)"}]
|
||||
[4] stop -> 200
|
||||
[4] remove KEEPING the backups -> 200 {"ok": true, "data": {"removed": "prowlarr", "volumes_removed": ["prowlarr_prowlarr_config"], "hdd_paths_removed": [], "hdd_paths_preserved": [], "userdata_kept": [], "verified": true}, "message": "Stack prowlarr removed"}
|
||||
[4] left after remove: containers=0 volumes=0
|
||||
[R] restoring prowlarr from snapshot 'helyi' (of 1 offered)
|
||||
[R] POST /backup/restore -> HTTP/2 302 ['location: /backups/restore?flash=flash.restore.started']
|
||||
+ 0.0s restore (True, None, None)
|
||||
+ 18.2s restore (False, None, None)
|
||||
[R] after restore: state=running hold=None phase=None
|
||||
[5] restore: {"ok": true, "snapshot_id": "helyi", "http": "HTTP/2 302", "seconds": 18.3, "state_after": "running", "hold_after": null, "why": null}
|
||||
prowlarr: tag kept, app profile present=True
|
||||
[5] 2.5 the seed reads back after remove + restore: True
|
||||
[6] R-1057 a STRANGER after the restore: GET /ping -> 401
|
||||
[6] R-1057 a STRANGER after the restore: GET / -> 302 loc=https://felhom.enkisfelhom.hu/__gate/start?rd=https%3A%2F%2Fprowlarr.e
|
||||
[6] R-1057 a STRANGER after the restore: GET /initialize.json -> 401
|
||||
[6] the HOUSEHOLD (gate cookie) after the restore: GET /ping -> 200
|
||||
[6] gate record after the restore: {"state": "closed", "since": "2026-10-15T12:41:37Z", "hosts": ["prowlarr.enkisfelhom.hu"]}
|
||||
[6] gate file on the box: /opt/docker/stacks/traefik/dynamic/setup-gate-prowlarr.yml
|
||||
[7] the household's „Kész, beállítottam” -> 200 {"data": {"opened": true}, "error": "", "ok": true}
|
||||
[7] a stranger after „Kész”: GET /ping -> 200 (the app itself answers now)
|
||||
[5] through traefik after restore: 200
|
||||
[5] app.yaml env keys after restore: DOMAIN SUBDOMAIN
|
||||
[5] image after restore: lscr.io/linuxserver/prowlarr:version-2.5.2.5491 health=healthy
|
||||
[X] stop -> 200 {'ok': True, 'message': 'Stack prowlarr stop completed'}
|
||||
[X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'prowlarr', 'volumes_removed': ['prowlarr_prowlarr_config'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'A
|
||||
[X] after remove: deployed=False leftovers='/opt/docker/stacks/prowlarr'
|
||||
[9] after remove: containers=0 volumes=0
|
||||
@@ -0,0 +1,23 @@
|
||||
# Controller v0.314.0 — the gate on restore, test builds and the floor, Prowlarr's link, Plex by address (2026-10-15)
|
||||
|
||||
Design read: `architecture/09-update-architecture.md` §3 decisions 46–49, 245–253; `07-backup-architecture.md`;
|
||||
`15-media-library.md` §6. Code: felhom-controller `17863fe` + `97c992a`.
|
||||
|
||||
| folder / file | what it proves |
|
||||
|---|---|
|
||||
| `9202/00-rc1.txt` | scratch guest 9202 on `0.314.0-rc.1` |
|
||||
| `9202/restore.txt` | **R-1057 live:** Prowlarr installed (no account), the Update's per-app backup, remove keeping backups, restore → a stranger: `/ping` 401, `/` 302 to the gate, `/initialize.json` 401; the household 200; „Kész" opens it |
|
||||
| `9202/02-controller-log.txt` | the box's own line: „restored with its setup gate CLOSED — it was closed when the backup was taken (R-1057)" |
|
||||
| `9202/link.txt` | **R-1058 live:** Sonarr, then Prowlarr → „a media app came up: prowlarr", linked ~40 s later, no button; a second run „unchanged" |
|
||||
| `release/01-tester1-rc1.txt` | Tester 1 put on `0.314.0-rc.1` by hand (rc.4 runs the old check and cannot leave by itself) |
|
||||
| `release/02-customer-floors.txt` | per-customer floors 0.314.0 / MinAgent 0.155.0 through CC's token: 303 `floor_set` ×3 |
|
||||
| `release/03-boxes-move.txt` | demo-hp and demo-felhom on 0.314.0 within 13 s; Tester 1 at 13:09:38 by itself (**R-1053 live**), at its regular report (R-1061) |
|
||||
| `release/04-https-only.txt`, `05-health.txt` | the box walk's checks on all three: https-only OK; 0 unhealthy, 0 restarting, 0 exited; controller healthy; dashboard 200 |
|
||||
| `release/06-global-floor.txt` | **the global floor 0.314.0, MinAgent 0.155.0, by CC's token:** 303 `floor_set` at 13:11:04 UTC; read back from the hub DB with its WAL |
|
||||
| `release/07-clear-customer-floors.txt` | the three per-customer floors cleared (empty), read back |
|
||||
| `plex9201/01-fake-code.txt` | **R-1060 live, partly:** a FAKE code through the dashboard's claim call — Plex took it as local and asked plex.tv (500 after 938 ms; before the fix 401 in 0 ms) |
|
||||
| `plex9201/02-claim.txt` | the real claim, when the operator's code arrives (watcher) |
|
||||
| `tools/` | `walk_gate_restore.py`, `walk_link.py`, `repoint9202.py` |
|
||||
|
||||
Red-proofs (mutation runs) are quoted in the controller CHANGELOG entry and the report
|
||||
(`REPORT-gate-restore-release-2026-10-15.md`).
|
||||
@@ -0,0 +1,8 @@
|
||||
controller: gitea.dooplex.hu/admin/felhom-controller:0.314.0
|
||||
dashboard claim with a FAKE code -> 409 {"data": null, "error": "Plex did not accept the code. It may have run out: get a new one at plex.tv/claim.", "ok": false}
|
||||
2026/10/15 13:14:14 media_events.go:382: [INFO] [web] plex: the household's claim code given to the running server -> HTTP 500 (err <nil>; the code is not logged)
|
||||
|
||||
Oct 15, 2026 15:11:43.440 [139838624131896] DEBUG - Request came in with unrecognized domain / IP 'plex' in header Host; treating as non-local
|
||||
Oct 15, 2026 15:14:13.878 [139838519249720] DEBUG - Request: [172.18.0.5:57144 (Subnet)] POST /myplex/claim?token=<fake> (2 live) #866 GZIP
|
||||
Oct 15, 2026 15:14:14.817 [139838624131896] DEBUG - Completed: [172.18.0.5:57144] 500 POST /myplex/claim?token=<fake> (2 live) #866 GZIP 938ms 405 bytes (pipelined: 1)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
M1 no gate on restore: rc=1 failing=['TestR1057_ClosedAtTheBackupComesBackClosed', 'TestR1057_InPlaceRestoreOfAClosedBackupRegates', 'TestR1057_InPlaceRestoreOfAnOpenBackupOpens', 'TestR1057_NoRecordNoProbeStaysClosed', 'TestR1057_NoRecordProbeDoneOpensByTheLoop', 'TestR1057_NoRecordProbeNotDoneStaysClosed', 'TestR1057_TheGateFileStandsBeforeTheRestoredAppStarts']
|
||||
M2 every known record is closed: rc=1 failing=['TestR1057_InPlaceRestoreOfAnOpenBackupOpens', 'TestR1057_OpenAtTheBackupComesBackOpen', 'TestR773_ARemovedAppComesBackWithSignupClosed']
|
||||
M3 no-record case dropped: rc=1 failing=['TestR1057_NoRecordNoProbeStaysClosed', 'TestR1057_NoRecordProbeDoneOpensByTheLoop', 'TestR1057_NoRecordProbeNotDoneStaysClosed']
|
||||
M4 closed case only for removed: rc=1 failing=['TestR1057_InPlaceRestoreOfAClosedBackupRegates']
|
||||
M5 never-gated captured as nil: rc=1 failing=['TestR1057_CaptureAndReadBack']
|
||||
M6 unknown state fails open: rc=1 failing=['TestR1057_CaptureAndReadBack']
|
||||
M7 reopen case dropped: rc=1 failing=['TestR1057_InPlaceRestoreOfAnOpenBackupOpens']
|
||||
M8 gate written after nothing (prepare dropped): rc=1 failing=['TestR1057_ClosedAtTheBackupComesBackClosed', 'TestR1057_InPlaceRestoreOfAClosedBackupRegates', 'TestR1057_TheGateFileStandsBeforeTheRestoredAppStarts']
|
||||
restored: ok gitea.dooplex.hu/admin/felhom-controller/internal/stacks 0.049s
|
||||
M9 no-record closes an installed app too: --- FAIL: TestR1057_InPlaceNoRecordKeepsTheAppsRecord (0.00s)
|
||||
M10 unit drops the gate: --- FAIL: TestR1057_TheUnitCarriesTheGateAndTheRestoreReadsIt (0.00s)
|
||||
M10 unit drops the gate: r1057_unit_gate_test.go:33: the restore must read the closed gate back from the unit, got {Known:false Closed:false OpenedBy:}
|
||||
@@ -0,0 +1,8 @@
|
||||
M1 comparePre returns 0: rc=1 failing=['TestR1053_ATestBuildIsMovedToTheFloor', 'TestR1053_PreReleaseOrdersBelowItsRelease']
|
||||
M2 floor parses strictly again: rc=1 failing=['TestR1053_ATestBuildIsMovedToTheFloor']
|
||||
M3 warn every call: rc=1 failing=['TestR1053_AnUnreadableVersionWarnsOncePerChange']
|
||||
M4 the button parses pre-releases: rc=1 failing=['TestR1053_TheButtonNeverOffersATestBuild']
|
||||
M5 a numeric id ordered as text: rc=1 failing=['TestR1053_PreReleaseOrdersBelowItsRelease']
|
||||
restored: ok gitea.dooplex.hu/admin/felhom-controller/internal/util 0.041s | ok gitea.dooplex.hu/admin/felhom-controller/internal/selfupdate 0.044s
|
||||
M6 every test build moved: --- FAIL: TestR1053_ATestBuildAboveTheFloorStays (0.00s)
|
||||
M7 at-floor updates anyway: --- FAIL: TestMaybeAutoUpdate_AtFloor_NoAction (0.00s)
|
||||
@@ -0,0 +1,2 @@
|
||||
C-M1 linkOnlyApps empty: --- FAIL: TestR1058_ProwlarrUpRunsTheWiring (0.01s)
|
||||
C-M1 linkOnlyApps empty: r1058_prowlarr_wires_test.go:36: wiring runs = [sonarr], want [prowlarr sonarr] (Prowlarr's up, Sonarr's up; never another app, never a non-up command)
|
||||
@@ -0,0 +1,6 @@
|
||||
C2-M1 Plex by name again: --- FAIL: TestR1060_TheClaimReachesPlexByAddress (0.00s)
|
||||
C2-M1 Plex by name again: r1060_plex_by_address_test.go:57: claim → 401 (<nil>); Plex refuses the name „plex” as non-local — the box must call it by address
|
||||
C2-M1 Plex by name again: --- FAIL: TestR1060_ProductionURLCallsPlexByAddress (0.00s)
|
||||
C2-M1 Plex by name again: plexaddr_test.go:22: plex → "http://plex:32400", want its IPv4 address
|
||||
C2-M2 wiring by name: --- FAIL: TestR1060_ProductionURLCallsPlexByAddress (0.00s)
|
||||
C2-M2 wiring by name: plexaddr_test.go:22: plex → "http://plex:32400", want its IPv4 address
|
||||
@@ -0,0 +1,4 @@
|
||||
before: gitea.dooplex.hu/admin/felhom-controller:0.313.0-rc.4
|
||||
17
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.314.0-rc.1
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.314.0-rc.1 Up 30 seconds (healthy)
|
||||
@@ -0,0 +1,3 @@
|
||||
12:55:51 POST /customers/tester-1/floor 0.314.0 MinAgent 0.155.0 -> HTTP/1.1 303 See Other Location: /customers/tester-1?flash=floor_set
|
||||
12:55:51 POST /customers/demo-hp/floor 0.314.0 MinAgent 0.155.0 -> HTTP/1.1 303 See Other Location: /customers/demo-hp?flash=floor_set
|
||||
12:55:51 POST /customers/demo-felhom/floor 0.314.0 MinAgent 0.155.0 -> HTTP/1.1 303 See Other Location: /customers/demo-felhom?flash=floor_set
|
||||
@@ -0,0 +1,51 @@
|
||||
12:56:04 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:56:23 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:56:42 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:57:01 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:57:20 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:57:40 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:57:59 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:58:18 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:58:37 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:58:56 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:59:16 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:59:35 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
12:59:54 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:00:13 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:00:33 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:00:52 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:01:11 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:01:30 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:01:49 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:02:08 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:02:28 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:02:47 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:03:06 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:03:25 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:03:45 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:04:04 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:04:23 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:04:42 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:05:02 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:05:21 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:05:40 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:05:59 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:06:18 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:06:21 tester-1=0.314.0-rc.1
|
||||
13:06:37 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:06:43 tester-1=0.314.0-rc.1
|
||||
13:06:55 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:07:04 tester-1=0.314.0-rc.1
|
||||
13:07:14 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:07:26 tester-1=0.314.0-rc.1
|
||||
13:07:33 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:07:48 tester-1=0.314.0-rc.1
|
||||
13:07:51 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:08:09 tester-1=0.314.0-rc.1
|
||||
13:08:10 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:08:30 tester-1=0.314.0-rc.1 demo-hp=0.314.0 demo-felhom=0.314.0
|
||||
13:08:30 tester-1=0.314.0-rc.1
|
||||
13:08:52 tester-1=0.314.0-rc.1
|
||||
13:09:14 tester-1=0.314.0-rc.1
|
||||
13:09:36 tester-1=0.314.0-rc.1
|
||||
13:09:58 tester-1=0.314.0
|
||||
@@ -0,0 +1,35 @@
|
||||
control NEGATIVE (a stub answering 200 over plain http): convicted, as it must (200 -)
|
||||
== tester-1 (enkicsifelhom.hu): 13 public name(s)
|
||||
OK control POSITIVE http://felhom.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://files.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://files.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://media.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://media.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://paperless.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paperless.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://paste.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paste.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://plex.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://plex.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://prowlarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://prowlarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://radarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://radarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://sonarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://sonarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://torrent.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://torrent.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://transmission.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://transmission.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://wiki.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkicsifelhom.hu/r1033-check?q=1
|
||||
== demo-hp (enkisfelhom.hu): 13 public name(s)
|
||||
OK http://books.enkisfelhom.hu/r1033-check?q=1 -> 301 https://books.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://docs.enkisfelhom.hu/r1033-check?q=1 -> 301 https://docs.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://felhom.enkisfelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://files.enkisfelhom.hu/r1033-check?q=1 -> 301 https://files.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://gist.enkisfelhom.hu/r1033-check?q=1 -> 301 https://gist.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://ha.enkisfelhom.hu/r1033-check?q=1 -> 301 https://ha.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://papir.enkisfelhom.hu/r1033-check?q=1 -> 301 https://papir.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://paste.enkisfelhom.hu/r1033-check?q=1 -> 301 https://paste.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://pdf.enkisfelhom.hu/r1033-check?q=1 -> 301 https://pdf.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://plex.enkisfelhom.hu/r1033-check?q=1 -> 301 https://plex.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://travel.enkisfelhom.hu/r1033-check?q=1 -> 301 https://travel.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://wiki.enkisfelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkisfelhom.hu/r1033-check?q=1
|
||||
== demo-felhom (demo-felhom.eu): 4 public name(s)
|
||||
OK http://felhom.demo-felhom.eu/r1033-check?q=1 -> 301 https://felhom.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://files.demo-felhom.eu/r1033-check?q=1 -> 301 https://files.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://gist.demo-felhom.eu/r1033-check?q=1 -> 301 https://gist.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1 -> 301 https://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1
|
||||
== every public name redirects http -> https
|
||||
@@ -0,0 +1,15 @@
|
||||
== tester-1
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.314.0
|
||||
containers=17 unhealthy=0 restarting=0 exited=0
|
||||
controller ERROR lines last 10m: 0
|
||||
== demo-hp
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.314.0
|
||||
containers=21 unhealthy=0 restarting=0 exited=0
|
||||
controller ERROR lines last 10m: 0
|
||||
== demo-felhom
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.314.0
|
||||
containers=5 unhealthy=0 restarting=0 exited=0
|
||||
controller ERROR lines last 10m: 0
|
||||
== tester-1 (2nd pass) controller-docker-health=healthy dashboard-via-traefik=200
|
||||
== demo-hp (2nd pass) controller-docker-health=healthy dashboard-via-traefik=200
|
||||
== demo-felhom (2nd pass) controller-docker-health=healthy dashboard-via-traefik=200
|
||||
@@ -0,0 +1 @@
|
||||
2026-10-15T13:11:04Z POST /configuration/global-floor 0.314.0 MinAgent 0.155.0 -> HTTP/1.1 303 See Other Location: /configuration?flash=floor_set
|
||||
@@ -0,0 +1,9 @@
|
||||
13:13:43 POST /customers/tester-1/floor (empty = clear) -> HTTP/1.1 303 See Other Location: /customers/tester-1?flash=floor_set
|
||||
13:13:43 POST /customers/demo-hp/floor (empty = clear) -> HTTP/1.1 303 See Other Location: /customers/demo-hp?flash=floor_set
|
||||
13:13:43 POST /customers/demo-felhom/floor (empty = clear) -> HTTP/1.1 303 See Other Location: /customers/demo-felhom?flash=floor_set
|
||||
demo-felhom|''
|
||||
demo-hp|''
|
||||
tester-1|''
|
||||
Tester-2|''
|
||||
min_controller_version|0.314.0
|
||||
min_controller_version_declared_min_agent|0.314.0=0.155.0
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
"""repoint.py mirror|drill|restore|status — point the Tester 1 box at the drill catalog WITHOUT a credential (R-992).
|
||||
|
||||
WHY A LOCAL MIRROR. `09` §6.5's route needs a catalog credential on the box: the drill repo is private (R-992). This
|
||||
session could not mint a READ-ONLY one — the Gitea credential on DooPlex is itself a token, and Gitea answers 403 to a
|
||||
token asking to list or create tokens (measured 2026-10-14) — and the night of 2026-10-06 put the admin WRITE token
|
||||
into the box's controller.yaml, which this task forbids. The cache-staging workaround of 2026-10-12 does not hold for a
|
||||
test that lasts days: every sync (and every controller start) re-applies the live catalog over the staged templates
|
||||
and over the installed apps' compose files (Syncer.copyTemplates, no deployed check).
|
||||
|
||||
So the drill catalog is MIRRORED onto the box: a bare git repository inside the controller's own data volume, and
|
||||
`git.repo_url: file:///opt/docker/felhom-controller/drill-catalog.git`. The controller's sync runs exactly as for a
|
||||
remote (clone/fetch/reset, the R-615 re-clone on a changed URL); no secret is on the box. DooPlex updates the mirror
|
||||
from the drill repo with a git bundle (`mirror`). The drill repo on Gitea stays the record.
|
||||
|
||||
SC=~/.felhom-tester1 TARGET=tester-1 python3 repoint.py mirror # (re)load the mirror from the drill repo main
|
||||
... repoint.py drill # save controller.yaml (once), set repo_url to the mirror, restart, sync, rescan
|
||||
... repoint.py restore # put the saved controller.yaml back (byte-identical), restart, sync, rescan
|
||||
... repoint.py status
|
||||
"""
|
||||
import os, subprocess, sys, time
|
||||
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
||||
import box_walk as w
|
||||
|
||||
DRILL = "/mnt/5_hdd/felhom.eu/drill/app-catalog-drill"
|
||||
VOL = "/var/lib/docker/volumes/felhom-controller-data/_data" # guest path of the controller's data volume
|
||||
CVOL = "/opt/docker/felhom-controller" # the same, inside the controller container
|
||||
SAVE = f"{VOL}/controller.yaml.pre-prowlarr1015"
|
||||
MIRROR_URL = f"file://{CVOL}/drill-catalog.git"
|
||||
|
||||
|
||||
def status():
|
||||
print(w.guest(f"""grep -nE 'repo_url|sync_interval' {VOL}/controller.yaml; ls -l {SAVE} 2>&1 | tail -1
|
||||
docker exec felhom-controller sh -c 'git -C {CVOL}/drill-catalog.git log -1 --format="mirror main %h %s" main 2>&1;
|
||||
git -C {CVOL}/data/catalog-cache log -1 --format="cache HEAD %h %s" 2>&1; git -C {CVOL}/data/catalog-cache remote get-url origin'"""))
|
||||
|
||||
|
||||
def mirror():
|
||||
b = "/tmp/drill-prowlarr.bundle"
|
||||
subprocess.run(["git", "-C", DRILL, "bundle", "create", b, "main"], check=True, capture_output=True)
|
||||
head = subprocess.run(["git", "-C", DRILL, "rev-parse", "--short", "main"], capture_output=True, text=True).stdout.strip()
|
||||
ssh = w.ssh_args()
|
||||
# bundle -> PVE host -> guest -> controller volume; the bare repo is made and fetched BY the controller's own user
|
||||
subprocess.run(["scp", "-q", "-o", "StrictHostKeyChecking=accept-new"] + (["-J", w._T["jump"]] if w._T["jump"] else [])
|
||||
+ [b, f"{w._T['ssh']}:{b}"], check=True)
|
||||
subprocess.run(ssh + [f"pct push {w.GUEST} {b} {VOL}/drill-prowlarr.bundle && rm -f {b}"], check=True)
|
||||
print(w.guest(f"""set -e
|
||||
docker exec felhom-controller sh -c 'test -d {CVOL}/drill-catalog.git || git init -q --bare -b main {CVOL}/drill-catalog.git
|
||||
git -C {CVOL}/drill-catalog.git fetch -q {CVOL}/drill-prowlarr.bundle +main:main && rm -f {CVOL}/drill-prowlarr.bundle
|
||||
git -C {CVOL}/drill-catalog.git log -1 --format="mirror main %h %s" main'"""))
|
||||
print("drill repo main:", head)
|
||||
os.remove(b)
|
||||
|
||||
|
||||
def restart_sync():
|
||||
w.guest("docker restart felhom-controller >/dev/null")
|
||||
for _ in range(60):
|
||||
time.sleep(3)
|
||||
try:
|
||||
w.login()
|
||||
break
|
||||
except SystemExit:
|
||||
continue
|
||||
w.sync_rescan()
|
||||
time.sleep(3)
|
||||
print(w.guest("docker logs --since 3m felhom-controller 2>&1 | grep -E '\\[sync\\]' | grep -v DEBUG | tail -8"))
|
||||
|
||||
|
||||
def drill():
|
||||
print(w.guest(f"""set -e
|
||||
test -f {SAVE} || cp -p {VOL}/controller.yaml {SAVE}
|
||||
sed -i -E 's#^(\\s+repo_url: ).*$#\\1{MIRROR_URL}#' {VOL}/controller.yaml
|
||||
grep -n 'repo_url' {VOL}/controller.yaml; diff {SAVE} {VOL}/controller.yaml || true"""))
|
||||
restart_sync()
|
||||
status()
|
||||
|
||||
|
||||
def restore():
|
||||
print(w.guest(f"""set -e
|
||||
cp -p {SAVE} {VOL}/controller.yaml
|
||||
cmp {SAVE} {VOL}/controller.yaml && echo RESTORED-IDENTICAL; grep -n repo_url {VOL}/controller.yaml"""))
|
||||
restart_sync()
|
||||
status()
|
||||
|
||||
|
||||
{"mirror": mirror, "drill": drill, "restore": restore, "status": status}[sys.argv[1]]()
|
||||
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""walk_gate_restore.py (from prowlarr-2026-10-15/tools/walk_restore.py) — R-1057 live proof on controller 0.314.0-rc.1, and
|
||||
NEW-APP-CHECKLIST 2.5 for Prowlarr on scratch guest 9202: seed -> backup -> remove -> restore ->
|
||||
read back, THROUGH THE PRODUCT (R-1048, 2026-10-15, second 9202 run).
|
||||
|
||||
The box has no per-app „Mentés most" (R-648: the only backup press is whole-box and is never pressed from a drill), so
|
||||
the per-app backup is the one the guarded Update takes in its `backing-up` phase — the same route as grocy's and
|
||||
lubelogger's restore records. Runs ON DOOPLEX with TARGET=9202, SC=<0600 dir holding .ctlpw>, EV=<evidence dir>; 9202
|
||||
must already read the drill mirror, whose prowlarr template is at FROM. Presses: deploy, the drill bump + Update,
|
||||
remove KEEPING the backups, the household's restore button (POST /backup/restore), remove with the backups.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
||||
import box_walk as w # noqa: E402
|
||||
import upgrade_fixtures_box as fx # noqa: E402
|
||||
|
||||
EV = os.environ["EV"]
|
||||
APP, SUB = "prowlarr", "prowlarr"
|
||||
FROM = "lscr.io/linuxserver/prowlarr:version-2.5.2.5491"
|
||||
TO = "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"
|
||||
TOOLS = os.path.dirname(os.path.abspath(__file__))
|
||||
LOG = open(os.path.join(EV, "restore.txt"), "a", buffering=1)
|
||||
_lib_say = w.say
|
||||
|
||||
|
||||
def say(*a):
|
||||
_lib_say(*a)
|
||||
LOG.write(" ".join(str(x) for x in a) + "\n")
|
||||
|
||||
|
||||
w.say = say
|
||||
|
||||
|
||||
def drill_set(ref, why):
|
||||
"""Point the drill template at `ref`, staged by explicit path, then reload the 9202 mirror."""
|
||||
D = w.DRILL
|
||||
comp, fy = f"{D}/templates/{APP}/docker-compose.yml", f"{D}/templates/{APP}/.felhom.yml"
|
||||
w.sh(["git", "-C", D, "pull", "-q", "--rebase", "origin", "main"], timeout=120)
|
||||
c_ = open(comp).read()
|
||||
other = TO if ref == FROM else FROM
|
||||
if f"image: {ref}" not in c_:
|
||||
open(comp, "w").write(c_.replace(f"image: {other}", f"image: {ref}"))
|
||||
w.sh(["git", "-C", D, "add", f"templates/{APP}/docker-compose.yml", f"templates/{APP}/.felhom.yml"])
|
||||
w.sh(["git", "-C", D, "commit", "-q", "-m", f"DRILL {APP}: {why} -> {ref} (R-1048, checklist 2.5)"])
|
||||
r_ = w.sh(["git", "-C", D, "push", "-q", "origin", "main"], timeout=120)
|
||||
say(f"drill commit {w.sh(['git', '-C', D, 'rev-parse', '--short=12', 'HEAD']).stdout.strip()}: -> {ref} (push rc={r_.returncode})")
|
||||
subprocess.run([sys.executable, os.path.join(TOOLS, "repoint9202.py"), "mirror"], check=True, env=dict(os.environ))
|
||||
|
||||
|
||||
def main():
|
||||
w.login()
|
||||
if w.stack(APP).get("deployed"):
|
||||
sys.exit(say("prowlarr is installed on 9202 — STOP, not a fresh install") or 1)
|
||||
drill_set(FROM, "back to the previous image for a step")
|
||||
w.sync_rescan(expect_app=APP, expect_ref=FROM)
|
||||
code, d = w.ctl("POST", f"/api/stacks/{APP}/deploy", {"values": {"DOMAIN": w.DOMAIN, "SUBDOMAIN": SUB}})
|
||||
say(f"[1] deploy -> {code} {json.dumps(d, ensure_ascii=False)[:120]}")
|
||||
w.DEPLOYED_THIS_RUN.add(APP)
|
||||
for _ in range(60):
|
||||
s = w.stack(APP)
|
||||
if s.get("deployed") and s.get("state") == "running":
|
||||
break
|
||||
time.sleep(3)
|
||||
w.wait_app(SUB, "/ping", want=("200",), tries=40)
|
||||
say("[1] through traefik: " + w.guest(f"curl -sk -o /dev/null -w '%{{http_code}}' -H 'Host: {SUB}.{w.DOMAIN}' https://127.0.0.1/ping").strip()
|
||||
+ " (gated: 302 to the gate is the healthy answer for a stranger)")
|
||||
say("[1] stack .felhom.yml bytes: " + w.guest(f"wc -c < /opt/docker/stacks/{APP}/.felhom.yml").strip())
|
||||
say("[1] running: " + w.guest("docker inspect prowlarr -f '{{.Config.Image}} health={{.State.Health.Status}}'").strip())
|
||||
|
||||
fxp = fx.FIXTURES[APP]
|
||||
tok = fxp.seed(w, SUB, say)
|
||||
ok1 = bool(tok) and fxp.verify(w, SUB, tok, say)
|
||||
say(f"[2] seed {tok} read back: {ok1}")
|
||||
say("[2] 2.7 config volume size after the seed: " + w.guest("du -sh /var/lib/docker/volumes/prowlarr_prowlarr_config/_data | cut -f1").strip())
|
||||
say("[2] 4.4 negative control, the same probe on a port nothing serves: rc=" + w.guest("docker exec prowlarr curl -sf -o /dev/null http://127.0.0.1:9697/ping; echo $?").strip()
|
||||
+ " ; the real probe: rc=" + w.guest("docker exec prowlarr curl -sf -o /dev/null http://127.0.0.1:9696/ping; echo $?").strip())
|
||||
say("[2] 1.8 the app's own start lines: " + w.guest("docker logs prowlarr 2>&1 | grep -m2 -E 'Hosting environment|Application started'").strip().replace("\n", " | "))
|
||||
|
||||
drill_set(TO, "the step")
|
||||
w.sync_rescan(expect_app=APP, expect_ref=TO)
|
||||
up = w.press_update(APP)
|
||||
say(f"[3] guarded Update (its backing-up phase is the per-app backup): final={up.get('final_phase')} "
|
||||
f"phases={[p.get('phase') for p in up.get('phases', [])]}")
|
||||
ok2 = bool(tok) and fxp.verify(w, SUB, tok, say)
|
||||
say(f"[3] seed read back after the Update: {ok2}")
|
||||
obs = w.observables(APP)
|
||||
say(f"[3] after: {json.dumps(obs, ensure_ascii=False)[:400]}")
|
||||
s = w.stack(APP)
|
||||
if os.environ.get("VERDICT") == "1":
|
||||
verdict = {"app": APP, "venue": "box 9202 (drill catalog), the product's guarded Update — run 3, the full .felhom.yml",
|
||||
"from": {APP: FROM}, "to": {APP: TO},
|
||||
"verdict": "proven" if (ok1 and ok2 and up.get("final_phase") == "done" and s.get("state") == "running") else "failed",
|
||||
"seed_read_before": ok1, "seed_read_after": ok2, "healthy_after": s.get("state") == "running",
|
||||
"duration_s": up.get("duration_s"), "final_phase": up.get("final_phase"),
|
||||
"measured_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
"evidence": "felhom.eu/documentation/audits/gate-restore-2026-10-15/9202/restore.txt"}
|
||||
os.makedirs(os.path.join(EV, "..", APP), exist_ok=True)
|
||||
json.dump(verdict, open(os.path.join(EV, "..", APP, "box-verdict-prowlarr.json"), "w"), indent=2)
|
||||
say(f"[3] box verdict {verdict['verdict']}")
|
||||
|
||||
say("[3] R-1057 the per-app backup's unit app.yaml, its gate lines: " + w.guest(
|
||||
f"f=$(find / -xdev -path '*{APP}*' -name app.yaml -path '*compose*' -newer /etc/felhom-controller-image 2>/dev/null | head -3); "
|
||||
"for x in $f; do echo \"$x: $(grep -A2 '^setup_gate:' $x | tr '\\n' ' ')\"; done").strip())
|
||||
snaps = w.snapshots(APP)
|
||||
say(f"[4] restore points offered BEFORE the remove: {json.dumps(snaps, ensure_ascii=False)[:400]}")
|
||||
c1, d1 = w.ctl("POST", f"/api/stacks/{APP}/stop")
|
||||
say(f"[4] stop -> {c1}")
|
||||
for _ in range(24):
|
||||
time.sleep(5)
|
||||
if w.stack(APP).get("state") != "running":
|
||||
break
|
||||
code, d = w.ctl("POST", f"/api/stacks/{APP}/remove", {"remove_hdd_data": False, "remove_backups": False})
|
||||
say(f"[4] remove KEEPING the backups -> {code} {json.dumps(d, ensure_ascii=False)[:300]}")
|
||||
time.sleep(5)
|
||||
say("[4] left after remove: " + w.guest("echo containers=$(docker ps -aq -f name=^prowlarr$ | wc -l) volumes=$(docker volume ls -q | grep -c prowlarr)").strip())
|
||||
|
||||
rr = w.restore(APP)
|
||||
say(f"[5] restore: {json.dumps({k: rr.get(k) for k in ('ok', 'snapshot_id', 'http', 'seconds', 'state_after', 'hold_after', 'why')}, ensure_ascii=False)}")
|
||||
w.wait_app(SUB, "/ping", want=("200",), tries=40)
|
||||
ok3 = bool(tok) and fxp.verify(w, SUB, tok, say)
|
||||
say(f"[5] 2.5 the seed reads back after remove + restore: {ok3}")
|
||||
for p, acc in (("/ping", "application/json"), ("/", "text/html"), ("/initialize.json", "application/json")):
|
||||
r = w.sh(["curl", "-sSk", "--max-time", "20", "-o", "/dev/null", "-D", "-", "-H", f"Host: {SUB}.{w.DOMAIN}",
|
||||
"-H", f"Accept: {acc}", f"{w.BASE}{p}"])
|
||||
st = re.match(r"HTTP/\S+ (\d+)", r.stdout or ""); loc = re.search(r"(?im)^location:\s*(\S+)", r.stdout or "")
|
||||
say(f"[6] R-1057 a STRANGER after the restore: GET {p} -> {st.group(1) if st else '000'} {('loc=' + loc.group(1)[:70]) if loc else ''}")
|
||||
rc, code, _ = w.app_curl(SUB, "/ping", timeout=20)
|
||||
say(f"[6] the HOUSEHOLD (gate cookie) after the restore: GET /ping -> {code}")
|
||||
st = w.stack(APP)
|
||||
say(f"[6] gate record after the restore: {json.dumps((st.get('app_config') or {}).get('setup_gate'))}")
|
||||
say("[6] gate file on the box: " + w.guest(f"ls /opt/docker/stacks/traefik/dynamic/setup-gate-{APP}.yml 2>&1").strip())
|
||||
code, d = w.ctl("POST", f"/apps/{APP}/setup-gate/open")
|
||||
say(f"[7] the household's „Kész, beállítottam” -> {code} {json.dumps(d, ensure_ascii=False)[:120]}")
|
||||
time.sleep(8)
|
||||
r = w.sh(["curl", "-sSk", "--max-time", "20", "-o", "/dev/null", "-w", "%{http_code}", "-H", f"Host: {SUB}.{w.DOMAIN}", f"{w.BASE}/ping"])
|
||||
say(f"[7] a stranger after „Kész”: GET /ping -> {r.stdout} (the app itself answers now)")
|
||||
say("[5] through traefik after restore: " + w.guest(f"curl -sk -o /dev/null -w '%{{http_code}}' -H 'Host: {SUB}.{w.DOMAIN}' https://127.0.0.1/ping").strip())
|
||||
say("[5] app.yaml env keys after restore: " + w.guest(f"sed -n '/^env:/,/^[a-z]/p' /opt/docker/stacks/{APP}/app.yaml | grep -oE '^ +[A-Z_]+' | paste -sd' '").strip())
|
||||
say("[5] image after restore: " + w.guest("docker inspect prowlarr -f '{{.Config.Image}} health={{.State.Health.Status}}'").strip())
|
||||
|
||||
# teardown through the product, backups too (this is a scratch install)
|
||||
w.remove(APP)
|
||||
say("[9] after remove: " + w.guest("echo containers=$(docker ps -aq -f name=^prowlarr$ | wc -l) volumes=$(docker volume ls -q | grep -c prowlarr)").strip())
|
||||
json.dump({"seed": tok, "seed_before": ok1, "after_update": ok2, "after_restore": ok3,
|
||||
"update_final": up.get("final_phase"), "restore": {k: rr.get(k) for k in ("ok", "snapshot_id", "http", "seconds", "state_after")},
|
||||
"measured_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())},
|
||||
open(os.path.join(EV, "restore-results.json"), "w"), indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""walk_link.py (from prowlarr-2026-10-15/tools/walk_9201_link.py) — R-1058 live proof on controller 0.314.0-rc.1:
|
||||
Sonarr first, then Prowlarr: the link must appear with NO button; a second run changes nothing.
|
||||
|
||||
(original docstring:) walk_9201_link.py — the read-back on demo-hp 9201 from the LIVE catalog after the publish (R-1048, brief Part D3).
|
||||
|
||||
Runs ON DOOPLEX with TARGET=9201, SC=<0600 dir holding .ctlpw>, EV=<evidence dir>. Presses only what a person presses:
|
||||
POST /api/sync, the deploy wizard's POST /api/stacks/<n>/deploy, the dashboard's „Bekötés most" (POST
|
||||
/api/storage/media/wire), stop + remove KEEPING data. Reads Prowlarr's own API inside its container with the key from
|
||||
its own config.xml (never printed), exactly as the box's wiring does.
|
||||
|
||||
The two orders the controller's code implies (mediawire runs when a media_library app comes up; Prowlarr has no marker):
|
||||
B Sonarr first, Prowlarr after -> expected NO link by itself; „Bekötés most" makes it
|
||||
A Radarr after Prowlarr -> expected the link by itself, ~60 s after Radarr comes up
|
||||
Then: Prowlarr's own connection test for each link, and its sync command to completion.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
||||
import box_walk as w # noqa: E402
|
||||
|
||||
EV = os.environ["EV"]
|
||||
os.makedirs(EV, exist_ok=True)
|
||||
LOG = open(os.path.join(EV, "link.txt"), "a", buffering=1)
|
||||
TO = "lscr.io/linuxserver/prowlarr:version-2.6.5.5623"
|
||||
HDD = os.environ.get("HDD", "/mnt/felhom-drives/hdd_1")
|
||||
RES = {}
|
||||
_lib_say = w.say
|
||||
|
||||
|
||||
def say(*a):
|
||||
_lib_say(*a)
|
||||
LOG.write(" ".join(str(x) for x in a) + "\n")
|
||||
|
||||
|
||||
w.say = say
|
||||
|
||||
|
||||
def papi(path, body=None, method=None):
|
||||
# FIXED 2026-10-15 after the first run: json.dumps() quoted this with DOUBLE quotes, so the guest shell expanded
|
||||
# $(sed …) and $K before docker exec — empty key, every call 401 (link.txt [B3]/[C2]). shlex.quote keeps it literal.
|
||||
"""Prowlarr's own API from inside its container (the key read from its config.xml at use, never printed)."""
|
||||
m = method or ("POST" if body is not None else "GET")
|
||||
data = json.dumps(body) if body is not None else ""
|
||||
script = ("K=$(sed -n 's:.*<ApiKey>\\(.*\\)</ApiKey>.*:\\1:p' /config/config.xml); "
|
||||
f"curl -s -o /tmp/pb.json -w '%{{http_code}}' -X {m} -H \"X-Api-Key: $K\" -H 'Content-Type: application/json' "
|
||||
f"--data-binary @- http://127.0.0.1:9696/api/v1{path}; echo; cat /tmp/pb.json")
|
||||
out = w.guest(f"docker exec -i prowlarr sh -c {shlex.quote(script)} <<'EOF'\n{data}\nEOF")
|
||||
code, _, body_ = out.partition("\n")
|
||||
try:
|
||||
return code.strip(), json.loads(body_) if body_.strip() else None
|
||||
except Exception:
|
||||
return code.strip(), body_[:300]
|
||||
|
||||
|
||||
def apps():
|
||||
c, d = papi("/applications")
|
||||
return [(a.get("name"), a.get("syncLevel"), next((f.get("value") for f in a.get("fields", []) if f.get("name") == "baseUrl"), None))
|
||||
for a in (d or [])] if c == "200" else c
|
||||
|
||||
|
||||
def wiring_lines(since):
|
||||
return w.guest(f"docker logs --since {since} felhom-controller 2>&1 | grep -E '\\[media\\] wiring|\\[mediawire\\]' | cut -c1-400").strip()
|
||||
|
||||
|
||||
def install(name, values):
|
||||
if w.stack(name).get("deployed"):
|
||||
sys.exit(say(f"{name} is already installed on 9201 — STOP, a household's app is never reused") or 1)
|
||||
code, d = w.ctl("POST", f"/api/stacks/{name}/deploy", {"values": values})
|
||||
say(f" deploy {name} {json.dumps(values)} -> {code} {json.dumps(d, ensure_ascii=False)[:120]}")
|
||||
w.DEPLOYED_THIS_RUN.add(name)
|
||||
for _ in range(90):
|
||||
time.sleep(5)
|
||||
st = w.stack(name)
|
||||
if st.get("deployed") and (st.get("app_config") or {}).get("pinned_images") and st.get("state") == "running":
|
||||
say(f" {name}: deployed, running, pinned={(st.get('app_config') or {}).get('pinned_images')}")
|
||||
return True
|
||||
sys.exit(say(f" {name} never became running — STOP") or 1)
|
||||
|
||||
|
||||
def wait_healthy(c, tries=60):
|
||||
for _ in range(tries):
|
||||
if w.guest(f"docker inspect {c} -f '{{{{.State.Health.Status}}}}'").strip() == "healthy":
|
||||
return True
|
||||
time.sleep(5)
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
w.login()
|
||||
for a in ("prowlarr", "sonarr"):
|
||||
if w.stack(a).get("deployed"):
|
||||
sys.exit(say(f"{a} is installed — STOP") or 1)
|
||||
say("[0] controller: " + w.guest("cat /etc/felhom-controller-image").strip())
|
||||
t0 = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||
install("sonarr", {"DOMAIN": w.DOMAIN, "SUBDOMAIN": "sonarr", "HDD_PATH": HDD})
|
||||
wait_healthy("sonarr")
|
||||
time.sleep(75)
|
||||
say("[1] wiring after Sonarr came up:\n" + wiring_lines(t0))
|
||||
tP = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||
install("prowlarr", {"DOMAIN": w.DOMAIN, "SUBDOMAIN": "prowlarr"})
|
||||
wait_healthy("prowlarr")
|
||||
linked = None
|
||||
for i in range(18):
|
||||
time.sleep(10)
|
||||
a = apps()
|
||||
if isinstance(a, list) and any(n == "Sonarr" for n, _, _ in a):
|
||||
linked = (i + 1) * 10
|
||||
say(f"[2] R-1058 Sonarr appeared in Prowlarr's Apps with NO button, ~{linked} s after Prowlarr ran: {a}")
|
||||
break
|
||||
if linked is None:
|
||||
say(f"[2] R-1058 FAILED: no link 180 s after Prowlarr ran; Apps = {apps()}")
|
||||
say("[2] wiring runs since Prowlarr's install:\n" + wiring_lines(tP))
|
||||
RES["linked_after_s"] = linked
|
||||
code, d = w.ctl("POST", "/api/storage/media/wire", {})
|
||||
pr = [r for r in ((d or {}).get("data") or []) if isinstance(r, dict) and r.get("app") == "prowlarr"]
|
||||
say(f"[3] a second run („Bekötés most”) -> {code}; prowlarr result: {json.dumps(pr, ensure_ascii=False)} (expected: unchanged, nothing written)")
|
||||
RES["second_run"] = pr
|
||||
c, full = papi("/applications")
|
||||
for a in (full or []):
|
||||
tc, td = papi("/applications/test", a)
|
||||
say(f"[4] Prowlarr's connection test for {a.get('name')} -> {tc}")
|
||||
for a in ("prowlarr", "sonarr"):
|
||||
c1, _ = w.ctl("POST", f"/api/stacks/{a}/stop")
|
||||
for _ in range(24):
|
||||
time.sleep(5)
|
||||
if w.stack(a).get("state") != "running":
|
||||
break
|
||||
code, d = w.ctl("POST", f"/api/stacks/{a}/remove", {"remove_hdd_data": False, "remove_backups": True})
|
||||
say(f"[X] {a}: remove (keep drive data, drop backups) -> {code} {json.dumps(d, ensure_ascii=False)[:200]}")
|
||||
time.sleep(5)
|
||||
say("[X] left: " + w.guest("echo containers=$(docker ps -aq -f name='^(prowlarr|sonarr)$' | wc -l) volumes=$(docker volume ls -q | grep -cE '^(prowlarr|sonarr)_')").strip())
|
||||
json.dump(RES, open(os.path.join(EV, "link-results.json"), "w"), indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,9 @@
|
||||
Host plex: claim(fake code)=401 identity=200 sections-no-token=401
|
||||
Host 172.18.0.15: claim(fake code)=500 identity=200 sections-no-token=200
|
||||
Oct 15, 2026 14:22:19.292 [139838622022456] DEBUG - Request came in with unrecognized domain / IP 'plex' in header Host; treating as non-local
|
||||
Oct 15, 2026 14:22:19.292 [139838622022456] DEBUG - Request: [172.18.0.5:52552 (Subnet)] POST /myplex/claim?token=<fake> (2 live) #700
|
||||
Oct 15, 2026 14:22:19.292 [139838622022456] DEBUG - Completed: [172.18.0.5:52552] 401 POST /myplex/claim?token=<fake> (2 live) #700 0ms 268 bytes
|
||||
Oct 15, 2026 14:22:19.378 [139838622022456] DEBUG - Request came in with unrecognized domain / IP 'plex' in header Host; treating as non-local
|
||||
Oct 15, 2026 14:22:19.463 [139838622022456] DEBUG - Request came in with unrecognized domain / IP 'plex' in header Host; treating as non-local
|
||||
Oct 15, 2026 14:22:19.552 [139838519249720] DEBUG - Request: [172.18.0.5:52584 (Subnet)] POST /myplex/claim?token=<fake> (2 live) #708
|
||||
Oct 15, 2026 14:22:20.519 [139838622022456] DEBUG - Completed: [172.18.0.5:52584] 500 POST /myplex/claim?token=<fake> (2 live) #708 966ms 325 bytes (pipelined: 1)
|
||||
@@ -26,6 +26,15 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-15 (evening) — controller v0.314.0, the first release under decision 250 (the floor raised by CC's token)
|
||||
|
||||
| Row | What | Closed | Evidence |
|
||||
|---|---|---|---|
|
||||
| **R-1057** | **A restore of a REMOVED gated app came back without its setup gate, even from a backup made before the setup.** (P2 — raised from P3 by the reviewer 2026-10-15) | CLOSED 2026-10-15 — **controller v0.314.0** (`09` §3 decision 253): the per-app backup records the gate; a restore puts it back as it was (closed then → closed now, file first; open then → open; no record → closed until the probe or „Kész"). Unit tests red-proved by ten mutations. **Live on 9202** (rc.1): Prowlarr installed, no account, the Update's per-app backup, remove keeping backups, restore → a stranger got `/ping` 401, `/` 302 to the gate, `/initialize.json` 401; the household 200; the box's log: „restored with its setup gate CLOSED — it was closed when the backup was taken (R-1057)"; „Kész" then opened it. Before the fix the same walk gave the stranger 200. Plex: the same rule, proven by a unit test with Plex's gate shape (no live Plex restore). | `audits/gate-restore-2026-10-15/9202/restore.txt`, `02-controller-log.txt` |
|
||||
| **R-1053** | **A box on an `-rc` tag was never moved by the floor.** (P3) | CLOSED 2026-10-15 — **controller v0.314.0**: `util.ParseVersionPre` puts `X.Y.Z-rc.N` below `X.Y.Z` for the floor only; the button never offers a test build; an unreadable version WARNs once per change. **Live:** Tester 1 put on 0.314.0-rc.1 by hand (rc.4 runs the old check and could never leave by itself), then the floor 0.314.0 moved it: „Post-update startup: update successful (0.314.0-rc.1 → 0.314.0)" at 13:09:38 UTC. | `audits/gate-restore-2026-10-15/release/01-tester1-rc1.txt`, `03-boxes-move.txt` |
|
||||
| **R-1023** | **Build: media apps see every drive, and the box says when they must restart.** (P2) | CLOSED 2026-10-15 — delivered by controller v0.313.0 and the floor; its last step was Tester 1 off the test build (R-1053): Tester 1 runs the release 0.314.0 since 13:09:38 UTC, moved by the floor by itself. | `audits/gate-restore-2026-10-15/release/03-boxes-move.txt`, `audits/media-build-2026-10-15/` |
|
||||
| **R-1058** | **Prowlarr installed AFTER Sonarr/Radarr was not linked until „Bekötés most".** (P3) | CLOSED 2026-10-15 — **controller v0.314.0**: Prowlarr's own start runs the media wiring (`linkOnlyApps`). **Live on 9202** (rc.1): Sonarr first, then Prowlarr → „a media app came up: prowlarr", Sonarr in Prowlarr's Apps ~40 s later, no button; a second run „unchanged"; Prowlarr's connection test 200. Catalog `e67aad4` and the website card drop the button sentence. | `audits/gate-restore-2026-10-15/9202/link.txt` |
|
||||
|
||||
## 2026-10-15 (evening) — Prowlarr in the live catalog (catalog 73b45a6)
|
||||
|
||||
| Row | What | Closed | Evidence |
|
||||
|
||||
@@ -124,7 +124,7 @@ stopping line that lies.
|
||||
| **R-494** | Install & onboarding | P4 | **NARROWED 2026-09-14 by operator ruling → [P3-LOW] the hub COULD create the tunnel at customer creation, for a domain already on Cloudflare. Not blocking: every customer has their own domain and the operator creates the tunnel per day-0 A.1 (`architecture/01-topology-and-trust.md`).** *Original finding, kept:* **[P1-HIGH] A new customer's dashboard has NO reachable address unless the operator hand-makes a Cloudflare tunnel — the link in the setup-code mail is dead.** MEASURED 2026-09-14 on a fresh install from the public ISO (drill intervention **I1**): the claim mail points at `https://felhom.drill0242.felhom.eu`; that name has **no A and no AAAA** record (`dig @1.1.1.1`, control `felhom.enkisfelhom.hu` resolves); the hub has **no tunnel- or DNS-creation code** (`hub/internal/cloudflare/` holds only geo-rule removal; `cf_tunnel_token` is a pasted, optional form field, `configs.go:1478`) — day-0 runbook A.1 makes it a manual Cloudflare-dashboard step that nothing on the customer-create page asks for; the box's own split-horizon resolver on the appliance LAN IP answered `google.com` but not the dashboard name at 13:27:39Z; the agent applied the record at **13:27:44Z** (`lanresolver: applied split-horizon record … ip=192.168.0.158`, 3 m 46 s after the controller started), so the box CAN answer the name — **but only to a device that uses the box as its DNS server, and no document, screen or mail tells a household to do that**; the router and the installer-offered DNS answer nothing. The page was reachable only at the guest's LAN address with the name forced (`curl --resolve …:443:192.168.0.158`). **A volunteer could not have done that.** **What it needs:** an operator ruling — the hub creates the tunnel and DNS at customer creation, or the product gives a household a LAN address that works with no DNS change. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: operator-side automation; the operator creates the tunnel by hand per the day-0 runbook.** | — | — | CC |
|
||||
| **R-504** | Install & onboarding | P4 | **[P3-LOW] `iso.felhom.eu` cannot show an index page on its own — its root returns 404, and the download page lives on the website instead.** MEASURED 2026-09-14: `https://iso.felhom.eu/` and `/index.html` → 404; only named objects answer. The host is an R2 bucket behind a custom domain; whether R2 would serve an uploaded `index.html` at `/` was **not measured** (uploading anything to the public bucket is a publication). The ISO v1.27.0 task puts the Hungarian download page at `felhom.eu/letoltes` (published with the ISO, after the operator's yes). **Remaining:** a redirect from `iso.felhom.eu/` to that page needs a Cloudflare rule the session has no credential for. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (Cloudflare rule)** **Re-ranked 2026-10-03: P3→P4: households are sent to the website's download page; the bare address is cosmetic.** | — | — | operator |
|
||||
|
||||
## Apps & catalog — 15 rows (P2 2, P3 8, P4 5)
|
||||
## Apps & catalog — 14 rows (P2 2, P3 7, P4 5)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@@ -134,10 +134,9 @@ stopping line that lies.
|
||||
| **R-994** | Apps & catalog | P3 | **Seven apps peak above 80 % of a memory limit, and 23 apps' typical use is above their catalog `mem_request`.** MEASURED 2026-10-13 (`documentation/audits/sizing-2026-10-13/SIZING.md` §3, `evidence/catalog-check.json`). Near a limit — risk: the container is stopped for using too much memory: docmost (container 475 of 512 MB), romm (703 of 768), termix (117 of 128), uptime-kuma (227 of 256, tester-1 telemetry), grafana (435 of 512), immich (`immich-server` 1,665 of 2,048 during a 200-photo import), tandoor (827 of 1,024). No container was killed during the bench. Under-stated request (typical ÷ request): calcom ×4.0, adventurelog ×3.6, termix ×3.4, claper ×2.5, grafana ×2.2, komga ×2.2, docmost ×2.1, recipe-importer ×2.1, uptime-kuma ×2.1, zipline ×2.0, and 13 more ≤ ×1.8. Templates NOT changed (read-only run). | **READY** — filed 2026-10-13 | — | Raise the seven limits and the 23 requests in their templates, each through the catalog's harness; re-measure Immich with a larger library first | CC |
|
||||
| **R-1018** | Apps & catalog | P3 | **The OnlyOffice links (FileBrowser, Nextcloud) have no recorded live proof since the move to Proxmox.** `controller/internal/integrations/` holds two handlers; no audit after the Proxmox move shows a document opened from FileBrowser or Nextcloud in OnlyOffice (searched 2026-10-14). The media-app links of `15` §6 will copy this code, including its revoke-on-stop rule (`lifecycle.go:10-13`). | **OPEN** — filed 2026-10-14 | — | One live walk on a Tier-0 box: install OnlyOffice + FileBrowser, open a document, save, read it back; then stop one app and record what the link does | CC |
|
||||
| **R-1028** | Apps & catalog | P3 | **Plex's claim code can run out during the install, and the box cannot recover it.** plex.tv's code lives 4 minutes from the moment the household copies it; the clock covers the wizard, the image pull (`plexinc/pms-docker` is 351 MB, measured) and the first start. Plex reads `PLEX_CLAIM` only on its first run, and the field is `locked_after_deploy` — so a late code leaves an unclaimed server and the only way back is remove + install again. Reported by the operator during the 2026-10-14 test; on Tester 1's line the pull+start took 8.5 s and the claim worked (`audits/media-all-drives-2026-10-14/``G-auth/G3-plex-claim.txt`), so it bites on a slow line or a slow hand. **K6, 2026-10-14 night (`audits/media-drive-events-2026-10-14/``K6-1-unclaimed-plex.txt`):** an unclaimed Plex starts with no code at all, and `POST /myplex/claim?token=` on the RUNNING server reaches plex.tv (a wrong code → 500 after plex.tv's 401). So the box can pull + start first and ask for the code after: no race. A real claim needs the household's code — the morning step on the throwaway `plex-k6` on Tester 1. Narrowed 2026-10-14 night. **PROVEN 2026-10-15 morning (`K6-2-claim-after-start.txt`):** the operator's real code, given to the RUNNING unclaimed `plex-k6` → 200, `claimed` 0→1, `PlexOnlineToken` written, and the open door closed (no token → 401). `ADVERTISE_IP` was unset and the claim did not need it; whether clients find the server without it was not measurable (the throwaway had no published port). **BUILT 2026-10-15 (v0.313.0):** `after_start` field — not asked at install; the Plex page asks for the code once Plex runs and claims the running server; the gate opens only when Plex says claimed. Proven up to the code on 9201 (G5). | **NARROWED** — the dashboard claim with a real code is the last step | a household Plex code | Claim through the Plex page with a real code (G5c); then close | CC |
|
||||
| **R-1060** | Apps & catalog | P2 | **On controller 0.313.0 (the floor) the Plex claim button can never work: Plex refuses the code with 401 because the box calls it as `http://plex:32400`.** Plex's own log: „Request came in with unrecognized domain / IP 'plex' in header Host; treating as non-local" → `401 POST /myplex/claim` in 0 ms (plex.tv never asked). Measured 2026-10-15 on demo-hp 9201, two codes, the second used ~1 minute after it was made (`audits/media-build-2026-10-15/G7/G5c-claim.txt`, `G5d-claim.txt`). The K6 spike on Tester 1 called the container by its IP address and got 200 (`audits/media-drive-events-2026-10-14/K6-2-claim-after-start.txt`); the product's `plexClaim` (`felhom-controller` `internal/web/media_events.go`) uses the name, and its test replaces the call with a seam, so no test ever met a real Plex. Since 0.313.0 does not ask the code in the wizard (`after_start`), a new Plex on any box at the floor stays unclaimed and behind its setup gate — safe, but unusable. | **OPEN** — filed 2026-10-15 | — | Dial Plex by the container's IP (or send a Host header Plex accepts); a test against a real Plex, red first; then claim 9201's waiting Plex with a fresh code | CC |
|
||||
| **R-1060** | Apps & catalog | P2 | **On controller 0.313.0 (the floor) the Plex claim button can never work: Plex refuses the code with 401 because the box calls it as `http://plex:32400`.** Plex's own log: „Request came in with unrecognized domain / IP 'plex' in header Host; treating as non-local" → `401 POST /myplex/claim` in 0 ms (plex.tv never asked). Measured 2026-10-15 on demo-hp 9201, two codes, the second used ~1 minute after it was made (`audits/media-build-2026-10-15/G7/G5c-claim.txt`, `G5d-claim.txt`). The K6 spike on Tester 1 called the container by its IP address and got 200 (`audits/media-drive-events-2026-10-14/K6-2-claim-after-start.txt`); the product's `plexClaim` (`felhom-controller` `internal/web/media_events.go`) uses the name, and its test replaces the call with a seam, so no test ever met a real Plex. Since 0.313.0 does not ask the code in the wizard (`after_start`), a new Plex on any box at the floor stays unclaimed and behind its setup gate — safe, but unusable. | **NARROWED** — filed 2026-10-15; the fix shipped in controller v0.314.0 (the floor since 13:11 UTC): all Plex calls by address (`mediawire.PlexURL`), red-proved against a fake Plex that refuses the name. Live on 9201 with a FAKE code through the dashboard's claim call: Plex took it as local and asked plex.tv (500 after 938 ms; before: 401 in 0 ms) — `audits/gate-restore-2026-10-15/plex9201/01-fake-code.txt`. A fifth call by name remains — the generic health probe's `GET :32400/identity` — and is harmless: `/identity` answers anyone (200 measured) | the operator's claim code | Claim 9201's waiting Plex through the dashboard with a fresh code (a watcher waits for `~/.felhom-demohp/plex-claim`); prove claimed, gate open, a stranger meets Plex's own login, one library wiring run; then close | CC |
|
||||
| **R-1029** | Apps & catalog | P3 | **Plex's folder picker shows the drives as identical „media” folders.** With one bind per drive at the same path (`/mnt/felhom-drives/<drive>/userdata/media`), the leaf of every library folder is `media`; the drive's name sits two levels up, so in Plex's picker the household sees several folders all called „media” (reported by the operator during the 2026-10-14 test on Tester 1). It conflicts with `15` §2 reason 2 (the same path everywhere). | **READY** — filed 2026-10-14 | the layout decision (STATUS D9) | Decide in `15` §2: the players (`:ro`, never linked) may get a readable path per drive (e.g. `/media/<drive label>`) without breaking the hardlink rule, which binds only the download/library apps | CC |
|
||||
| **R-1036** | Apps & catalog | P3 | **The apps page lags the catalog by two apps.** `app-catalog-felhom.eu/templates/` holds **64** apps; `/alkalmazasok` and `/en/apps` render **63** cards, one of which (`filebrowser`) is built in and not a catalog template — so **62 catalog apps are listed**. Missing: **`plant-it`** and **`wger`**, both in the catalog since 2026-10-06/08. Their website assets are ALREADY present (`plant-it-logo.svg` + 3 screenshots, `wger-logo.png` + 3 screenshots, renamed to the catalog slug by `a3507761`, 2026-10-08) — only the cards were never written, so this is not a new-asset job. Found 2026-10-14 while reconciling the app count for the „felhő tárhely” page, where four places said **60** and the rest said **62**. **Done in that session:** every stated count on the site set to **62** (`index.html`, `szolgaltatasok.html`, `en/services.html` were 60), which is what the apps page actually shows — so no visitor can count the cards and find the number wrong. **62 is still not the catalog's number.** **To close:** write the two cards on both apps pages (gate 15 keeps the two pages' app sets equal, so they move together), then raise every stated count 62 → 64 in the same commit. **The operator chose this split at the 2026-10-14 STOP** over doing it inside the SEO change. **2026-10-15 (R-1048):** Prowlarr's card added on both pages and every stated count 62 → 63 (the media group 19 → 20), so the site still states what it shows; the two owed cards would make it 65. **Note before writing them:** `wger` is `lifecycle: hidden` and `plant-it` `lifecycle: abandoned` — neither is offered for a new install, so a card would advertise an app the wizard refuses. | **OPEN** | — | write the `plant-it` + `wger` cards on both apps pages, then 62 → 64 everywhere, one commit | CC |
|
||||
| **R-1058** | Apps & catalog | P3 | **Prowlarr installed AFTER Sonarr/Radarr is not linked to them until the household presses „Bekötés most" (or a media app restarts).** The box's wiring runs when an app with the `media_library` marker comes up (`felhom-controller` `internal/stacks/manager.go` `onMediaUp`); Prowlarr has no marker (it uses no drive), so its own install starts no run. Measured 2026-10-15 on demo-hp 9201, live catalog, controller 0.313.0 (`audits/prowlarr-2026-10-15/box9201/link.txt`): Sonarr first → its run; Prowlarr after → no run in 150 s; „Bekötés most" wrote `application Sonarr`; Radarr after Prowlarr → linked by itself ~60 s later. The template copy and the website card tell the household this today. | **OPEN** — filed 2026-10-15 by the Prowlarr read-back | — | Start the wiring when Prowlarr comes up too (a list of link-only apps beside the marker, no drive binds); red-proof with the 9201 order (Sonarr, then Prowlarr); then drop the button sentence from the copy | CC |
|
||||
| **R-76** | Apps & catalog | P4 | **FileBrowser-created folders break the setgid chain, and a drop-zone's mode is not stable** **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-26, size S, roadmap state `idea (surfaced by the R-75 spike, 2026-07-26)`.** Moved verbatim; nothing added or reinterpreted. The roadmap keeps its copy as history, marked moved. | **OPEN — migrated from ROADMAP 2026-08-22, rank unchanged** **2026-10-06 night: not a catalog fix** — the setgid chain is set by the controller's FileBrowser setup; a controller row. | — | Two related findings from `audits/SPIKE-catalog-data-paths-2026-07-26.md` P3/P5, both **pre-existing** and deliberately left alone by that spike. **(a)** FileBrowser Quantum 1.3.3 creates files `0644` and folders `0755` and does **not** propagate the setgid bit — even though the entrypoint wrapper's `umask 002` really is in effect (`/proc/1/status` `Umask: 0002`). Group inheritance itself works (a file uploaded into a 2775 group-100 dir landed group 100, not the process gid 1000), so the convention's *group* half holds and only its *mode* half is lost. The consequence is proven with a control: inside a UI-created `0755` folder a gid-1000 process's file landed group **1000**, while the identical write into the 2775 parent landed group **100**. So **any folder a customer creates through FileBrowser breaks the shared-group chain one level down.** Latent today — every userdata-touching catalog app that declares an identity declares uid/gid **1000**, the same uid FileBrowser runs as, so owner permissions mask it; it bites the day a content app runs as a different non-root uid with gid 1000. The comment at `infra/infra.go:156` is right that the image ignores `-e UMASK` but does not say t | CC |
|
||||
| **R-707** | Apps & catalog | P4 | **[P2] 37 apps still start with a login a stranger can take (`09` §3 decision 45).** Audit of all 53 apps: `app-catalog-felhom.eu/FIRST-ADMIN.md` (class, fix route, status, measured or read). Open: **3 hard-coded defaults** — calibre-web (`admin / admin123`, measured working on demo-hp and 9202; its own `cps.py -s` route needs a generated password WITH a special character — our generator is letters+digits, a controller change), mealie (`changeme@example.com / MyPassword`), wger (`admin / adminadmin`); **34 open first-run screens** (the first visitor creates the admin: actualbudget, adventurelog, audiobookshelf, calcom, docmost, emby, ghost, gitea, gramps-web, home-assistant, homebox, immich, jellyfin, komga, n8n, navidrome, opengist, outline, papra, plant-it, radarr, rallly, recipe-importer, romm, seerr, sonarr, sparkyfitness, tandoor, termix, uptime-kuma, vikunja, wanderer, wishlist, zipline). **Stale notes:** romm's `default_creds` `admin / admin` answers 401 on demo-hp (like a wrong password) — the page now warns with a login that does not exist; zipline's looks stale too. **Measured on demo-hp 2026-09-28 (read-only):** bookstack's default still logs in on the INSTALLED app (the fix is for new installs; the page now warns). Each fix: route (a) env or (b) the app's own CLI/API via `after_install:`, proven on 9202 with the default failing and the generated password working; route (c) a page sentence. Several sessions (operator, 2026-09-28). **2026-09-29 (controller v0.280.0, catalog `d0e7e2e`):** every class-3 app fixed — mealie, wger, calibre-web by `after_install` (calibre-web with the new `password:24:special`), proven on 9202 fresh installs (`audits/login-gate-2026-09-29/D/`); the setup gate (decision 46, spike PASSED) built and live on immich, n8n, audiobookshelf (probes measured) and uptime-kuma (button) (`…/C/`); romm's and zipline's stale notes removed. **Left: 30 class-4 apps** — gate each (probe measured on 9202 where one exists — 11 upstream candidates listed in `…/B/B-VERDICT.md` §3; the button otherwise). **2026-09-29 afternoon (controller v0.281.0, catalog `6faf432`):** 28 more class-4 apps gated — 32 of 34 — each proven on 9202 (`audits/gate-rollout-2026-09-29/`B): stranger → gate page / 401, household reached the first-setup screen, the gate opened (9 by a measured probe, the rest by the press), the app answered after. seerr, outline, rallly: gated, their opening needs a media server / e-mail (not proven). **Left:** wanderer (R-714); plant-it is not installable. | **NARROWED** (2026-10-03 triage: the row's verdict was finished, but it names open work no other row carries — seerr, outline and rallly are gated, but the gate OPENING is not proven (needs a media server / e-mail)) — **CLOSED — 2026-09-29 (the rest → R-714)** | — | — | CC |
|
||||
| **R-905** | Apps & catalog | P4 | **wger's collected style files (283 MB) go into every wger backup, though the app rebuilds them at every start.** Since R-762's fix (`DJANGO_DEBUG=False` + `collectstatic`, catalog `cf1ed43`) the static files sit in a named volume that the backup legs copy like data (measured 283 MB, `audits/design-build-2026-10-06/`); `collectstatic` regenerates them, so they are rebuildable bytes in every Tier-1 unit, Tier-2 copy and off-site snapshot. wger is `lifecycle: hidden` and no box runs it (hub read 2026-10-08). Options to weigh: a non-backed-up volume class for regenerable data, or an anonymous volume. Found by the R-762 helpers 2026-10-08. | **OPEN — needs a design before wger is shown** | — | Design the „regenerable volume" exclusion (catalog + controller backup legs) | CC |
|
||||
@@ -189,16 +188,15 @@ stopping line that lies.
|
||||
| **R-878** | Backup & restore | P4 | **A catch-up (R-871) runs the database-dump leg in the DAY, and that leg stops an app with a volume for its copy — the household may notice the stop, and a large volume makes it longer.** MEASURED 2026-10-05 on demo-felhom: the catch-up at 08:25:02 stopped opengist, copied 182.5 KB, started it again — about 1 s, then a few seconds of `health: starting`; the night does exactly the same, unseen. Nothing measured for a large volume. Fix direction (if it matters): skip the volume copy of a running app in a DAYTIME catch-up and leave it to the next night, or warn. `audits/catchup-2026-10-05/partA/live-demo-felhom.txt` | **READY — owner: CC** **2026-10-05 (burn-down night): NEEDS A LIVE MEASUREMENT** — a timed catch-up on 9202 for an app with a multi-GB volume, then the skip-or-warn choice. | — | measure a large volume first | CC |
|
||||
| **R-32** | Backup & restore | P4 | **[P2-HIGH] RESET must purge the customer base dir; the orphan card must stay honest; unattributed bytes must be visible.** The rehearsal's S7 said in advance that an orphan card would BE a finding — and one appeared (16:58:14). Cause: RESET's `"hetzner":"ok"` leg destroys the sub-account, but **a Hetzner sub-account is an access-control object, not a data object** — its directory survives, so re-enabling offsite recreated an account over the previous lifecycle's ciphertext, encrypted under a key that same RESET had destroyed. **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-21, size M, roadmap state `idea`.** Moved verbatim; nothing added or reinterpreted. The roadmap keeps its copy as history, marked moved. | **OPEN — migrated from ROADMAP 2026-08-22, rank unchanged** **2026-10-06 night: design written** (`audits/night-burndown-2026-10-06/design-R-32.md`) — pick A: purge the repo + set-aside copies through the sub-account's own password login BEFORE deleting it (no main-account credential); the „repo data destroyed" comment at `hub/internal/offsite/offsite.go:276-280` is false for the shared tier; part (3) waits on a read-only measurement (`du` on port 23). Operator: agree to route A. **2026-10-07 07:58: `09` §3 decision 167 — option A yes, and delete the old RESET leftovers once.** **2026-10-07 (Part D step 2, read only):** the hub shows bx11 holding 3.3 GB of data while the four live customers account for ~0.6 GB → **~2.7 GB belongs to no live customer**; no live customer shows a set-aside folder, so the leftovers are in homes whose sub-account is gone, reachable only by the pool box's MAIN account. **The one-time clean-up is STOPPED:** no main-account login for bx11 is held on DooPlex (the API token in the credentials file sees no box). Needs: the main-account login, or the operator lists and deletes in the Hetzner console (`audits/day-2026-10-07/D/D2-listing-readonly.txt`). **2026-10-07: option A DELIVERED (hub 0.142.0):** RESET purges the repo and every `.orphaned-*` through the sub-account's own login before deleting it, and keeps the sub-account if the purge fails (red-proved, `audits/day-2026-10-07/D/`). Not yet seen live (no scratch RESET today). **The one-time clean-up of the ~2.7 GB of leftovers is STOPPED:** it needs the pool box's main-account login (see above). **2026-10-07 14:43 — `09` §3 decision 173: leave the ~2.7 GB of old leftovers for now; re-ranked P2 → P4.** Option A itself is delivered (hub 0.142.0). What stays open is ONLY the leftovers: they were computed (box total minus live customers), never listed, and deleting them needs the pool box's MAIN login, which reaches every customer's data. **Blocker: a safe main-account path.** | — | **Ruling from the run (three parts, deliberately separate):** (1) because RESET destroys custody, the ciphertext it leaves behind is unrecoverable **BY DESIGN** → RESET gains a **main-account purge of the customer base dir** (the existing operator ack already covers it); (2) the **move-aside guard STAYS** for reinstall-*without*-RESET — there custody survives and the card's "history recoverable" promise is true (R-26 depends on exactly that); (3) the operator **Restic tab shows per-customer directory bytes vs attributed snapshot bytes**, so dead data cannot hide. Measured on the pool box that night: **49 M attributed** (2 snapshots, 48.717 MiB) against **1.4 G + 3.0 M unattributed** across TWO `.orphaned-*` dirs. Evidence `restic-and-pool.txt` | CC |
|
||||
|
||||
## Storage & devices — 4 rows (P2 1, P3 2, P4 1)
|
||||
## Storage & devices — 3 rows (P3 2, P4 1)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **R-1023** | Storage & devices | P2 | **Build: media apps see every drive, and the box says when they must restart (operator rulings 2026-10-14, `09` §3).** Scope from `architecture/15-media-library.md`: one bind per drive of `userdata/media/<library folders>` at the same path; the drive-absent gate per bind (today it keys on the app's one `HDD_PATH`, and the boot gate waits only for THAT drive — measured after the 17:25 power cut); the restart notice after a drive is added, removed or comes back, with „restart now” / „later” and a per-drive „apps restarted since” mark; removal, export and backup classes taught that a shared library is no app's own (R-1027, R-1019). **S5/S6 measured 2026-10-14:** a pulled drive stops none of the media apps (they key on another drive), they keep a dead view; on return nothing restarts them — so the restart notice MUST also cover „a drive came back”, the restart must be stop+start, and Sonarr/Radarr's own health page says nothing about an unreachable root folder, so the box must say it. **Night 2026-10-14 measured every event × app (`15` §10, `audits/media-drive-events-2026-10-14/`):** nothing is lost while a drive is gone (K1), so the build needs NO scan/unmonitor/torrent guard; it DOES need: (1) one bind per PRESENT drive only — a bind to an absent path makes the app unstartable (K3-4: 500 „error while creating mount source path … permission denied”); (2) the media apps recreated without the drive BEFORE a safe removal unmounts it (R-1044); (3) a returned drive restarts every container that binds it, FileBrowser included (K5: 2–10 s per app); (4) „restart now” cuts a playing stream (K5-4: cut, back in 22 s) — check Plex/Jellyfin sessions first or offer „later”; (5) the stale view is found with NO disk I/O: each container's `/proc/<pid>/mountinfo` shows the old device with `shutdown` (K4). Narrowed 2026-10-14 night. **BUILT 2026-10-15 — controller v0.313.0 + catalog `0188430` (`15` §11; `audits/media-build-2026-10-15/`): proven on Tester 1 (G1–G4, G6) and demo-hp 9201 (G7).** What is left is delivery. **2026-10-15 11:29: the operator vouched golden 0.313.0 and raised the global floor to 0.313.0 (read back from the hub DB).** demo-felhom moved to 0.313.0 by itself through the floor; demo-hp was put back on 0.313.0 by hand (it ran the test tag rc.4 and has its own floor override 0.311.0); Tester 1 still runs rc.4, which the floor never moves (R-1053) — fenced in the 2026-10-15 afternoon task. | **NARROWED** — delivered by the floor; Tester 1 waits on R-1053's step | R-1053 (Tester 1 back on a release tag) | Put Tester 1 on the 0.313.0 tag (or let R-1053's fix move it); read it back; close | CC |
|
||||
| **R-330** | Storage & devices | P3 | **Disk health Phase 2 — the three SMART attributes the wire does not carry.** The failing drive's most telling counter was **187 `Reported_Uncorrect`**, sitting at normalized **1** against threshold **0** with a raw count of **1001** — one point from failing and structurally unable to get there. Also wanted: **199 `UDMA_CRC_Error_Count`** (cabling) and **188 `Command_Timeout`**. None are on the agent→controller wire today, so v0.215.0's ladder could not use them. Phase 2 also persists periodic SMART **samples** (the right home is `metrics.MetricsStore`, NOT the Phase-1 state file, which is one record per disk and must stay that way). **This is a declared WIRE change, so under the G-1 gate the hub must model the new fields in the SAME session** — that is precisely why it was kept out of Phase 1, where it would have turned a one-word severity fix into a three-repo change | **READY** — **2026-10-12: the wire half is CLOSED in this row** — released in agent v0.150.0 (boxes run 0.154.0), controller and hub decode 187/188/199. **Only open work:** the verdict slice (use the three counters in the disk verdict and the household's words) and persisting samples. **READY (M) — NEW 2026-08-14** **2026-10-06 night: the wire half BUILT on main (unreleased):** the agent sends SMART 187 `reported_uncorrect`, 188 `command_timeout` (raw, vendor-packed on some drives — carried as reported), 199 `udma_crc_errors` (pointer + omitempty: unknown is absent, never 0); the controller decodes them and the hub's host-report mirror models them (G-1 wire gate green; it convicted the agent half alone). **Carried only** — no verdict, banner, mail or alarm reads them (`TestR330_CountersChangeNoVerdictYet`); using them changes what a household is told and is the next slice, with persisting samples. Red-proofs `audits/night-burndown-2026-10-06/r330/`. Ships with agent v0.150.0 and the next controller and hub releases. | R-328 (closed) | The verdict slice: 187/188/199 into the disk verdict with tests; then persist samples | CC |
|
||||
| **R-332** | Storage & devices | P3 | **The new Hiba-from-counters path has never fired on real hardware.** v0.215.0's whole point is a verdict the product could not previously reach, and it is proven only against the committed fixture's values in unit tests (12 scenario groups, 11 of 12 red-proofs failing as required). The live validation on demo-hp proved the **negative** — three healthy disks still read Rendben across the deploy, no false alert — and the **severity wire** end to end, but no live disk has actually reached Hiba. **This is the honest gap and it must not be closed by pointing at the fixture tests**: the drive that produced the fixture is in DooPlex, which is Tier 2 and never a drill target, and the demo boxes are all-flash and healthy | **WATCHING — NEW 2026-08-14, NARROWED same day.** One item originally in this gap is now PROVEN LIVE: the **persisted state surviving a controller restart**. The v0.215.0→v0.216.0 redeploy destroyed and rebuilt the container, and the new one read back a `changed_at` written by the PREVIOUS version (`2026-08-14T07:23:14.640216851Z`, still intact at 09:31:35Z) instead of re-baselining — Scenario L on real hardware, not just the production-path unit test. **What remains unproven is the verdict itself, plus the stronger restart half: an already-ALERTED disk not re-alerting** | a real degrading disk, or an injection harness | **Closing condition:** a live disk reaching Hiba from counters, OR a deliberate injection through the REAL pipeline (agent `/disks` → controller check → hub event), not a hand-set verdict | CC |
|
||||
| **R-331** | Storage & devices | P4 | **Disk health Phase 3 — growth-rate detection, and retiring the static 64.** The v0.215.0 count backstop (64 unreadable sectors → Hiba) is **a judgement from ONE drive**: the observed benign excursion peaked at 16 and cleared inside an hour, and the terminal run passed 64 at 13 Aug 11:28 and never came back. It is deliberately a backstop BEHIND the sustain rule, not the primary signal, but it is still a magic number tuned on a single sample and it will be wrong for some drive. With Phase 2's history the box can ask the question that actually matters — *is this count climbing, and how fast* — which distinguishes a drive with eight stable aging sectors from one adding forty a day, something no static threshold can do. Revisit 64 when that exists | **READY (M) — NEW 2026-08-14** | R-330 | Growth-rate rule over persisted samples; re-derive or delete the static 64 | CC |
|
||||
|
||||
## Security & access — 22 rows (P1 2, P2 3, P3 11, P4 6)
|
||||
## Security & access — 21 rows (P1 2, P2 3, P3 10, P4 6)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@@ -223,9 +221,8 @@ stopping line that lies.
|
||||
| **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 |
|
||||
| **R-1050** | Security & access | P4 | **Raise HSTS to one year on or after 2026-11-15, if no https trouble was reported.** `09` §3 decision 249 (D2 = A, operator 2026-10-15): HSTS stays `max-age=86400` now; after a month with no https trouble it becomes one year (`31536000`); never preload, never `includeSubDomains`. The value is `HSTSMaxAgeSeconds` in `felhom-controller/controller/internal/infra/infra.go`, pinned by `TestRenderForwardedHeaders_HTTPSOnly`. „Trouble” = any report of a name that will not open, or a box answering http after the floor. | **WATCHING** — filed 2026-10-15 | the date | On or after 2026-11-15: read the register and the operator's mail for https trouble since 2026-10-15; none → a controller release with one year (test updated), bake → vouch → floor | CC |
|
||||
| **R-1054** | Security & access | P3 | **The hub's operator/global API key is still one bearer that can do everything on `/api/v1`, including reading every box's break-glass root password (`GET /admin/hosts/{id}/recovery-credential`).** Hub v0.152.0 gave the web surface scoped tokens (`05` §17) but tokens are refused on `/api/v1` by design. Who uses the global key today is not listed in one place. Fix: list its consumers; move each operator use to a scoped token on the web side (or a token class on the API), and keep the global key only where a box path needs it — or retire it. | OPEN — filed 2026-10-15 by the token build | — | List the global key's consumers (pod env, scripts, runbooks) | CC |
|
||||
| **R-1057** | Security & access | P3 | **A restore of a REMOVED gated app comes back without its setup gate, even when the backup was taken before the household's setup — so the app returns open, with no account.** Measured 2026-10-15 on 9202 with Prowlarr (`audits/prowlarr-2026-10-15/box/run3/restore.txt`): installed (gated, no account made), the guarded Update's per-app backup, remove keeping the backups, the household's restore button — afterwards a stranger's `/ping` through traefik answered 200, where before the remove it answered 401 (the gate). This follows the documented rule (`felhom-controller` `internal/stacks/life_records.go` `carryLifeRecords`: no prior record = no gate, „the data comes back with its admin"); the rule's assumption fails for a backup made before the setup. For a first-visitor app (Prowlarr, n8n, …) the first stranger sets the sign-in; for Prowlarr `/initialize.json` also hands out the API key (seen before the setup in the install window — NOT measured after this restore). Needs: the household to install, let a backup run before finishing the setup, remove, restore. | **OPEN** — filed 2026-10-15 by the Prowlarr walk | — | Record the gate's state in the per-app backup and re-close it on restore when it was closed then; red-proof with this walk (`tools/walk_restore.py`) | CC |
|
||||
|
||||
## Box system & updates — 13 rows (P2 1, P3 9, P4 3)
|
||||
## Box system & updates — 13 rows (P2 1, P3 8, P4 4)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@@ -240,8 +237,8 @@ stopping line that lies.
|
||||
| **R-1004** | Box system & updates | P4 | **After an unclean stop every guest ext4 volume waits ~43 s in multi-mount protection before it mounts, so each restart after a power cut takes ~1.5 min longer.** Kernel: „EXT4-fs warning (device dm-6): ext4_multi_mount_protect: MMP interval 42 higher than expected, please wait" on dm-6/dm-7 and loop0/loop1, demo-hp and Tester 1, every round of `audits/night-power-2026-10-13/` (R1 `repairs.txt`). | **READY** — filed 2026-10-14 | — | CC: find where MMP is switched on for the guest volumes (Proxmox `pct` create, or our own mkfs), whether it protects anything on a one-host disk, and measure the restart without it on a scratch guest | CC |
|
||||
| **R-1013** | Box system & updates | P4 | **After a power cut the agent's start-up stale-lock pass logs an ERROR „pct unlock failed … no lock found" when Proxmox's own start-all removed the lock in the same second.** The outcome is the goal (no lock), so the line is a false alarm in the log. Seen on demo-hp 2026-10-14 12:07:44–45 UTC (`audits/power-cut-fixes-2026-10-14/E/after-boot.txt`; Proxmox: „removed left over backup lock from '9201'!"). Small, but the agent's release for this session is spent (v0.156.1). | **OPEN** — filed 2026-10-14 | — | CC: treat „no lock found" from `pct unlock` as already clear (INFO), with a test; ride the next agent release | CC |
|
||||
| **R-1010** | Box system & updates | P3 | **The agent's stale-lock janitor cannot remove a half-removed `vzdump` snapshot: Proxmox's delete fails on the first missing volume („Failed to find logical volume pve/snap_vm-9201-disk-0_vzdump") and the janitor retries the same call every 10 min forever, leaving the other volume's snapshot and the snapshot entry.** Seen 2026-10-14 after a backup ran out of space on a full host root (LVM could not write its metadata; `audits/power-cut-fixes-2026-10-14/README.md` C4). Fixed by hand with `pct delsnapshot 9201 vzdump --force`. | **READY** — filed 2026-10-14 | — | CC: on that error, retry once with `--force` (a sudoers line or the API's `force=1`), with a test that replays the error text; next agent release | CC |
|
||||
| **R-1052** | Box system & updates | P3 | **The new-box path is not proven: a fresh install from a golden older than the floor reaching the floor by itself at first boot.** `09` §3 decision 250 relies on it (the golden is weekly, releases ride the floor). Proof owed at the next controller release: a throwaway VM on demo-hp installed from the ISO (the vouched golden one release behind the floor); time from claim to the floor; the controller's self-update log lines; then the VM and its hub records removed. | **OPEN** — filed 2026-10-15 | the next controller release | Run it at the next release; quote the times | CC |
|
||||
| **R-1053** | Box system & updates | P3 | **A box running a controller test tag (`0.313.0-rc.N`) is never moved by the floor, and says so only at DEBUG.** `util.ParseVersion` wants X.Y.Z, so `maybeAutoUpdate` logs `current … not parseable (dev?) — skip` and the box stays on the test build forever (measured 2026-10-15 on demo-hp and Tester 1 after the floor reached 0.313.0; demo-felhom, on a release tag, moved by itself). The same parser keeps an rc tag off the household's update button — that half is wanted. Today's guard is procedural (the `felhom-build-deploy` skill: write the release tag back after testing). Tester 1 still runs rc.4. | **READY** — filed 2026-10-15 | — | A controller fix: treat `X.Y.Z-rc.N` as a pre-release BELOW `X.Y.Z` for the floor (and keep it off the button), log the skip at WARN; a test per case. Meanwhile put Tester 1 on 0.313.0 | CC |
|
||||
| **R-1052** | Box system & updates | P3 | **The new-box path is not proven: a fresh install from a golden older than the floor reaching the floor by itself at first boot.** `09` §3 decision 250 relies on it (the golden is weekly, releases ride the floor). Proof owed at the next controller release: a throwaway VM on demo-hp installed from the ISO (the vouched golden one release behind the floor); time from claim to the floor; the controller's self-update log lines; then the VM and its hub records removed. | **BLOCKED** — filed 2026-10-15; not run with the 0.314.0 release: the proof needs a throwaway customer, and creating a customer is session only since hub 0.153.0 (`09` §3 decision 252) — CC's token is refused by design | the operator: create a throwaway customer (or name one to reuse) in the browser | Then: a VM on demo-hp from the current ISO, claimed as that customer; time from claim to the floor; remove the VM; the operator deletes the customer (session only) | CC |
|
||||
| **R-1061** | Box system & updates | P4 | **A per-customer floor reached Tester 1 only at its next 15-minute report, not in seconds.** Set 2026-10-15 12:55:51 UTC for tester-1, demo-hp and demo-felhom together (hub 0.153.0, CC's token): demo-hp and demo-felhom ran 0.314.0 within 13 s (the hub's wake); Tester 1 moved at 13:09:35, its regular report (`audits/gate-restore-2026-10-15/release/03-boxes-move.txt`). It had restarted at 12:54:29 onto rc.1 and logged „hub wait channel active". Why the wake did not reach it is not read (the wake line is DEBUG). No harm: the floor arrived. | **OPEN** — filed 2026-10-15 | — | Read Tester 1's DEBUG ring at the next floor change; compare with the hub's bump log | CC |
|
||||
|
||||
|
||||
## Monitoring & notifications — 10 rows (P2 1, P3 6, P4 3)
|
||||
|
||||
@@ -55,11 +55,14 @@ Check current live version first: same `docker ps` command, or `cat /etc/felhom-
|
||||
|
||||
1. **Test images get an `-rc.N` tag** (`./build.sh 0.313.0-rc.1 --push`). The household's update button takes the HIGHEST
|
||||
tag in the registry, and an `-rc` tag is not a version to it (`util.ParseVersion` wants X.Y.Z) — so it is never offered.
|
||||
**But a box running an `-rc` tag is never moved by the floor either** (`maybeAutoUpdate: current … not parseable —
|
||||
skip`, DEBUG only): after testing, write the release tag back into `/etc/felhom-controller-image` (R-1053).
|
||||
**Since controller 0.314.0 a box on `X.Y.Z-rc.N` is BELOW `X.Y.Z` for the floor** (`util.ParseVersionPre`, R-1053), so
|
||||
the release's floor moves it by itself (proven: Tester 1, rc.1 → 0.314.0). A box on an rc built BEFORE 0.314.0 runs the
|
||||
old check and is never moved: put it on a new rc (or the release tag) by hand, through `/etc/felhom-controller-image`.
|
||||
2. **Push the release tag only when it is ready for EVERY box** — from that second every household's button offers it.
|
||||
3. **The three test boxes through the normal update path:** a per-customer floor for Tester 1, demo-hp and demo-felhom at
|
||||
the release, MinAgent declared; read back each box's `docker ps` and its `SetFloor`/self-update log lines.
|
||||
the release, MinAgent declared — `POST /customers/<id>/floor` with the same token and fields as step 5 (ids: `tester-1`,
|
||||
`demo-hp`, `demo-felhom`); read back each box's `docker ps` and its `SetFloor`/self-update log lines. A box can take up to
|
||||
its next 15-minute report (R-1061). After the global floor, clear them: the same POST with both fields empty.
|
||||
4. **The box walk green on all three** — `python3 scripts/https_only_check.py` included (felhom.eu).
|
||||
5. **Raise the global floor**, MinAgent declared (from the CHANGELOG header), with **CC's hub token** — never the
|
||||
operator password (`09` §3 decision 251; hub ≥ 0.152.0, `05` §17). The token is `HUB_TOKEN` in
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
## 2026-10-15 — Prowlarr on the apps pages; the count 62 → 63 (R-1048)
|
||||
## 2026-10-15 (evening) — Prowlarr's card: no button any more (R-1058)
|
||||
|
||||
- The card says the box links Prowlarr to Sonarr and Radarr whichever is installed first (controller 0.314.0, the floor).
|
||||
|
||||
## 2026-10-15 — Prowlarr on the apps pages; the count 62 → 63 (R-1048)
|
||||
|
||||
- A Prowlarr card after Radarr on `alkalmazasok.html` and `en/apps.html` (the logo arrived in the commit before). The
|
||||
card says the box links it to Sonarr and Radarr, with at most one button press (the controller links it by itself only
|
||||
|
||||
@@ -547,7 +547,7 @@
|
||||
<div class="app-title-area"><h3>Prowlarr</h3><span class="app-tagline">Letöltési források egy helyen</span></div>
|
||||
</div>
|
||||
<div class="app-card-body">
|
||||
<p class="app-description">A letöltési forrásaidat (indexereket) egyszer adod meg, és a Sonarr meg a Radarr magától megkapja őket. A gép köti össze őket; legfeljebb egy gombot nyomsz meg hozzá. Saját meghajtót nem használ.</p>
|
||||
<p class="app-description">A letöltési forrásaidat (indexereket) egyszer adod meg, és a Sonarr meg a Radarr magától megkapja őket. A gép köti össze őket, bármelyiket telepíted előbb. Saját meghajtót nem használ.</p>
|
||||
<div class="app-features"><span class="app-feature">Egyszer kell megadni</span><span class="app-feature">Sonarr és Radarr</span><span class="app-feature">A gép köti össze</span></div>
|
||||
</div>
|
||||
<div class="app-card-footer">
|
||||
|
||||
@@ -547,7 +547,7 @@
|
||||
<div class="app-title-area"><h3>Prowlarr</h3><span class="app-tagline">Download sources in one place</span></div>
|
||||
</div>
|
||||
<div class="app-card-body">
|
||||
<p class="app-description">Enter your download sources (indexers) once, and Sonarr and Radarr get them by themselves. The box links them; at most you press one button. It uses no drive of its own.</p>
|
||||
<p class="app-description">Enter your download sources (indexers) once, and Sonarr and Radarr get them by themselves. The box links them, whichever you install first. It uses no drive of its own.</p>
|
||||
<div class="app-features"><span class="app-feature">Enter once</span><span class="app-feature">Sonarr and Radarr</span><span class="app-feature">The box links them</span></div>
|
||||
</div>
|
||||
<div class="app-card-footer">
|
||||
|
||||
Reference in New Issue
Block a user