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:
@@ -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)
|
||||
Reference in New Issue
Block a user