554b237ba9
gates / gates (push) Successful in 6m46s
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
254 lines
26 KiB
Markdown
254 lines
26 KiB
Markdown
# 15 — The media library: media apps on every drive
|
||
|
||
> **What this is:** how the media apps (download → library → player) share the household's drives — the folder
|
||
> layout, the links between the apps, what happens when a drive comes or goes, and what backup, removal and export must
|
||
> 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: BUILT (controller v0.313.0, 2026-10-15)** — §2–§10 are built and proven on Tester 1 and demo-hp 9201 (§11, with the evidence per part); delivery to the fleet is golden 0.313.0 → the operator's vouch → the floor (`09` §3 decision 247). Plex's real claim on the running server is proven by hand (K6-2); through the dashboard it waits on a household code (§11).
|
||
|
||
## 1. The problem
|
||
|
||
**[FACT]** Live catalog (`main` 8ee687e): qBittorrent saves to `${USERDATA_PATH}/media/torrents`, Transmission to
|
||
`media/transmission`; Sonarr and Radarr look in `${USERDATA_PATH}/downloads`. A download never reaches a library.
|
||
**[FACT]** Even with matching folders, Sonarr/Radarr bind `media/tv` (or `movies`) and `downloads` as TWO bind mounts:
|
||
`link(2)` answers EXDEV across two mount points on one filesystem, so every import is a full copy (S1:
|
||
`ln: … Cross-device link`; two inodes). **[FACT]** Each app binds ONE drive (`HDD_PATH`, locked after deploy,
|
||
`stacks/deploy.go` `withPathVars`); only FileBrowser follows every drive (`web/handlers.go` `buildFileBrowserPaths`).
|
||
|
||
## 2. The layout [DESIGN, measured on Tester 1]
|
||
|
||
Each media app binds, **per enrolled drive**, that drive's `userdata/media` at **the same path inside the container as
|
||
in the guest**:
|
||
|
||
```
|
||
/mnt/felhom-drives/<drive>/userdata/media → /mnt/felhom-drives/<drive>/userdata/media (rw; :ro for players)
|
||
media/tv/<Show>/… Sonarr library media/torrents/{tv,movies}/… qBittorrent
|
||
media/movies/<Film>/… Radarr library media/transmission/{tv,movies}/… Transmission
|
||
```
|
||
|
||
The four reasons:
|
||
|
||
1. **One bind per drive keeps download and library on one mount point** — the import is a hardlink: one file, two
|
||
names, no extra space. **[FACT]** S2: Transmission and qBittorrent, drive B → link count 2, same inode; S3: drive A
|
||
the same.
|
||
2. **The same path everywhere** — no app translates another app's path (no „remote path mapping"), and moving an
|
||
installed app from one drive to many later ADDS binds without rewriting the paths Sonarr and Radarr have stored.
|
||
3. **`media/` only** — never `felhom-data`, never the whole `userdata` (documents, roms and the app's own data stay
|
||
out).
|
||
4. **Today's host folders** (`media/torrents`, `media/transmission`) — an installed download app keeps its files.
|
||
|
||
**[FACT, and a correction of the brief]** `media/` is not photo-free: the skeleton puts `photos`, `books`,
|
||
`audiobooks`, `music`, `comics`, `podcasts` there too (`appbackup/userdata.go` + the catalog-derived set), and Immich,
|
||
calibre-web, audiobookshelf and navidrome use them. With reason 1, a download app needs ONE mount that holds both its
|
||
download folder and the libraries — so binding `media/tv` and `media/torrents` separately brings EXDEV back.
|
||
|
||
**RULED 2026-10-14 (`09` §3 decision 242, D9 = A with a limit) — this table replaces every earlier shape:**
|
||
|
||
| App | Bind (rw unless noted), the SAME path inside the container as on the box | Sees |
|
||
|---|---|---|
|
||
| Sonarr, Radarr | `${USERDATA_PATH}/media` | the whole `media` folder — it must: download and library on ONE mount (S1) |
|
||
| qBittorrent | `${USERDATA_PATH}/media/torrents` (+ the same folder as `/downloads`, for torrents added before 2026-10-14) | only its download folder |
|
||
| Transmission | `${USERDATA_PATH}/media/transmission` (+ `/downloads` likewise; `/watch` = `media/transmission/watch`) | only its download folder |
|
||
| Plex, Jellyfin, Emby | unchanged: `${USERDATA_PATH}/media:/media:ro` | they move with the all-drives build |
|
||
|
||
The hardlink is made by Sonarr/Radarr in their own container, where both folders are one mount; the client reports
|
||
`…/media/torrents/<name>` and Sonarr finds exactly that path. The download clients cannot reach `media/photos` (B-3,
|
||
measured: the path does not exist in their containers). Sonarr/Radarr's bind is declared `excluded` in their backup
|
||
block — unlisted it would be MANDATORY, every tier, off-site included (B-1, measured with the production classifier).
|
||
Built in the live catalog 2026-10-14 (`audits/media-folder-fix-2026-10-14/`); the all-drives build adds one such bind
|
||
per drive at the same paths, so nothing installed now is redone.
|
||
|
||
## 3. Which apps are „media apps" [DESIGN, proposal]
|
||
|
||
Sonarr, Radarr, qBittorrent, Transmission (download/library, rw); Plex, Jellyfin, Emby (players, `:ro`). Others
|
||
(Lidarr, Readarr, SABnzbd, NZBGet, navidrome, audiobookshelf…) later, each with its own rule. A media app carries a
|
||
marker in `.felhom.yml` (e.g. `media_library: rw|ro`); the controller builds its per-drive binds — templates never
|
||
spell drive names (the drill used plain host paths only to stay out of the classifier, §7).
|
||
|
||
## 4. The drive-absent gate and the restart notice
|
||
|
||
**[FACT]** FileBrowser's pattern is the one to extend: bind every drive's `userdata/`, skip a drive that is not a live
|
||
mountpoint, recreate the container when the set changes. **[FACT]** The app gate (`web/intermediary.go`
|
||
`planDriveGates`/`appsOnStoragePath`) stops and restarts only apps whose `HDD_PATH` equals the absent drive; at boot it
|
||
waits for THAT drive's bind only (seen after the 17:25 power cut: the four media apps were recreated onto
|
||
`media_a` with no check of `media_b`). A media app depends on EVERY drive it binds.
|
||
|
||
**[FACT] What the dashboard says when a drive is added (A5):** the drive card „Aktív", „Nincs alkalmazás ezen a
|
||
tárolón" — nothing about apps that need a restart. **[FACT] Docker binds are resolved at container START**: a running
|
||
container does not see a drive added later (guest propagation is `shared,slave`, the container's bind is `rprivate`).
|
||
|
||
**[FACT] Today's „restart" restarts nothing** (R-1026): `RestartStack` = `compose up -d`, a no-op when the compose did
|
||
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 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/`]
|
||
|
||
**S5 — Media B's disk detached at 20:10:23 while six media apps ran** (Sonarr, Radarr, qBittorrent, Transmission,
|
||
Plex, Jellyfin; all with `HDD_PATH` on Media A):
|
||
|
||
- **The host** saw the disk go in 2 s and unmounted its own mount, but the **guest kept a dead ext4 mount** of
|
||
`/mnt/felhom-drives/media_b`: `mountpoint` = yes, every read = Input/output error (not an empty folder — so nothing can
|
||
be written onto the guest's own disk there, which the brief feared).
|
||
- **The controller** marked the drive disconnected at 20:10:47, **stopped 0 apps** (none keys on Media B), pushed
|
||
`storage_disconnected` (error); the hub got it the same second and mailed the operator; `health_degraded` followed at
|
||
20:13. It also tried to rebuild the skeleton ON the dead mount („chmod … input/output error") — the gate asks „is it a
|
||
mountpoint?", and a dead mount says yes (R-1038).
|
||
- **The dashboard**: the card reads „Media B — Leválasztva 2026. 10. 14. 20:10" with „Csatlakoztatás"; it does not say
|
||
that six apps use the drive. No banner on the start page.
|
||
- **The apps** did not crash; all read I/O errors on B. Sonarr/Radarr mark root folder B `accessible=false` but their
|
||
own health page stays EMPTY. qBittorrent's B torrents sat queued and did not notice.
|
||
- **Plex and Jellyfin removed nothing in 30 minutes.** Plex scans only when asked (R-1037); Jellyfin's real-time monitor
|
||
got errors, not deletions. A scan during an outage is the risk; with Plex's „empty trash after every scan" on by default
|
||
such a scan would drop B's items — not measured, deliberately.
|
||
|
||
**S6 — the same volume re-attached to the same slot at 20:41:00:** the agent re-mounted it (`sde`) and bound it under
|
||
the parent within 24 s, ON TOP of the dead mount (two stacked mounts); the controller logged „drive RETURNED …
|
||
restarted gate-stopped apps" (there were none) and `storage_reconnected`. **Every running container still saw the dead
|
||
mount**, FileBrowser included („ensured running without recreate"). The dashboard's app „restart" changed nothing
|
||
(same start time, still I/O error — R-1026). **Stop + start fixed all six apps at once**; files and all 13 hardlink pairs
|
||
intact. FileBrowser is protected — the household cannot stop or start it (403), so it stays broken (R-1038).
|
||
|
||
**What the restart notice (decisions 238–239) must therefore cover [DESIGN]:** a drive added, a drive removed, **and a
|
||
drive that came back** — the last is the one where the household is told nothing today and the apps are silently half
|
||
blind. The restart is stop + start (or `up -d --force-recreate`) of every container that binds the drive, FileBrowser
|
||
included, and the box — not Sonarr — says that a root folder is unreachable.
|
||
|
||
## 6. The links [FACT, measured calls in `audits/media-all-drives-2026-10-14/tools/wire.py`]
|
||
|
||
Per drive: a qBittorrent category `tv-<d>`/`movies-<d>` (save path on that drive, incomplete path on that drive); a
|
||
Sonarr/Radarr root folder, a tag, and two client entries („qBittorrent <d>" by category, „Transmission <d>" by
|
||
directory) with that tag. Facts the link code needs:
|
||
|
||
- **The API key**: read from `/config/config.xml` — or SET at install with `SONARR__AUTH__APIKEY` /
|
||
`RADARR__AUTH__APIKEY` (measured: 200 with that key, 401 with another). Setting it is the cleaner design.
|
||
- **qBittorrent must run in automatic torrent management** (`auto_tmm_enabled`), or a category's save path is ignored
|
||
and every torrent lands on the default drive.
|
||
- **A Transmission entry must clear the default category** — Sonarr/Radarr refuse „Cannot use Category and Directory".
|
||
- **Transmission has ONE incomplete folder** for all torrents; it must be off, or every drive-B download is a cross-drive
|
||
copy at completion.
|
||
- **Transmission answers by its container name** (`transmission:9091`) despite its Host allow-list (measured).
|
||
- **A show with no tag gets no download app** when every entry carries a tag. The UI must make the drive choice set
|
||
both the root folder AND the tag; S3: a mismatch (root B, tag A) works but makes a full copy on B while A seeds.
|
||
- **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).
|
||
|
||
## 7. Backup classes for a per-drive media bind
|
||
|
||
**[FACT]** An unlisted writable `${…_PATH}` bind is MANDATORY (every tier, off-site included); a plain host path is in
|
||
no capture set (`stacks/classify_binds.go` `classifyRoot`). Measured over the live templates: Transmission's unlisted
|
||
`watch` bind goes off-site; the download folders are `optional`, so a second drive gets a nightly copy of every
|
||
download (R-1019). **[DESIGN]** The per-drive media bind is declared `excluded` for every media app — the family's
|
||
media is no single app's data; whether the library gets its own household-level backup is a separate decision.
|
||
|
||
## 8. Removal and export assume one drive per app
|
||
|
||
**[FACT]** Removal with „delete data" deletes every bind under the app's `HDD_PATH`, literal and `:ro` ones included —
|
||
removing a reader Plex deleted drive A's whole `userdata/media` (R-1027). **[DESIGN]** A media app's removal never
|
||
deletes the shared library; it deletes only its own config.
|
||
|
||
Call sites that assume ONE `HDD_PATH`/`USERDATA_PATH` per app (controller main acbbf4b):
|
||
`stacks/deploy.go` `withPathVars`; `stacks/manager.go:1566`; `stacks/delete.go` `appHDDPath`, `hddPathForRemoval`,
|
||
`ExportDataMounts`, `ParseComposeUserdataMounts`; `stacks/kept.go:226`; `stacks/migrate.go` `MigrateApp` (rewrites
|
||
`HDD_PATH`); `web/intermediary.go` `appsOnStoragePath`; `web/handlers.go` `appsUsingPath`; `backup/file_legs.go`,
|
||
`backup/backup.go:431`, `backup/offbox_capture.go`, `backup/offbox_placement.go`, `backup/offbox_restore.go`;
|
||
`report/dr_recipe.go:110`; `appexport/provider.go`.
|
||
|
||
## 9. From one drive to many
|
||
|
||
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 — PROVEN 2026-10-15 with a real code (`K6-2`): 200, `claimed` 0→1, `PlexOnlineToken` written, and the open door
|
||
closed (no token → 401). `ADVERTISE_IP` was not needed for the claim. So the box asks for the code only once the server is up:
|
||
no 4-minute race with the pull.
|
||
|
||
## 11. Built — controller v0.313.0 (2026-10-15) [FACT; evidence `audits/media-build-2026-10-15/`]
|
||
|
||
| Part | What is built | Where | Proven |
|
||
|---|---|---|---|
|
||
| §2/§3 the marker | `media_library:` (`folder:`, `read_only:`) on Sonarr, Radarr, qBittorrent, Transmission, Plex, Jellyfin, Emby (catalog `0188430`); one bind per PRESENT drive in the controller-owned `docker-compose.override.yml`, rewritten before every `compose up`; absent, dead (`shutdown`) and safely removed drives never bound; network shares never | `stacks/media_library.go` | G1: every stored path, library item, watch mark and all 69 files identical after the move; G7: controller 0.312.0 ignores the marker (one drive, no override) |
|
||
| §4 restart | `up -d --force-recreate` with the stack's env; start times before/after; an error when nothing started | `stacks/manager.go` `RestartStack` | G1/G2/G3: six apps restarted in ~30 s, each start time moved |
|
||
| §6 links | qBittorrent categories + automatic management; Transmission incomplete folder off (template entrypoint + belt); Sonarr/Radarr tag, root, clients, minimum free space; Prowlarr → Sonarr/Radarr; Plex folders + scan + „scan on change" with the server's own token read at use (`09` §3 decision 248); Jellyfin folders + scan with the API key named „felhom" read at use from Jellyfin's database (no key → one household step on the page) | `internal/mediawire` | G2: Media D wired into every app in one run; two more runs wrote nothing; G7: live catalog on 9201 |
|
||
| §7/§8 backup, export, removal | the override is read by no classifier, export or removal; „delete my data" never deletes a `:ro` or shared-library bind (R-1027); the safety list knows the userdata layout (R-1032) | `stacks/delete.go` | unit tests, each red-proved |
|
||
| §8 the wizard | a media app is not asked for a drive; its config goes on the default drive (R-1031) | `stacks/metadata.go`, `deploy.go` | unit test |
|
||
| §9 old installs | Sonarr/Radarr from before `bc54c01`: same-path root, `moveFiles:false`, rescan, old root removed, Radarr collections — only when `/media` is no longer bound | `mediawire` `repairOldRoot` | the K7 sequence as a unit test |
|
||
| §10 events | added / removed / returned recorded on the drive; the notice on the storage page and the dashboard with „Újraindítás most" (Plex/Jellyfin stream check first) and „Később" (03:30); per drive „restarted since"; containers holding a dead drive listed | `web/media_events.go`, `media_notice.html` | G2, G3: the notice within 6 s of a pull; one restart cured every app |
|
||
| §10 returned | the file browser is recreated at once (R-1038); a dead or removed drive is not bound into it | `web/handlers.go` | G3: `/srv/media_b` readable the second the drive was back |
|
||
| §10 safe removal | pause that drive's torrents → recreate the apps and the file browser without it → unmount → keep it registered; its return needs no household step; the torrents resume after the restart (R-1044) | `web/media_events.go`, `storage_handlers.go` | G4: only D's 2 torrents paused, every app let go before the unmount, back by itself 7 s after the disk, torrents seeding with no re-check |
|
||
| Plex | code asked after the start (`after_start` field) on the Plex page; the gate opens only when Plex says claimed, „Kész" refused before (R-1028, R-1045) | `web/media_events.go`, `setup_gate.go` | G5 on 9201: no code at install, gated, „Kész" 409; the claim itself: K6-2 by hand (the dashboard claim waits on a household code) |
|
||
|
||
**Measured traps, kept here because each would be rebuilt:** the agent's disk `state` stays „attached" for a registered
|
||
disk that is gone — presence is the backing device; the periodic drive gate must leave a safely removed drive alone (it
|
||
raced the 32-second removal); qBittorrent 5 answers a good login with 204; Sonarr creates a qBittorrent category with no
|
||
save path when its client is saved before the wiring; **a Plex status probe (`/library/sections`, done 401) OPENED an
|
||
unclaimed Plex because Plex answers 401 for a moment while it starts — never gate on a status a starting app also
|
||
returns** (G7f).
|
||
|