Media drive events, night 2026-10-14: 15 §10 (event x app, measured), K4-K8 evidence, rows R-1044..R-1049, narrowed R-1022/1023/1024/1026/1028/1037/1038, night report
gates / gates (push) Successful in 6m34s

- 15-media-library.md §10: what the box does on each drive event, per app, with the measured call; the guards
  that ARE needed and the ones that are NOT (K1: nothing lost while a drive is gone).
- Evidence K4 (dead-drive check: mountinfo `shutdown`, stat unreliable), K5 (restart), K6 (unclaimed Plex),
  K7 (old Sonarr/Radarr repair on the bench), K8 (hub received the drive-return events and mailed none), K9 (files identical).
- nightly-rotation.md runs scripts/https_only_check.py first every night; scripts/CHANGELOG.md entry.
- STATUS, CONTEXT (decisions 244-246), REPORT-night-2026-10-14.md.

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-14 23:40:26 +02:00
parent 5aaa3312ba
commit 0e88b7eb99
19 changed files with 616 additions and 18 deletions
+51 -3
View File
@@ -5,7 +5,7 @@
> learn. **No document covered this before 2026-10-14** — itself a finding: the four templates were written one by one
> and never tested together (R-1014). Rulings: `09` §3 decisions 237–241. Measured on Tester 1:
> `audits/media-all-drives-2026-10-14/`. Marks as in `01`: **[DESIGN]** a decision or proposal; **[FACT]** read in a
> named file or measured. **Status: DRAFT** — measured end to end on Tester 1; the layout of §2 is RULED (decision 242) and live for one drive.
> named file or measured. **Status: DRAFT** — measured end to end on Tester 1; the layout of §2 is RULED (decision 242) and live for one drive; the drive events of §10 measured (decision 245, option A).
## 1. The problem
@@ -82,8 +82,8 @@ container does not see a drive added later (guest propagation is `shared,slave`,
not change; it logged success in 0.1 s while the containers kept their start time. Stop + start works. The notice of
decisions 238–239 needs a real restart (`up -d --force-recreate` once the per-drive binds are in the compose).
**[DESIGN]** The restart notice (decision 238) fires on: a drive added, a drive removed, and — pending S6 — a drive
that came back. Each drive card carries „apps restarted since this drive was added: yes/no" with the button
**[DESIGN]** The restart notice (decision 238) fires on: a drive added, a drive removed, and a drive that came back
(S6 and K1-c/K5 measured it: after a return nothing sees the drive until restarted; §10 has the per-app actions). Each drive card carries „apps restarted since this drive was added: yes/no" with the button
(decision 239).
## 5. A drive is pulled / comes back [FACT, measured on Tester 1 2026-10-14, `audits/media-all-drives-2026-10-14/S5-S6/`]
@@ -179,3 +179,51 @@ Call sites that assume ONE `HDD_PATH`/`USERDATA_PATH` per app (controller main a
An installed media app keeps its `HDD_PATH` (its config's unit lives there) and gains binds for the other drives at its
next recreate. Because the paths are the same everywhere (§2 reason 2), nothing Sonarr or Radarr stored changes. A
household's existing files under `media/torrents` stay where they are.
## 10. What the box does on each drive event [FACT, measured 2026-10-14 night on Tester 1 + the bench; `09` §3 decision 245 — option A]
Evidence: `audits/media-drive-events-2026-10-14/` (K1–K7). „Idempotent" = a second run changes nothing (K2: run 2 left every entry, folder and library as it
was). „Household" = a step only the household can take.
| event → app | action | measured call | idempotent | household | evidence |
|---|---|---|---|---|---|
| **added** — every media app | the compose gains the drive's bind → `up -d` recreates it (the compose CHANGED, so today's restart works) | controller compose `up -d` | yes | no | K5-1: 1–10 s per app |
| added — FileBrowser | already: `SyncFileBrowserMounts` recreates on a new path | — | yes | no | K2-2 |
| added — the drive's folders | `torrents/{tv,movies}`, `transmission/{tv,movies}` made (the skeleton lacks them for a drill template; Sonarr raises a „remote path mapping" health error without them) | mkdir, owner 1000, 2775 | yes | no | K2-6 |
| added — qBittorrent | categories `tv-<d>`, `movies-<d>`: save path AND incomplete path on that drive; automatic torrent management ON | `createCategory`/`editCategory`, `setPreferences auto_tmm_enabled` | yes (re-writes equal values) | no | K2-6/7 |
| added — Transmission | no per-drive setting; one incomplete folder → OFF, written into `settings.json` at start (an RPC `session-set` was LOST after the 17:25 power cut) | entrypoint `jq` | yes | no | K1 note, wire run 3 |
| added — Sonarr, Radarr | root folder, tag `<d>`, „qBittorrent <d>" (category) + „Transmission <d>" (directory, category CLEARED), min free space | `/api/v3/rootfolder`, `/tag`, `/downloadclient` (Host: <app>) | yes (check before create) | no | K2-6/7 |
| added — Prowlarr | nothing per drive (it feeds Sonarr/Radarr) | — | — | indexers | H |
| added — Plex | the drive's folders added to the EXISTING libraries, a scan, „scan when a change is detected" ON | `PUT /library/sections/<id>?…&location=…` (all locations repeated), `/refresh`, `PUT /:/prefs FSEventLibraryUpdatesEnabled=1` | yes | no — but the token is the server's own `PlexOnlineToken` (a household-account token; 401 without one, K2-5) → R-1046 | K2-6 |
| added — Jellyfin | the drive's folders added, a scan; an API key for the box | `POST /Library/VirtualFolders/Paths`, `/Library/Refresh`, `POST /Auth/Keys?app=felhom` | yes | no IF the box made the first admin itself; else ONE step (an API key) | K2-6 |
| **safe removal** — qBittorrent, Transmission | pause ONLY the torrents saved on that drive | qBit `torrents/stop` by `save_path`; Transmission `torrent-stop` by `downloadDir` | yes | no | K3-2 |
| safe removal — every media app | **recreate WITHOUT the drive's bind BEFORE the unmount**: today's eject detaches the guest's bind and keeps the raw mount, while the running apps still HOLD the filesystem (they saw its files after the „safe" removal) | render binds for present drives; `up -d` | yes | no | K3-3 |
| safe removal — the drive | eject today also DEREGISTERS it; after a re-attach the agent mounts it again, but the household must „Meglévő meghajtó csatolása" (`register-mounted`) | — | — | **yes, today** | K3-5 |
| safe removal — Plex, Jellyfin | nothing (they keep every item, §5) | — | — | no | K1 |
| **pulled** — every app | NOTHING is lost: Plex (autoEmptyTrash on or off), Jellyfin, Sonarr/Radarr refresh + rescan (unmonitor on or off), torrents — the guest keeps a DEAD mount and every read is an I/O error, which every app treats as „unavailable" | — | — | no | K1 |
| pulled — the box | knows which apps hold a dead view WITHOUT disk I/O: the guest's `/proc/self/mountinfo` shows `shutdown` on the drive; each container's `/proc/<pid>/mountinfo` shows the old device + `shutdown` | read `/proc` only | yes | no | K4 |
| pulled — restart an app | still starts while the DEAD mount is there (K1: Jellyfin); FAILS (500, „error while creating mount source path … permission denied") once the path is gone (after an eject) — the app is down until the drive returns | — | — | no | K3-4 |
| **came back** — every media app + FileBrowser | stop+start, or `up -d --force-recreate` WITH the controller's environment (a bare `compose up` lost every `${…}` value: routes `Host(\`.\`)`, an empty password — K5-2); today's restart (`up -d`, compose unchanged) does nothing (R-1026) | `StopStack`+`StartStack`, or `composeExecCustomEnv(…, "up","-d","--force-recreate")` | yes | no | K5-2/3: 2–10 s per app, FileBrowser 5 s |
| came back — qBittorrent, Transmission | nothing: every torrent seeds again, no „missing files", no re-check | — | — | no | K1-c, K3-6 |
| came back — a registered drive | the gate re-binds it by itself; an EJECTED one needs the household's attach | — | — | after an eject | K1-c, K3-5 |
| came back — a playing stream | is CUT by the restart (curl exit 56), Jellyfin answers again 22 s later → offer „later", or check active sessions first | Jellyfin `/Sessions`, Plex `/status/sessions` | — | no | K5-4 |
**Guards the measurements show ARE needed:** (1) never start a media app with a bind to an absent path — render one bind per
PRESENT drive (the FileBrowser pattern); (2) a safe removal recreates the media apps without the drive, then unmounts the
raw device (R-1044); (3) every restart through the controller's environment; (4) a returned drive restarts every container that binds
it, FileBrowser included; (5) Transmission's settings written at start, not by RPC. **Guards NOT needed — do not build
them:** a scan or „empty trash" suppression for Plex/Jellyfin, an unmonitor guard for Sonarr/Radarr, pausing torrents on
a pull — K1 measured no loss in any of them while the dead mount stands. **The one condition behind that:** the guest keeps
a DEAD mount (I/O errors). R-1038's fix must NOT turn a pulled drive into an EMPTY folder that a scan could read as „all
files deleted" — keep the dead mount, or stop the media apps first.
**Old installs (K7):** Sonarr/Radarr from before app-catalog `bc54c01` keep their library at `/media/tv` / `/media/movies`;
after the update the root folder is missing (health „Missing root folder"), records stay. Repair through the apps' own API,
no file moved: add the same-path root folder; `PUT /series/editor` (`/movie/editor`) `rootFolderPath`, `moveFiles:false`;
rescan; delete the old root folder; Radarr's collections keep the old root (a separate setting) and the health page shows the
old warning until its next check. File and inode unchanged.
**Plex without the race (K6):** an unclaimed Plex starts with no code and answers every request from a private address
WITHOUT a token — it must never be routed publicly while unclaimed; `POST /myplex/claim?token=<code>` on the RUNNING server
claims it (the route answered and called plex.tv; a real code needs the household's account — the morning step). So the box
asks for the code only once the server is up: no 4-minute race with the pull.