Media all drives: Prowlarr on Tester 1 (drill only) wired to Sonarr/Radarr by wire.py; sign-in check (outsiders are asked; home network and containers are not); R-1028..R-1032
gates / gates (push) Successful in 6m31s
gates / gates (push) Successful in 6m31s
This commit is contained in:
@@ -103,6 +103,18 @@ directory) with that tag. Facts the link code needs:
|
||||
- **The OnlyOffice revoke-on-stop rule must NOT apply** (`integrations/lifecycle.go:10-13`): it would delete the download
|
||||
entries whenever a download app restarts. And the OnlyOffice links themselves have no live proof since Proxmox
|
||||
(R-1018).
|
||||
- **Prowlarr holds the indexers ONCE** and pushes them (Settings → Apps, „Full Sync”) to Sonarr and Radarr by container
|
||||
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.
|
||||
- **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):
|
||||
`incomplete-dir-enabled=false` was back to `true` after the 17:25 cut. The link code writes it into `settings.json`
|
||||
at start (as the template's entrypoint does for port forwarding), not by RPC alone.
|
||||
- **Sign-in (R-1030):** with „Disabled for Local Addresses” an outside visitor IS asked (the tunnel keeps the public
|
||||
address in `X-Forwarded-For`), but the home network and every other container get in with no sign-in, the API key
|
||||
included. The box seeds „Enabled”.
|
||||
- Plex and Jellyfin are NOT linked: they watch folders. Indexers stay the household's choice.
|
||||
- **Plex behind the tunnel** needs `ADVERTISE_IP=https://<sub>.<domain>:443/` so plex.tv hands apps a reachable
|
||||
address (measured: `customConnections` set; public `/identity` 200; anonymous library calls 401).
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# 2026-10-14T18:23:01+02:00 — from DooPlex over the PUBLIC names (DNS → Cloudflare 2606:4700:…, the same tunnel path as any outside visitor); no cookie, no key, no login
|
||||
sonarr/ -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
sonarr/api/v3/system/status -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
sonarr/api/v3/series -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
sonarr/api/v3/movie -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
sonarr/initialize.json -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
sonarr/login -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
radarr/ -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
radarr/api/v3/system/status -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
radarr/api/v3/series -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
radarr/api/v3/movie -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
radarr/initialize.json -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
radarr/login -> 401 redirect= bytes=51 | {"error":"this app is waiting for its first setup"}
|
||||
@@ -0,0 +1,78 @@
|
||||
sonarr via docker network (source 172.18.x = traefik-like), header [none]: /api/v3/system/status -> 401
|
||||
/ -> 200
|
||||
sonarr via docker network (source 172.18.x = traefik-like), header [X-Forwarded-For: 203.0.113.7]: /api/v3/system/status -> 401
|
||||
/ -> 302 http://sonarr:8989/login?returnUrl=%2F
|
||||
sonarr via docker network (source 172.18.x = traefik-like), header [X-Real-Ip: 203.0.113.7]: /api/v3/system/status -> 401
|
||||
/ -> 200
|
||||
sonarr via docker network (source 172.18.x = traefik-like), header [X-Forwarded-For: 203.0.113.7, 172.18.0.2]: /api/v3/system/status -> 401
|
||||
/ -> 302 http://sonarr:8989/login?returnUrl=%2F
|
||||
radarr via docker network (source 172.18.x = traefik-like), header [none]: /api/v3/system/status -> 401
|
||||
/ -> 200
|
||||
radarr via docker network (source 172.18.x = traefik-like), header [X-Forwarded-For: 203.0.113.7]: /api/v3/system/status -> 401
|
||||
/ -> 302 http://radarr:7878/login?returnUrl=%2F
|
||||
radarr via docker network (source 172.18.x = traefik-like), header [X-Real-Ip: 203.0.113.7]: /api/v3/system/status -> 401
|
||||
/ -> 200
|
||||
radarr via docker network (source 172.18.x = traefik-like), header [X-Forwarded-For: 203.0.113.7, 172.18.0.2]: /api/v3/system/status -> 401
|
||||
/ -> 302 http://radarr:7878/login?returnUrl=%2F
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
http:
|
||||
redirections:
|
||||
--
|
||||
forwardedHeaders:
|
||||
trustedIPs:
|
||||
- "172.16.253.2/32"
|
||||
http:
|
||||
middlewares:
|
||||
- felhom-forwarded@file
|
||||
|
||||
/etc/traefik:
|
||||
acme.json
|
||||
certs
|
||||
dynamic
|
||||
traefik.yml
|
||||
|
||||
/etc/traefik/dynamic:
|
||||
controller.yml
|
||||
forwarded.yml
|
||||
serverstransports.yml
|
||||
setup-gate-radarr.yml
|
||||
setup-gate-sonarr.yml
|
||||
/etc/traefik/dynamic/forwarded.yml-5-http:
|
||||
/etc/traefik/dynamic/forwarded.yml-6- middlewares:
|
||||
/etc/traefik/dynamic/forwarded.yml:7: felhom-forwarded:
|
||||
/etc/traefik/dynamic/forwarded.yml-8- headers:
|
||||
/etc/traefik/dynamic/forwarded.yml-9- customRequestHeaders:
|
||||
/etc/traefik/dynamic/forwarded.yml-10- X-Forwarded-Port: "443"
|
||||
/etc/traefik/dynamic/forwarded.yml-11- X-Forwarded-Host: ""
|
||||
/etc/traefik/dynamic/forwarded.yml-12- X-Forwarded-Uri: ""
|
||||
/etc/traefik/dynamic/forwarded.yml-13- X-Forwarded-Method: ""
|
||||
/etc/traefik/dynamic/forwarded.yml-14- X-Forwarded-Prefix: ""
|
||||
/etc/traefik/dynamic/forwarded.yml-15- X-Forwarded-Tls-Client-Cert: ""
|
||||
/etc/traefik/dynamic/forwarded.yml-16- X-Forwarded-Tls-Client-Cert-Info: ""
|
||||
/etc/traefik/dynamic/forwarded.yml-17- Forwarded: ""
|
||||
/etc/traefik/dynamic/forwarded.yml-18- True-Client-Ip: ""
|
||||
/etc/traefik/dynamic/forwarded.yml-19- X-Client-Ip: ""
|
||||
--
|
||||
/etc/traefik/traefik.yml-22- http:
|
||||
/etc/traefik/traefik.yml-23- middlewares:
|
||||
/etc/traefik/traefik.yml:24: - felhom-forwarded@file
|
||||
/etc/traefik/traefik.yml-25- tls:
|
||||
/etc/traefik/traefik.yml-26- certResolver: letsencrypt
|
||||
/etc/traefik/traefik.yml-27-
|
||||
/etc/traefik/traefik.yml-28-providers:
|
||||
/etc/traefik/traefik.yml-29- docker:
|
||||
/etc/traefik/traefik.yml-30- endpoint: "unix:///var/run/docker.sock"
|
||||
/etc/traefik/traefik.yml-31- exposedByDefault: false
|
||||
/etc/traefik/traefik.yml-32- network: traefik-public
|
||||
/etc/traefik/traefik.yml-33- file:
|
||||
/etc/traefik/traefik.yml-34- directory: /etc/traefik/dynamic
|
||||
/etc/traefik/traefik.yml-35- watch: true
|
||||
/etc/traefik/traefik.yml-36-
|
||||
sonarr /initialize.json [none] -> 200, contains apiKey field: 1
|
||||
sonarr /initialize.json [X-Forwarded-For: 203.0.113.7] -> 302, contains apiKey field: 0
|
||||
radarr /initialize.json [none] -> 200, contains apiKey field: 1
|
||||
radarr /initialize.json [X-Forwarded-For: 203.0.113.7] -> 302, contains apiKey field: 0
|
||||
|
||||
WebAPI login failure. Reason: invalid credentials, attempt count: 1, IP: 37.191.56.193, username: nobody
|
||||
@@ -0,0 +1,19 @@
|
||||
created 2026-10-14T16:04:53.056354746Z started 2026-10-14T16:04:53.476264772Z
|
||||
Attempting to obtain server token from claim token
|
||||
Token obtained successfully
|
||||
Plex Media Server first run setup complete
|
||||
Starting Plex Media Server.
|
||||
2026/10/14 15:41:23 [INFO] [api] Remove requested for stack: plex
|
||||
2026/10/14 15:41:23 [INFO] Removing deployed stack: plex (removeHDDData=true, hddDeclared=true, backupPaths=2)
|
||||
2026/10/14 15:41:50 [INFO] [stacks] RemoveStack plex: removed volume(s) [plex_plex_config plex_plex_transcode]
|
||||
2026/10/14 15:41:50 [INFO] Stack plex removed successfully (took 26.7s)
|
||||
2026/10/14 15:41:50 [INFO] Event pushed: app_removed (info) [hu-only] — Alkalmazás eltávolítva: plex
|
||||
2026/10/14 15:41:50 [INFO] [stacks] image retention (remove of plex): deleted [plexinc/pms-docker:1.41.4.9463-630c9f557] (0186be6d1305, 351MB) — no container, installed app or undo names it (decision 53)
|
||||
2026/10/14 15:41:50 [INFO] [stacks] image retention (remove of plex): pass over 21 image(s) of [plexinc/pms-docker] — 1 candidate(s), 1 deleted, the rest kept
|
||||
2026/10/14 16:04:44 [INFO] [api] Deploy requested for stack: plex
|
||||
2026/10/14 16:04:45 [INFO] [stacks] Deploying stack plex with 4 env vars: [DOMAIN, SUBDOMAIN, PLEX_CLAIM, HDD_PATH]
|
||||
2026/10/14 16:04:45 [INFO] Event pushed: app_deploy_started (info) [hu-only] — Alkalmazás telepítése elindult: Plex
|
||||
2026/10/14 16:04:53 [INFO] [stacks] Stack plex deployed successfully (took 8.5s)
|
||||
2026/10/14 16:04:53 [INFO] Event pushed: app_deployed (info) [hu-only] — Alkalmazás telepítve: Plex
|
||||
351174951
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
18:24:50 prowlarr: POST deploy {"DOMAIN": "enkicsifelhom.hu", "SUBDOMAIN": "prowlarr"} -> 202 {"ok": true, "data": {"warning": "Az alkalmazások csúcsterhelése meghaladhatja a rendelkezésre álló memóriát. Normál használat mellett ez nem okoz problémát."}, "message": "Telepítés elindítva – az ál
|
||||
18:25:10 prowlarr: deployed, state=running, pinned={'prowlarr': 'lscr.io/linuxserver/prowlarr:version-2.6.5.5623'}
|
||||
running /var/lib/docker/volumes/prowlarr_prowlarr_config/_data->/config:true
|
||||
2026/10/14 16:24:50 [INFO] [api] Deploy requested for stack: prowlarr
|
||||
2026/10/14 16:24:50 [INFO] [stacks] Deploying stack prowlarr with 2 env vars: [DOMAIN, SUBDOMAIN]
|
||||
2026/10/14 16:24:50 [INFO] Event pushed: app_deploy_started (info) [hu-only] — Alkalmazás telepítése elindult: Prowlarr
|
||||
2026/10/14 16:24:58 [INFO] [stacks] Stack prowlarr deployed successfully (took 7.5s)
|
||||
2026/10/14 16:24:58 [INFO] Event pushed: app_deployed (info) [hu-only] — Alkalmazás telepítve: Prowlarr
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
== qBittorrent http://172.18.0.2:8080 (password: the deploy secret, read from the container's environment)
|
||||
POST http://172.18.0.2:8080/api/v2/auth/login -> 204 ''
|
||||
session cookie name: QBT_SID_8080
|
||||
GET http://172.18.0.2:8080/api/v2/app/preferences -> 200 {add_stopped_enabled, add_to_top_of_queue, add_trackers, add_trackers_enabled, add_trackers_from_url_enabled, add_trackers_url, add_trackers_url_list, alt_dl_limit, alt_up_limit, alternative_webui_enabled, alternative_webui_path, announce_ip, announce_port, announce_to_all_tiers, announce_to_all_trac}
|
||||
before: auto_tmm_enabled=True save_path=/mnt/felhom-drives/media_a/userdata/media/torrents temp_path_enabled=True temp_path=/mnt/felhom-drives/media_a/userdata/media/torrents/.temp
|
||||
POST http://172.18.0.2:8080/api/v2/app/setPreferences -> 200 ''
|
||||
GET http://172.18.0.2:8080/api/v2/app/preferences -> 200 {add_stopped_enabled, add_to_top_of_queue, add_trackers, add_trackers_enabled, add_trackers_from_url_enabled, add_trackers_url, add_trackers_url_list, alt_dl_limit, alt_up_limit, alternative_webui_enabled, alternative_webui_path, announce_ip, announce_port, announce_to_all_tiers, announce_to_all_trac}
|
||||
after : auto_tmm_enabled=True (a category's save path applies only in automatic mode)
|
||||
GET http://172.18.0.2:8080/api/v2/torrents/categories -> 200 {movies-drive-a, movies-drive-b, tv-drive-a, tv-drive-b}
|
||||
POST http://172.18.0.2:8080/api/v2/torrents/editCategory -> 200 ''
|
||||
POST http://172.18.0.2:8080/api/v2/torrents/editCategory -> 200 ''
|
||||
POST http://172.18.0.2:8080/api/v2/torrents/editCategory -> 200 ''
|
||||
POST http://172.18.0.2:8080/api/v2/torrents/editCategory -> 200 ''
|
||||
GET http://172.18.0.2:8080/api/v2/torrents/categories -> 200 {movies-drive-a, movies-drive-b, tv-drive-a, tv-drive-b}
|
||||
category movies-drive-a: savePath=/mnt/felhom-drives/media_a/userdata/media/torrents/movies downloadPath=/mnt/felhom-drives/media_a/userdata/media/torrents/.temp
|
||||
category movies-drive-b: savePath=/mnt/felhom-drives/media_b/userdata/media/torrents/movies downloadPath=/mnt/felhom-drives/media_b/userdata/media/torrents/.temp
|
||||
category tv-drive-a: savePath=/mnt/felhom-drives/media_a/userdata/media/torrents/tv downloadPath=/mnt/felhom-drives/media_a/userdata/media/torrents/.temp
|
||||
category tv-drive-b: savePath=/mnt/felhom-drives/media_b/userdata/media/torrents/tv downloadPath=/mnt/felhom-drives/media_b/userdata/media/torrents/.temp
|
||||
== Transmission http://172.18.0.3:9091/transmission/rpc (password: the deploy secret, read from the container's environment)
|
||||
POST http://172.18.0.3:9091/transmission/rpc -> 409 '<h1>409: Conflict</h1><p>Your request had an invalid session_id header.</p><p>To fix this, follow these steps:<ol><li>
|
||||
POST http://172.18.0.3:9091/transmission/rpc -> 200 {arguments, result}
|
||||
before: {"download-dir": "/mnt/felhom-drives/media_a/userdata/media/transmission/complete", "incomplete-dir": "/mnt/felhom-drives/media_a/userdata/media/transmission/incomplete", "incomplete-dir-enabled": false, "rename-partial-files": true}
|
||||
POST http://172.18.0.3:9091/transmission/rpc -> 409 '<h1>409: Conflict</h1><p>Your request had an invalid session_id header.</p><p>To fix this, follow these steps:<ol><li>
|
||||
POST http://172.18.0.3:9091/transmission/rpc -> 200 {arguments, result}
|
||||
POST http://172.18.0.3:9091/transmission/rpc -> 409 '<h1>409: Conflict</h1><p>Your request had an invalid session_id header.</p><p>To fix this, follow these steps:<ol><li>
|
||||
POST http://172.18.0.3:9091/transmission/rpc -> 200 {arguments, result}
|
||||
after : {"download-dir": "/mnt/felhom-drives/media_a/userdata/media/transmission/complete", "incomplete-dir-enabled": false, "rename-partial-files": true} (one incomplete folder for all torrents would be on drive A)
|
||||
POST http://172.18.0.3:9091/transmission/rpc -> 409 '<h1>409: Conflict</h1><p>Your request had an invalid session_id header.</p><p>To fix this, follow these steps:<ol><li>
|
||||
POST http://172.18.0.3:9091/transmission/rpc -> 200 {arguments, result}
|
||||
probe Host: transmission:9091 -> HTTP 200 {'arguments': {'version': '4.1.3 (838877323f)'}, 'result': 'success'}
|
||||
== sonarr http://172.18.0.5:8989/api/v3 (key: <ApiKey> in /config/config.xml, read from the config volume)
|
||||
GET http://172.18.0.5:8989/api/v3/tag -> 200 [list of 2]
|
||||
tags: {'drive-a': 1, 'drive-b': 2}
|
||||
GET http://172.18.0.5:8989/api/v3/rootfolder -> 200 [list of 2]
|
||||
GET http://172.18.0.5:8989/api/v3/rootfolder -> 200 [list of 2]
|
||||
root folder: /mnt/felhom-drives/media_a/userdata/media/tv accessible=True freeSpace=99701403648
|
||||
root folder: /mnt/felhom-drives/media_b/userdata/media/tv accessible=True freeSpace=99701551104
|
||||
GET http://172.18.0.5:8989/api/v3/downloadclient/schema -> 200 [list of 18]
|
||||
GET http://172.18.0.5:8989/api/v3/downloadclient -> 200 [list of 4]
|
||||
client qBittorrent drive-a: exists — left as it is
|
||||
client Transmission drive-a: exists — left as it is
|
||||
client qBittorrent drive-b: exists — left as it is
|
||||
client Transmission drive-b: exists — left as it is
|
||||
GET http://172.18.0.5:8989/api/v3/downloadclient -> 200 [list of 4]
|
||||
client qBittorrent drive-a: impl=QBittorrent enable=True tags=[1] host=qbittorrent category=tv-drive-a directory=None
|
||||
client qBittorrent drive-b: impl=QBittorrent enable=True tags=[2] host=qbittorrent category=tv-drive-b directory=None
|
||||
client Transmission drive-a: impl=Transmission enable=True tags=[1] host=transmission category= directory=/mnt/felhom-drives/media_a/userdata/media/transmission/tv
|
||||
client Transmission drive-b: impl=Transmission enable=True tags=[2] host=transmission category= directory=/mnt/felhom-drives/media_b/userdata/media/transmission/tv
|
||||
GET http://172.18.0.5:8989/api/v3/config/mediamanagement -> 200 {autoUnmonitorPreviouslyDownloadedEpisodes, chmodFolder, chownGroup, copyUsingHardlinks, createEmptySeriesFolders, deleteEmptyFolders, downloadPropersAndRepacks, enableMediaInfo, episodeTitleRequired, extraFileExtensions, fileDate, id, importExtraFiles, minimumFreeSpaceWhenImporting, recycleBin, recy}
|
||||
before: minimumFreeSpaceWhenImporting=10240 MB, copyUsingHardlinks=True
|
||||
PUT http://172.18.0.5:8989/api/v3/config/mediamanagement/1 -> 202 {autoUnmonitorPreviouslyDownloadedEpisodes, chmodFolder, chownGroup, copyUsingHardlinks, createEmptySeriesFolders, deleteEmptyFolders, downloadPropersAndRepacks, enableMediaInfo, episodeTitleRequired, extraFileExtensions, fileDate, id, importExtraFiles, minimumFreeSpaceWhenImporting, recycleBin, recy}
|
||||
GET http://172.18.0.5:8989/api/v3/config/mediamanagement -> 200 {autoUnmonitorPreviouslyDownloadedEpisodes, chmodFolder, chownGroup, copyUsingHardlinks, createEmptySeriesFolders, deleteEmptyFolders, downloadPropersAndRepacks, enableMediaInfo, episodeTitleRequired, extraFileExtensions, fileDate, id, importExtraFiles, minimumFreeSpaceWhenImporting, recycleBin, recy}
|
||||
after : minimumFreeSpaceWhenImporting=10240 MB, copyUsingHardlinks=True (default kept)
|
||||
== radarr http://172.18.0.7:7878/api/v3 (key: <ApiKey> in /config/config.xml, read from the config volume)
|
||||
GET http://172.18.0.7:7878/api/v3/tag -> 200 [list of 2]
|
||||
tags: {'drive-a': 1, 'drive-b': 2}
|
||||
GET http://172.18.0.7:7878/api/v3/rootfolder -> 200 [list of 2]
|
||||
GET http://172.18.0.7:7878/api/v3/rootfolder -> 200 [list of 2]
|
||||
root folder: /mnt/felhom-drives/media_a/userdata/media/movies accessible=True freeSpace=99701403648
|
||||
root folder: /mnt/felhom-drives/media_b/userdata/media/movies accessible=True freeSpace=99701551104
|
||||
GET http://172.18.0.7:7878/api/v3/downloadclient/schema -> 200 [list of 19]
|
||||
GET http://172.18.0.7:7878/api/v3/downloadclient -> 200 [list of 4]
|
||||
client qBittorrent drive-a: exists — left as it is
|
||||
client Transmission drive-a: exists — left as it is
|
||||
client qBittorrent drive-b: exists — left as it is
|
||||
client Transmission drive-b: exists — left as it is
|
||||
GET http://172.18.0.7:7878/api/v3/downloadclient -> 200 [list of 4]
|
||||
client qBittorrent drive-a: impl=QBittorrent enable=True tags=[1] host=qbittorrent category=movies-drive-a directory=None
|
||||
client qBittorrent drive-b: impl=QBittorrent enable=True tags=[2] host=qbittorrent category=movies-drive-b directory=None
|
||||
client Transmission drive-a: impl=Transmission enable=True tags=[1] host=transmission category= directory=/mnt/felhom-drives/media_a/userdata/media/transmission/movies
|
||||
client Transmission drive-b: impl=Transmission enable=True tags=[2] host=transmission category= directory=/mnt/felhom-drives/media_b/userdata/media/transmission/movies
|
||||
GET http://172.18.0.7:7878/api/v3/config/mediamanagement -> 200 {autoRenameFolders, autoUnmonitorPreviouslyDownloadedMovies, chmodFolder, chownGroup, copyUsingHardlinks, createEmptyMovieFolders, deleteEmptyFolders, downloadPropersAndRepacks, enableMediaInfo, extraFileExtensions, fileDate, id, importExtraFiles, minimumFreeSpaceWhenImporting, pathsDefaultStatic, re}
|
||||
before: minimumFreeSpaceWhenImporting=10240 MB, copyUsingHardlinks=True
|
||||
PUT http://172.18.0.7:7878/api/v3/config/mediamanagement/1 -> 202 {autoRenameFolders, autoUnmonitorPreviouslyDownloadedMovies, chmodFolder, chownGroup, copyUsingHardlinks, createEmptyMovieFolders, deleteEmptyFolders, downloadPropersAndRepacks, enableMediaInfo, extraFileExtensions, fileDate, id, importExtraFiles, minimumFreeSpaceWhenImporting, pathsDefaultStatic, re}
|
||||
GET http://172.18.0.7:7878/api/v3/config/mediamanagement -> 200 {autoRenameFolders, autoUnmonitorPreviouslyDownloadedMovies, chmodFolder, chownGroup, copyUsingHardlinks, createEmptyMovieFolders, deleteEmptyFolders, downloadPropersAndRepacks, enableMediaInfo, extraFileExtensions, fileDate, id, importExtraFiles, minimumFreeSpaceWhenImporting, pathsDefaultStatic, re}
|
||||
after : minimumFreeSpaceWhenImporting=10240 MB, copyUsingHardlinks=True (default kept)
|
||||
== done; Transmission reachable by its container name: True
|
||||
== prowlarr http://172.18.0.13:9696/api/v1 (key: <ApiKey> in /config/config.xml, read from the config volume)
|
||||
GET http://172.18.0.13:9696/api/v1/applications/schema -> 200 [list of 7]
|
||||
GET http://172.18.0.13:9696/api/v1/applications -> 200 [list of 0]
|
||||
POST http://172.18.0.13:9696/api/v1/applications -> 201 {configContract, enable, fields, id, implementation, implementationName, infoLink, name, syncLevel, tags}
|
||||
POST http://172.18.0.13:9696/api/v1/applications -> 201 {configContract, enable, fields, id, implementation, implementationName, infoLink, name, syncLevel, tags}
|
||||
GET http://172.18.0.13:9696/api/v1/applications -> 200 [list of 2]
|
||||
application Radarr: syncLevel=fullSync prowlarrUrl=http://prowlarr:9696 baseUrl=http://radarr:7878 syncCategories=[2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080, 2090]
|
||||
application Sonarr: syncLevel=fullSync prowlarrUrl=http://prowlarr:9696 baseUrl=http://sonarr:8989 syncCategories=[5000, 5010, 5020, 5030, 5040, 5045, 5050, 5090]
|
||||
GET http://172.18.0.13:9696/api/v1/indexer -> 200 [list of 0]
|
||||
indexers in Prowlarr: []
|
||||
POST http://172.18.0.13:9696/api/v1/command -> 201 {body, clientUserAgent, commandName, id, name, priority, queued, sendUpdatesToClient, started, stateChangeTime, status, trigger, updateScheduledTask}
|
||||
GET http://172.18.0.13:9696/api/v1/command/1 -> 200 {body, clientUserAgent, commandName, duration, ended, id, message, name, priority, queued, sendUpdatesToClient, started, stateChangeTime, status, trigger, updateScheduledTask}
|
||||
ApplicationIndexerSync -> status=completed result=None message=Completed
|
||||
GET http://172.18.0.5:8989/api/v3/indexer -> 200 [list of 0]
|
||||
sonarr now holds 0 indexer(s): []
|
||||
GET http://172.18.0.7:7878/api/v3/indexer -> 200 [list of 0]
|
||||
radarr now holds 0 indexer(s): []
|
||||
== prowlarr part done
|
||||
|
||||
@@ -20,3 +20,5 @@ Predictions were written before each measurement: `A-drives/PREDICTIONS-A4-A6.md
|
||||
| S2 | One public-domain film (archive.org, CC PD Mark) on drive B: through Transmission and through qBittorrent → **hardlink** in `media_b/…/movies`. archive.org's web seed failed the last piece for qBittorrent; a local peer (the same torrent in Transmission) finished it — recorded | `S2-*.txt` |
|
||||
| S3 | Tag drive-a, no client forced → „Transmission drive-a", hardlink on A. Mismatch (root B, tag A) → download on A, **copy** on B (link count 1 each) | `S3-*.txt` |
|
||||
| — | **17:25 CEST: demo-hp was power-cut by another session's Immich drill** (`plugcut.py --plug hp`) — Tester 1 went down with it and came back by itself; the boot gate recreated the four media apps onto drive A; all test films then removed (0 files left) | `AFTER-power-cut-1725.txt` |
|
||||
| H | Prowlarr (drill-only template `b0fd3a0`, `version-2.6.5.5623`, config volume only) deployed through the dashboard; `wire.py` adds Sonarr and Radarr as Prowlarr apps with „Full Sync” (both pass Prowlarr's test; the sync completes; 0 indexers so far). Sonarr now refuses a bare IP (the operator set „Allowed hosts”) — `wire.py` sends `Host: <app>` | `H-*.txt` |
|
||||
| G | Sign-in with „Disabled for Local Addresses”: outsiders ARE asked (the public address arrives in `X-Forwarded-For`; Sonarr/Radarr redirect to `/login`); the home network and other containers are not (200 + API key) — R-1030. The box's setup gate also blocks outsiders until setup is marked done | `G-auth/` |
|
||||
|
||||
@@ -12,6 +12,7 @@ About one hour. From any place with a browser. Use only content you have the rig
|
||||
| qBittorrent | https://torrent.enkicsifelhom.hu |
|
||||
| Transmission | https://transmission.enkicsifelhom.hu |
|
||||
| Plex (after step 2) | https://plex.enkicsifelhom.hu/web |
|
||||
| Prowlarr (added 18:25) | https://prowlarr.enkicsifelhom.hu |
|
||||
|
||||
The two test drives are **Media A** and **Media B**. Their folders:
|
||||
|
||||
@@ -33,7 +34,8 @@ If the code expired: remove Plex in the dashboard **without** „delete data" (s
|
||||
|
||||
1. Open Sonarr. The box asks for your dashboard sign-in first. Then Sonarr asks you to make its admin user. Make it.
|
||||
2. Do the same in Radarr.
|
||||
3. In each: **Settings → Indexers → +**. Add your own download sources.
|
||||
3. **Prowlarr:** make its admin user. Add your own download sources in **Indexers** — once, in Prowlarr only.
|
||||
Prowlarr sends them to Sonarr and Radarr by itself („Full Sync”). If a source asks for FlareSolverr, tell CC.
|
||||
|
||||
The download apps are already connected. Do not change **Settings → Download Clients**, except as step 4c says.
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ For each drive d in DRIVES, M = /mnt/felhom-drives/<d>/userdata/media (the same
|
||||
„Transmission <d>" (directory M/transmission/tv, tag <d>)
|
||||
Radarr : the same with movies
|
||||
Sonarr/Radarr: minimum free space when importing = MIN_FREE_MB; „use hardlinks instead of copy" quoted
|
||||
Prowlarr : (when installed) one Application entry each for Sonarr and Radarr, sync level „Full Sync", reached by
|
||||
container name; then an ApplicationIndexerSync command, and the indexers each app holds read back
|
||||
Box-wide, recorded with the reason in the output:
|
||||
qBittorrent : auto_tmm_enabled = true (a category's save path applies ONLY in automatic torrent management;
|
||||
qBittorrent's default is manual, which saves every torrent to the default
|
||||
@@ -82,7 +84,7 @@ def ip(c):
|
||||
|
||||
|
||||
def apikey(app):
|
||||
x = open(f"/var/lib/docker/volumes/{app}_{app}_config/_data/config.xml").read()
|
||||
x = open(f"/var/lib/docker/volumes/{app}_{app}_config/_data/config.xml").read() # sonarr, radarr, prowlarr
|
||||
k = re.search(r"<ApiKey>([^<]+)</ApiKey>", x).group(1)
|
||||
SECRETS.append(k)
|
||||
return k
|
||||
@@ -158,7 +160,9 @@ TR_BY_NAME = c == 200
|
||||
# ---------------------------------------------------------------- Sonarr / Radarr
|
||||
def arr(app, port, kind, root_kind):
|
||||
base = f"http://{ip(app)}:{port}/api/v3"
|
||||
H = {"X-Api-Key": apikey(app)}
|
||||
# Host = the container name: Sonarr/Radarr refuse a bare IP once the household sets „Allowed hosts"
|
||||
# (measured 2026-10-14 18:30: 400 „Invalid Hostname" for Host=<ip>, 200 for Host=sonarr)
|
||||
H = {"X-Api-Key": apikey(app), "Host": app}
|
||||
say(f"== {app} {base} (key: <ApiKey> in /config/config.xml, read from the config volume)")
|
||||
c, tags, _ = call("GET", base + "/tag", H)
|
||||
have = {t["label"]: t["id"] for t in tags or []}
|
||||
@@ -220,3 +224,53 @@ def arr(app, port, kind, root_kind):
|
||||
arr("sonarr", 8989, "tv", "tv")
|
||||
arr("radarr", 7878, "movie", "movies")
|
||||
say("== done; Transmission reachable by its container name:", TR_BY_NAME)
|
||||
|
||||
# ---------------------------------------------------------------- Prowlarr -> Sonarr / Radarr (Settings -> Apps)
|
||||
def prowlarr():
|
||||
if not sh("docker ps -q -f name=^prowlarr$"):
|
||||
say("== prowlarr: not installed — skipped")
|
||||
return
|
||||
base = f"http://{ip('prowlarr')}:9696/api/v1"
|
||||
H = {"X-Api-Key": apikey("prowlarr"), "Host": "prowlarr"}
|
||||
say(f"== prowlarr {base} (key: <ApiKey> in /config/config.xml, read from the config volume)")
|
||||
c, schema, _ = call("GET", base + "/applications/schema", H)
|
||||
sch = {x["implementation"]: x for x in schema}
|
||||
c, apps, _ = call("GET", base + "/applications", H)
|
||||
have = {a["name"] for a in apps or []}
|
||||
for impl, port in (("Sonarr", 8989), ("Radarr", 7878)):
|
||||
if impl in have:
|
||||
say(f" application {impl}: exists — left as it is")
|
||||
continue
|
||||
body = json.loads(json.dumps(sch[impl]))
|
||||
vals = {"prowlarrUrl": "http://prowlarr:9696", "baseUrl": f"http://{impl.lower()}:{port}",
|
||||
"apiKey": apikey(impl.lower())}
|
||||
for f in body["fields"]:
|
||||
if f["name"] in vals:
|
||||
f["value"] = vals[f["name"]]
|
||||
body.update({"name": impl, "syncLevel": "fullSync", "tags": []})
|
||||
c, r, _ = call("POST", base + "/applications", H, body, expect=(200, 201, 400))
|
||||
if c == 400:
|
||||
say(f" application {impl}: REFUSED by Prowlarr's own test: {redact(json.dumps(r))[:400]}")
|
||||
c, apps, _ = call("GET", base + "/applications", H)
|
||||
for a in apps or []:
|
||||
f = {k["name"]: k.get("value") for k in a["fields"]}
|
||||
say(f" application {a['name']}: syncLevel={a.get('syncLevel')} prowlarrUrl={f.get('prowlarrUrl')} baseUrl={f.get('baseUrl')}"
|
||||
f" syncCategories={f.get('syncCategories')}")
|
||||
c, ix, _ = call("GET", base + "/indexer", H)
|
||||
say(f" indexers in Prowlarr: {[i['name'] for i in ix or []]}")
|
||||
c, cmd, _ = call("POST", base + "/command", H, {"name": "ApplicationIndexerSync"})
|
||||
cid = (cmd or {}).get("id")
|
||||
import time
|
||||
for _ in range(20):
|
||||
time.sleep(3)
|
||||
c, st, _ = call("GET", base + f"/command/{cid}", H)
|
||||
if (st or {}).get("status") in ("completed", "failed", "aborted"):
|
||||
break
|
||||
say(f" ApplicationIndexerSync -> status={(st or {}).get('status')} result={(st or {}).get('result')} message={(st or {}).get('message')}")
|
||||
for app, port in (("sonarr", 8989), ("radarr", 7878)):
|
||||
c, ai, _ = call("GET", f"http://{ip(app)}:{port}/api/v3/indexer", {"X-Api-Key": apikey(app), "Host": app})
|
||||
say(f" {app} now holds {len(ai or [])} indexer(s): {[(i['name'], i.get('implementation')) for i in ai or []]}")
|
||||
|
||||
|
||||
prowlarr()
|
||||
say("== prowlarr part done")
|
||||
|
||||
@@ -124,11 +124,12 @@ 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 — 14 rows (P2 2, P3 7, P4 5)
|
||||
## Apps & catalog — 17 rows (P2 3, P3 9, P4 5)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **R-1014** | Apps & catalog | P2 | **The media chain cannot work: the download clients save where Sonarr and Radarr never look, and the templates' hardlink promise is false.** Live catalog `main`: qBittorrent saves in `${USERDATA_PATH}/media/torrents`, Transmission in `media/transmission`; Sonarr and Radarr see `/downloads` = `${USERDATA_PATH}/downloads` and `/media/tv` or `/media/movies` — a download never reaches a library. The comment „SIBLING of media — same FS → hardlink/atomic move” (sonarr/radarr `docker-compose.yml` line 12) is FALSE: two bind mounts are two mount points, and `link(2)` answers EXDEV even on one filesystem. MEASURED 2026-10-14 on Tester 1 (S1): today's two-bind shape → `ln: … Cross-device link`, the import is a copy (link count 1, two inodes); one bind per drive at the same path → link count 2, one inode. S2: with that layout, one public-domain film went Radarr → Transmission → drive B → import as a hardlink. One fix closes both (the comment goes with the binds). Evidence `audits/media-all-drives-2026-10-14/``S1-hardlink.txt`, `S2-*.txt`; design `architecture/15-media-library.md`. | **READY** — filed 2026-10-14 | — | The live-fix task (`TASK-media-folder-fix-and-n100-2026-10-14.md`) moves the six templates to the layout of `15` §2 | CC |
|
||||
| **R-1030** | Apps & catalog | P2 | **Sonarr's and Radarr's first admin must default to „Enabled”, not „Disabled for Local Addresses”.** MEASURED 2026-10-14 on Tester 1 (`audits/media-all-drives-2026-10-14/``G-auth/`) with the operator's choice „Disabled for Local Addresses”: an outside visitor IS asked to sign in — the tunnel delivers the real public address in `X-Forwarded-For` (qBittorrent logged it) and Sonarr/Radarr then send `/` and `/initialize.json` to `/login`. But a request WITHOUT that header — any device on the home network, and any other container on the box's `traefik-public` network — gets the page and `/initialize.json` WITH the API key, no sign-in (200, `apiKey` present). One compromised catalog app can then drive Sonarr and Radarr. Today the box's setup gate also blocks outsiders until setup is marked done. | **READY** — filed 2026-10-14 | — | Seed `AuthenticationRequired=Enabled` (with `AuthenticationMethod=Forms`) in the templates' first-start config, the same way qBittorrent seeds its password; say in the first steps why | CC |
|
||||
| **R-981** | Apps & catalog | P3 | **The homepage app gets the Docker socket (that is root in the guest) and has no login, no setup gate and no family gate.** Fix: remove the socket mount, or put homepage behind the family gate. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN | — | Change the catalog template; prove on 9202 | CC |
|
||||
| **R-971** | Apps & catalog | P2 | **A change pushed to the catalog's `main` reaches the installed apps on every box within about 15 minutes, and nothing on the box refuses dangerous container settings (privileged mode, the Docker socket, host mounts, host namespaces); so whoever can push to the catalog repository can take every guest.** Design-shaped: installed apps take catalog fixes on purpose. Decision for the operator in `STATUS.md`. Fix options: a privilege allow-list check on the box before any compose is written (and in catalog CI), and later signed catalog releases. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | WAITING-ON-OPERATOR — confirmed by a second reader | operator decision | Decide; then build the box-side check | operator |
|
||||
| **R-676** | Apps & catalog | P3 | **[P3-LOW] Watch: immich's first start restarted 12 times — decision 28's crash-loop stop (6 in 10 min) would stop it.** From the 2026-09-17 chaos night (DB connection dropped during the first-start geocoding import on a 6 GB guest; it did not recover that night). No healthy app in any drill evidence restarts on a first start (1831 samples, 40 live containers), so the threshold stands; this row exists so the first immich install under v0.269.x is watched. `audits/night-2026-09-24/A3/40-first-start-restarts.txt` **2026-09-25 night (read from source, v0.271.0): a DEPLOY's first start is NOT covered by decision 28's suppression** — `Deploying` clears when `compose up -d` returns (`deploy.go` "Clear deploying flag"), and `ObserveUnhealthy` then samples the app; an automatic update's step, verify and undo ARE covered (`Updating`, pinned by `TestD28_NoCrashLoopStopDuringAnAutomaticStep`). So a first start that restarts ≥ 6 times in 10 min is stopped — which R-676 already accepts for a broken first start; a healthy slow first start would be stopped too. **-- 2026-09-30: the first-start restarts are explained.** immich's first-start geodata import OOM-kills its database at 512M on a guest with no swap (R-732, measured: 61–104 kills); the 2026-09-17 chaos-night case (DB connection dropped during the import on a 6 GB guest) fits it. Fixed in the catalog (`56c4888`, 768M). The watch itself (decision 28 on a DEPLOY's first start) is unchanged. | **OPEN — P3; owner: CC (watch)** | — | — | CC |
|
||||
@@ -137,6 +138,8 @@ stopping line that lies.
|
||||
| **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-1026** | Apps & catalog | P3 | **The dashboard's app „restart” restarts nothing.** `RestartStack` runs `docker compose up -d`, which changes nothing when the compose did not change; it logged „Stack qbittorrent restarted successfully (took 0.1s)” while the container's StartedAt stayed the same (Tester 1, 2026-10-14, `audits/media-all-drives-2026-10-14/``D-plex-remove-deleted.txt`). So the button cannot make an app see a new drive or recover from a deleted bind source, and its success line is presence, not result. Stop + start did work. | **READY** — filed 2026-10-14 | — | Make restart a real restart (`up -d --force-recreate`, or stop+start), and make its log line state what changed; a test that asserts a new container start time. The restart notice of R-1023 depends on it | CC |
|
||||
| **R-1024** | Apps & catalog | P3 | **Links for the media apps: Sonarr and Radarr connected to the download apps and to a drive by the box.** Measured calls and answer shapes: `audits/media-all-drives-2026-10-14/``tools/wire.py` + `C-wire-run2.txt`. Facts the code needs: the API key can be SET at install (`SONARR__AUTH__APIKEY`/`RADARR__AUTH__APIKEY`, measured 200/401); qBittorrent needs automatic torrent management on, or a category's folder is ignored; a Transmission entry must clear the default category („Cannot use Category and Directory”); Transmission's one incomplete folder must be off; a show with no tag gets no download app when every entry is tagged; the OnlyOffice revoke-on-stop rule (`lifecycle.go:10-13`) must NOT apply (it would delete the download entries at every restart). | **OPEN** — filed 2026-10-14 | R-1023 | Build with R-1023 | 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. | **READY** — filed 2026-10-14 | — | Pull the image BEFORE asking for the code (or ask for it on a step after the pull), and give the household a „claim again” action that sets the code on the running server (Plex's `/myplex/claim?token=` call) | 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-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 |
|
||||
@@ -188,12 +191,14 @@ 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 — 5 rows (P2 2, P3 2, P4 1)
|
||||
## Storage & devices — 7 rows (P2 4, P3 2, P4 1)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **R-1027** | Storage & devices | P2 | **„Delete my data too” deletes every bind under the app's drive — read-only and shared ones included.** MEASURED 2026-10-14 on Tester 1: removing a throwaway Plex (drill template: `/mnt/felhom-drives/media_a/userdata/media` bound `:ro`, `HDD_PATH=/mnt/felhom-drives/media_a`) with `remove_hdd_data:true` logged „Removed HDD data: /mnt/felhom-drives/media_a/userdata/media (80K)” — the WHOLE media folder of the drive, which four other apps were using. `ParseComposeHDDMounts` counts a literal path under `HDD_PATH` as the app's own data, and `ProtectedHDDPaths` does not protect `userdata/media`. Only an empty test skeleton was lost; it was rebuilt by the controller's next file-browser sync and the apps restarted (`audits/media-all-drives-2026-10-14/``D-plex-remove-deleted.txt`). Today's LIVE templates bind `${USERDATA_PATH}` and are not hit (R-800); **the media-library build (R-1023) would be, unless removal learns that a shared library is never one app's data.** The note „literal binds are never removed” in this session's first draft was WRONG and is retracted. | **READY** — filed 2026-10-14 | — | Removal: never delete a `:ro` bind nor anything under `userdata/`; a test that removes a reader app and asserts the shared folder survives (red-proof first); `15` §8 | CC |
|
||||
| **R-1027** | Storage & devices | P2 | **„Delete my data too” deletes every bind under the app's drive — read-only and shared ones included.** MEASURED 2026-10-14 on Tester 1: removing a throwaway Plex (drill template: `/mnt/felhom-drives/media_a/userdata/media` bound `:ro`, `HDD_PATH=/mnt/felhom-drives/media_a`) with `remove_hdd_data:true` logged „Removed HDD data: /mnt/felhom-drives/media_a/userdata/media (80K)” — the WHOLE media folder of the drive, which four other apps were using. `ParseComposeHDDMounts` counts a literal path under `HDD_PATH` as the app's own data, and `ProtectedHDDPaths` does not protect `userdata/media` (R-1032). Only an empty test skeleton was lost; it was rebuilt by the controller's next file-browser sync and the apps restarted (`audits/media-all-drives-2026-10-14/``D-plex-remove-deleted.txt`). Today's LIVE templates bind `${USERDATA_PATH}` and are not hit (R-800); **the media-library build (R-1023) would be, unless removal learns that a shared library is never one app's data.** The note „literal binds are never removed” in this session's first draft was WRONG and is retracted. | **READY** — filed 2026-10-14 | — | Removal: never delete a `:ro` bind nor anything under `userdata/`; a test that removes a reader app and asserts the shared folder survives (red-proof first); `15` §8 | CC |
|
||||
| **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). | **OPEN** — filed 2026-10-14; design in `15` (S5/S6 results pending the operator's test) | the operator's test on Tester 1 („done”), then S5/S6 | Finish `15` with S5/S6; then a build task | CC |
|
||||
| **R-1031** | Storage & devices | P2 | **The build must not ask a media app for one drive.** Today every media template carries a required `HDD_PATH` field („Médiatár útvonal”, `locked_after_deploy`), so the wizard makes the household pick ONE drive — the opposite of `09` §3 decision 237 — and the drive gate, removal and backup key on that one drive (`15` §4, §8). The test rig kept the field only because the deploy needs it. | **READY** — filed 2026-10-14 | R-1023 | In the build: a media app (`15` §3 marker) has no drive field; its own config stays on the system SSD; its library binds come from the enrolled drives | CC |
|
||||
| **R-1032** | Storage & devices | P2 | **The delete safety list still names the old folder layout.** `ProtectedHDDPaths` (`felhom-controller/controller/internal/stacks/delete.go:240`) protects `<drive>/media` and `<drive>/Dokumentumok` — the layout before `userdata/` — and NOT `<drive>/userdata`, `userdata/media` or `userdata/documents`. That is why removing a Plex deleted `userdata/media` (R-1027). It touches every app with a drive bind, not only media apps. | **READY** — filed 2026-10-14 | — | Protect the current skeleton roots (`userdata` and its top folders) and keep the old names; a test that fails when a skeleton root is missing from the list (red-proof first). Fix with R-1027 | 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 |
|
||||
|
||||
Reference in New Issue
Block a user