From e6dbc7304bfe22ce7317a901a567b0818ea8a007 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Wed, 14 Oct 2026 17:52:56 +0200 Subject: [PATCH] =?UTF-8?q?Media=20all=20drives,=20Part=20D:=20test=20shee?= =?UTF-8?q?t=20for=20the=20operator;=20design=20note=2015=20(draft);=2009?= =?UTF-8?q?=20=C2=A73=20decisions=20237-241;=20R-1022..R-1027=20(removal?= =?UTF-8?q?=20deletes=20the=20shared=20library=20R-1027,=20restart=20resta?= =?UTF-8?q?rts=20nothing=20R-1026)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- REPORT-media-all-drives-2026-10-14.md | 23 ++- STATUS.md | 3 + documentation/README.md | 3 + .../architecture/09-update-architecture.md | 16 ++- .../architecture/15-media-library.md | 135 ++++++++++++++++++ .../D-plex-remove-deleted.txt | 107 ++++++++++++++ .../D-plex-throwaway.txt | 18 +++ .../media-all-drives-2026-10-14/TEST-SHEET.md | 81 +++++++++++ documentation/backlog/OPEN-ITEMS.md | 14 +- 9 files changed, 393 insertions(+), 7 deletions(-) create mode 100644 documentation/architecture/15-media-library.md create mode 100644 documentation/audits/media-all-drives-2026-10-14/D-plex-remove-deleted.txt create mode 100644 documentation/audits/media-all-drives-2026-10-14/D-plex-throwaway.txt create mode 100644 documentation/audits/media-all-drives-2026-10-14/TEST-SHEET.md diff --git a/REPORT-media-all-drives-2026-10-14.md b/REPORT-media-all-drives-2026-10-14.md index 7653fa25..2e186f2b 100644 --- a/REPORT-media-all-drives-2026-10-14.md +++ b/REPORT-media-all-drives-2026-10-14.md @@ -4,7 +4,13 @@ > sessions in this repo clobber each other") sends a parallel session to a topic file. The task asked for the line at > the top of `REPORT.md`; it is here instead. -**Resume point:** Parts A–C done and pushed. Next: Part D (Plex path check, the test sheet, then STOP for the operator). +**Resume point (STOP, Part D):** A–D done and pushed; the test sheet went to the operator. Waiting for „done". Then: +Part E (`tools/where.py`, then S5 pull drive B = `qm set 341 --delete scsi3` on demo-hp, S6 re-attach), then finish +`architecture/15-media-library.md` §5 and this report. Tester 1 follows the drill mirror (drill `3297003`). + +**Read this before the live fix:** `media/` also holds `photos`, `books`, `music` — binding it whole gives the download +apps write access to them (`15` §2, operator decision D9). And removal with „delete data" deletes literal binds under +`HDD_PATH` (R-1027) — the live fix must not use plain host paths. # Media apps on every drive — Tester 1 rig (2026-10-14) @@ -30,4 +36,17 @@ Evidence: `documentation/audits/media-all-drives-2026-10-14/README.md` (one row ## Register -Rows before 133 → after 139 · opened 6 (R-1014..R-1019) · closed 0. +Rows before 133 → after 146 (another session added R-1020, R-1021 and closed R-1007 meanwhile) · opened 12 (R-1014..R-1027, R-1022..R-1026) · closed 0. + +## Part D (Plex, the sheet) + +- Plex behind the tunnel: the drill template now sets `ADVERTISE_IP=https://plex.:443/`. A throwaway unclaimed + Plex read it back as `customConnections`; public `/identity` and `/web` 200; anonymous library calls 401. Removed. +- **The removal of that Plex deleted drive A's whole `userdata/media` (80 KB empty skeleton)** — R-1027. Rebuilt by the + controller's file-browser sync; `torrents/` and `transmission/` re-made by hand with the skeleton's owner and mode + (the drill templates no longer declare them); the four apps stopped and started through the dashboard. +- **The dashboard's „restart" restarts nothing** (R-1026) — found while repairing. +- Decisions in `09` §3: 237–241 (the operator's rulings of 2026-10-14, recorded). +- Design note `architecture/15-media-library.md` — DRAFT, §5 pending S5/S6. Listed in `documentation/README.md`. +- No CHANGELOG entry: `felhom.eu`'s CHANGELOGs are per area (hub, scripts, website, marketing); this session changed + none of them. diff --git a/STATUS.md b/STATUS.md index 6c8173e6..895fc054 100644 --- a/STATUS.md +++ b/STATUS.md @@ -22,6 +22,7 @@ ## What we work on - The power-cut findings still open (R-1004, R-1005, R-1010, R-1012, R-1013); the hub build must refuse a stale checkout (R-1021). +- Media apps on every drive (R-1023): the layout is proven on Tester 1 — a download reaches the library as one file, no copy. The live templates get fixed next (R-1014). ## Waiting on you — decide @@ -35,10 +36,12 @@ | D6 | R-992 | The scratch box cannot read the private drill catalog: give it a read-only login, or make that copy readable? | **A read-only login** | Readable copy: the drill catalog becomes public | every new-app test needs hand-staging | new-app tests | | D7 | R-990, R-989 | Rule yes / no / later on the forum ideas (`audits/forum-research-2026-10-12/FELHOM.md` §5) and the selfh.st ideas (`audits/selfhst-survey-2026-10-12/FELHOM.md` §6) | — | — | nothing changes; the research ages | — | | D8 | R-206 | Install DooPlex's Docker settings from git (restarts Docker), and prune the build cache weekly: yes or no? | **Yes, in a quiet window** | — | a rebuild of DooPlex still has the settings in git | — | +| D9 | R-1023 | Media apps: bind the whole `media` folder (download apps can then write into Photos and Music), or move films and shows to their own folder first? | **Own folder** (`15-media-library.md` §2) | Whole `media`: no move, but the internet-facing apps reach the family photos | the build waits for this answer | the build | ## Waiting on you — do - **Controller 0.311.0 to the N100 and Tester 1:** bake golden 0.311.0 (CC, ~30 min), vouch it, then the global floor 0.311.0 (MinAgent 0.155.0) and clear demo-hp's own floor. If not: only demo-hp repairs Immich after a power cut; demo-hp's own floor stays above the golden. - **The shared `git/felhom.eu` checkout** is behind and its pull fails on untracked leftovers (R-1021): say whose they are, or let CC move them aside. If not: the hub build script builds stale code. +- **The media test on Tester 1** (about one hour): `documentation/audits/media-all-drives-2026-10-14/TEST-SHEET.md`, then write „done". Then the teardown word when you want (R-1022). - **N100 BIOS:** read „After Power Loss" (the HP's „Previous State" made it start after 8 of 8 cuts). **HP BIOS:** „Bypass F1 Prompt on Configuration Changes" → Enable. - **Pull the failed disk** Z6A07P2G (R-934; the twin Z6A070XZ is `/mnt/3_hdd`). If not: its alarms return on 11 November. - **Hub master key** into the password manager, then `shred -u` the key file (security review 2026-10-12, in the history). diff --git a/documentation/README.md b/documentation/README.md index cd125a24..cd138d54 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -40,6 +40,9 @@ The operator-tier agent and the Proxmox platform. ### Hub (operator backend) — `architecture/05` - [`architecture/05-hub-architecture.md`](architecture/05-hub-architecture.md) — hub architecture (v0.11.0) +### Apps — `architecture/15` +- [`architecture/15-media-library.md`](architecture/15-media-library.md) — the media library: media apps on every drive, the links, the restart notice (**DRAFT**, 2026-10-14) + ### Security audits & remediation — `audits/` - [`audits/deep-sweep-2026-06-13.md`](audits/deep-sweep-2026-06-13.md) — cross-repo deep audit (controller + agent) with remediation status - [`audits/bughunt-reconcile-2026-06-13.md`](audits/bughunt-reconcile-2026-06-13.md) — reconciliation of the v0.30.3 BUGHUNT against current code + merged fix list diff --git a/documentation/architecture/09-update-architecture.md b/documentation/architecture/09-update-architecture.md index 7296c056..1c88bf0a 100644 --- a/documentation/architecture/09-update-architecture.md +++ b/documentation/architecture/09-update-architecture.md @@ -915,6 +915,20 @@ its length, and both fixes cost something the household would notice — operato 127. **The agent's three by-design abilities (`03` §3.1) stay for now**; revisited before the first paying customer. *Operator ruling 2026-10-05.* (R-861) +### 2026-10-14 (afternoon) — media apps on every drive (recorded before the work; the media-all-drives brief) + +237. **Media apps see every drive.** Each media app (`15-media-library.md` §3) binds every enrolled drive's media + library, not only the drive picked at install. *Operator ruling 2026-10-14.* (R-1023) +238. **After a drive is added or removed, the box says so.** It tells the household that the media apps will not see + the change until they restart, and offers „restart now" or „schedule for later". *Operator ruling 2026-10-14.* + (R-1023; today's restart button restarts nothing — R-1026) +239. **Each drive shows whether its apps have restarted since it was added.** If they have not, a manual restart + button is there. *Operator ruling 2026-10-14.* (R-1023) +240. **The N100 becomes a lived-in box.** Its rule is recorded by the second task of 2026-10-14 + (`TASK-media-folder-fix-and-n100-2026-10-14.md`). *Operator ruling 2026-10-14.* +241. **Tester 1 may follow the drill catalog for the media test, until the operator says „tear it down".** It is the + one exception to §6.5's fence; the teardown is R-1022. *Operator ruling 2026-10-14.* + ### 2026-10-14 — R-1007: how the box asks Immich to repair after a power cut (recorded before the build) 236. **The box repairs Immich's broken photos through Immich's OWN job queue (option 1, spike A4)** — the controller adds @@ -2389,7 +2403,7 @@ is fixed, the drill procedure is: ### The fence -Only a scratch guest is ever pointed at the drill catalog. The drill repo's README says so, and no +Only a scratch guest is ever pointed at the drill catalog. **Exception: Tester 1, for the media test, until the operator's teardown word — §3 decision 241.** The drill repo's README says so, and no customer box has credentials for it. The store listens on the guest's loopback only. ## 7. What slices 1 and 2 actually built diff --git a/documentation/architecture/15-media-library.md b/documentation/architecture/15-media-library.md new file mode 100644 index 00000000..eb65c22d --- /dev/null +++ b/documentation/architecture/15-media-library.md @@ -0,0 +1,135 @@ +# 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: DRAFT** — §5 waits on S5/S6 (after the operator's test). + +## 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//userdata/media → /mnt/felhom-drives//userdata/media (rw; :ro for players) + media/tv//… Sonarr library media/torrents/{tv,movies}/… qBittorrent + media/movies//… 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. **Open +question for the operator (two options):** + +- **A — bind `media/` whole, rw for the download apps.** Simple; measured working. Cost: qBittorrent, Transmission, + Sonarr and Radarr can write into the family's photos and music folders. +- **B — move the video library into its own folder** (e.g. `userdata/video/{tv,movies,torrents,transmission}`) and bind + only that. Download apps never see photos. Cost: a one-time move on boxes that already have files there, and every + template and skeleton entry changes. +- **CC recommends B** for the build: the download apps are the internet-facing ones, and „the box cannot touch your + photos" is a promise worth one move. A is fine for the Tester 1 rig. + +## 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 — pending S6 — a drive +that came back. 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 — PENDING (S5/S6, after the operator's test) + +Predictions written: a pulled drive B stops nothing (no media app has `HDD_PATH` on B); the apps keep a dead view of B; +Plex/Jellyfin may mark items unavailable; on return, running containers do not see B until recreated. + +## 6. The links [FACT, measured calls in `audits/media-all-drives-2026-10-14/tools/wire.py`] + +Per drive: a qBittorrent category `tv-`/`movies-` (save path on that drive, incomplete path on that drive); a +Sonarr/Radarr root folder, a tag, and two client entries („qBittorrent " by category, „Transmission " 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). +- Plex and Jellyfin are NOT linked: they watch folders. Indexers stay the household's choice. +- **Plex behind the tunnel** needs `ADVERTISE_IP=https://.: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. diff --git a/documentation/audits/media-all-drives-2026-10-14/D-plex-remove-deleted.txt b/documentation/audits/media-all-drives-2026-10-14/D-plex-remove-deleted.txt new file mode 100644 index 00000000..1a5e0bbb --- /dev/null +++ b/documentation/audits/media-all-drives-2026-10-14/D-plex-remove-deleted.txt @@ -0,0 +1,107 @@ +== media_a +ls: cannot access '/mnt/felhom-drives/media_a/userdata/media': No such file or directory +/mnt/felhom-drives/media_a/userdata/: +total 20 +drwxrwsr-x 5 root 1000 4096 Oct 14 15:41 . +drwxr-xr-x 4 root root 4096 Oct 14 14:09 .. +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 documents +drwxrwsr-x 2 root 1000 4096 Oct 14 14:08 downloads +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 roms +== media_b +/mnt/felhom-drives/media_b/userdata/: +total 24 +drwxrwsr-x 6 root 1000 4096 Oct 14 13:59 . +drwxr-xr-x 3 root root 4096 Oct 14 13:59 .. +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 documents +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 downloads +drwxrwsr-x 16 root 1000 4096 Oct 14 13:59 media +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 roms + +/mnt/felhom-drives/media_b/userdata/media: +total 64 +drwxrwsr-x 16 root 1000 4096 Oct 14 13:59 . +drwxrwsr-x 6 root 1000 4096 Oct 14 13:59 .. +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 audiobooks +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 books +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 comics +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 grimmory +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 metube +drwxrwsr-x 2 root 1000 4096 Oct 14 15:33 movies +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 music +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 nzbget +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 photos +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 podcasts +drwxrwsr-x 4 root 1000 4096 Oct 14 15:03 torrents +drwxrwsr-x 5 root 1000 4096 Oct 14 14:56 transmission +drwxrwsr-x 2 root 1000 4096 Oct 14 14:10 tv +drwxrwsr-x 2 root 1000 4096 Oct 14 13:59 usenet +2026/10/14 15:38:14 [INFO] [stacks] ParseComposeHDDMounts: found 2 HDD mounts for /opt/docker/stacks/paperless-ngx/docker-compose.yml +2026/10/14 15:38:14 [INFO] [stacks] ParseComposeHDDMounts: found 0 HDD mounts for /opt/docker/stacks/privatebin/docker-compose.yml +2026/10/14 15:38:14 [INFO] [stacks] ParseComposeHDDMounts: found 0 HDD mounts for /opt/docker/stacks/privatebin/docker-compose.yml +2026/10/14 15:38:14 [INFO] [stacks] ParseComposeHDDMounts: found 0 HDD mounts for /opt/docker/stacks/privatebin/docker-compose.yml +2026/10/14 15:38:14 [INFO] [stacks] ParseComposeHDDMounts: found 1 HDD mounts for /opt/docker/stacks/radarr/docker-compose.yml +2026/10/14 15:38:14 [INFO] [stacks] ParseComposeHDDMounts: found 1 HDD mounts for /opt/docker/stacks/sonarr/docker-compose.yml +2026/10/14 15:38:14 [INFO] [stacks] ParseComposeHDDMounts: found 2 HDD mounts for /opt/docker/stacks/transmission/docker-compose.yml +2026/10/14 15:38:14 [INFO] [stacks] ParseComposeHDDMounts: found 1 HDD mounts for /opt/docker/stacks/qbittorrent/docker-compose.yml +2026/10/14 15:39:54 [INFO] [stacks] ParseComposeHDDMounts: found 1 HDD mounts for /opt/docker/stacks/plex/docker-compose.yml +2026/10/14 15:39:54 [INFO] [stacks] ParseComposeHDDMounts: found 1 HDD mounts for /opt/docker/stacks/plex/docker-compose.yml +2026/10/14 15:39:54 [INFO] [stacks] Deploying stack plex with 4 env vars: [DOMAIN, SUBDOMAIN, PLEX_CLAIM, HDD_PATH] +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:23 [INFO] [stacks] ParseComposeHDDMounts: found 1 HDD mounts for /opt/docker/stacks/plex/docker-compose.yml +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] Removed HDD data: /mnt/felhom-drives/media_a/userdata/media (80K) +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 + +drwxrwsr-x 14 root 1000 4096 Oct 14 15:42 /mnt/felhom-drives/media_a/userdata/media +audiobooks books comics grimmory metube movies music nzbget photos podcasts tv usenet +sonarr: 2026-10-14T15:28:50.432279316Z sees A=0 +radarr: 2026-10-14T15:28:45.399705005Z sees A=0 +qbittorrent: 2026-10-14T15:29:00.502333441Z sees A=0 +transmission: 2026-10-14T15:28:58.010876451Z sees A=0 + +drwxrwsr-x 2 root 1000 4096 Oct 14 15:44 torrents +drwxrwsr-x 3 root 1000 4096 Oct 14 15:44 transmission + +17:44:33 qbittorrent restart -> 200 {'ok': True, 'data': {'state': 'running'}, 'message': 'Stack qbittorrent restart +17:44:33 transmission restart -> 200 {'ok': True, 'data': {'state': 'running'}, 'message': 'Stack transmission restar +17:44:33 sonarr restart -> 200 {'ok': True, 'data': {'state': 'running'}, 'message': 'Stack sonarr restart requ +17:44:33 radarr restart -> 200 {'ok': True, 'data': {'state': 'running'}, 'message': 'Stack radarr restart requ +sonarr: started 2026-10-14T15:28:50.432279316Z sees A=0 B=14 +radarr: started 2026-10-14T15:28:45.399705005Z sees A=0 B=14 +qbittorrent: started 2026-10-14T15:29:00.502333441Z sees A=0 B=14 +transmission: started 2026-10-14T15:28:58.010876451Z sees A=0 B=14 + +sonarr: started 2026-10-14T15:28:50.432279316Z sees A=0 +radarr: started 2026-10-14T15:28:45.399705005Z sees A=0 +qbittorrent: started 2026-10-14T15:29:00.502333441Z sees A=0 +transmission: started 2026-10-14T15:28:58.010876451Z sees A=0 +2026/10/14 15:44:32 [INFO] [api] restart requested for stack: qbittorrent +2026/10/14 15:44:32 [INFO] [stacks] Restarting stack: qbittorrent +2026/10/14 15:44:32 [INFO] [stacks] Stack qbittorrent restarted successfully (took 0.1s) +2026/10/14 15:44:33 [INFO] [api] restart requested for stack: transmission +2026/10/14 15:44:33 [INFO] [stacks] Restarting stack: transmission +2026/10/14 15:44:33 [INFO] [stacks] Stack transmission restarted successfully (took 0.1s) +2026/10/14 15:44:33 [INFO] [api] restart requested for stack: sonarr +2026/10/14 15:44:33 [INFO] [stacks] Restarting stack: sonarr +2026/10/14 15:44:33 [INFO] [stacks] Stack sonarr restarted successfully (took 0.1s) +2026/10/14 15:44:33 [INFO] [api] restart requested for stack: radarr +2026/10/14 15:44:33 [INFO] [stacks] Restarting stack: radarr +2026/10/14 15:44:33 [INFO] [stacks] Stack radarr restarted successfully (took 0.1s) + +17:46:00 qbittorrent stop -> 200 +17:46:05 transmission stop -> 200 +17:46:08 sonarr stop -> 200 +17:46:12 radarr stop -> 200 +17:46:32 qbittorrent start -> 200 {'ok': True, 'data': {'state': 'starting'}, 'message': 'Stac +17:46:33 transmission start -> 200 {'ok': True, 'data': {'state': 'starting'}, 'message': 'Stac +17:46:34 sonarr start -> 200 {'ok': True, 'data': {'state': 'starting'}, 'message': 'Stac +17:46:34 radarr start -> 200 {'ok': True, 'data': {'state': 'starting'}, 'message': 'Stac +sonarr: started 2026-10-14T15:46:33.691456Z healthy sees A=14 B=14 +radarr: started 2026-10-14T15:46:34.33316681Z healthy sees A=14 B=14 +qbittorrent: started 2026-10-14T15:46:32.525118942Z healthy sees A=14 B=14 +transmission: started 2026-10-14T15:46:33.100564276Z healthy sees A=14 B=14 + diff --git a/documentation/audits/media-all-drives-2026-10-14/D-plex-throwaway.txt b/documentation/audits/media-all-drives-2026-10-14/D-plex-throwaway.txt new file mode 100644 index 00000000..561031d5 --- /dev/null +++ b/documentation/audits/media-all-drives-2026-10-14/D-plex-throwaway.txt @@ -0,0 +1,18 @@ +17:39:54 deploy throwaway plex -> 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 pro +17:40:19 state running +Attempting to obtain server token from claim token +ProcessedMachineIdentifier="7bcd5d93956601d9573acffac9f6ebcc2787e8d0 +customConnections="https://plex.enkicsifelhom.hu:443/ + +public https://plex.enkicsifelhom.hu/identity -> 200 +public https://plex.enkicsifelhom.hu/web/index.html -> 200 +public GET /library/sections (no token) -> 401 Unauthorized

401 Unauthorized

+public GET /:/prefs (no token) -> 401 Unauthorized

401 Unauthorized

+public GET /myplex/account (no token) -> 401 Unauthorized

401 Unauthorized

+public GET /accounts (no token) -> 401 Unauthorized

401 Unauthorized

+17:41:18 [X] stop -> 200 {'ok': True, 'message': 'Stack plex stop completed'} +17:41:50 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'plex', 'volumes_removed': ['plex_plex_config', 'plex_plex_transcode'], 'hdd_paths_removed': ['/mnt/felhom-drives/media_a/userd +17:41:59 [X] after remove: deployed=False leftovers='/opt/docker/stacks/plex' +/opt/docker/stacks/plex +/mnt/felhom-drives/media_b/userdata/media/tv: + diff --git a/documentation/audits/media-all-drives-2026-10-14/TEST-SHEET.md b/documentation/audits/media-all-drives-2026-10-14/TEST-SHEET.md new file mode 100644 index 00000000..93aafe4c --- /dev/null +++ b/documentation/audits/media-all-drives-2026-10-14/TEST-SHEET.md @@ -0,0 +1,81 @@ +# Test sheet — media apps on two drives (Tester 1) + +About one hour. From any place with a browser. Use only content you have the right to. + +## 1. Addresses + +| What | Address | +|---|---| +| Dashboard | https://felhom.enkicsifelhom.hu | +| Sonarr (shows) | https://sonarr.enkicsifelhom.hu | +| Radarr (films) | https://radarr.enkicsifelhom.hu | +| qBittorrent | https://torrent.enkicsifelhom.hu | +| Transmission | https://transmission.enkicsifelhom.hu | +| Plex (after step 2) | https://plex.enkicsifelhom.hu/web | + +The two test drives are **Media A** and **Media B**. Their folders: + +- Media A: `/mnt/felhom-drives/media_a/userdata/media/tv` and `.../media/movies` +- Media B: `/mnt/felhom-drives/media_b/userdata/media/tv` and `.../media/movies` + +## 2. Plex + +1. Open https://plex.tv/claim and sign in. Copy the code. It works for 4 minutes. +2. In the dashboard, open **Alkalmazások → Plex → Telepítés**. +3. Paste the code into **Plex Claim Token**. For the drive, select **Media A**. Press install. +4. Open https://plex.enkicsifelhom.hu/web. Sign in with your Plex account. +5. Make one library **TV Shows**. Add two folders: Media A `tv` and Media B `tv`. +6. Make one library **Movies**. Add two folders: Media A `movies` and Media B `movies`. + +If the code expired: remove Plex in the dashboard **without** „delete data" (see 7), get a new code, install again. + +## 3. Sonarr and Radarr: first sign-in + +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. + +The download apps are already connected. Do not change **Settings → Download Clients**, except as step 4c says. + +## 4. The test + +Each show or film needs a **root folder** (which drive) and a **tag** (which download entries). The tag `drive-a` sends +the download to Media A. The tag `drive-b` sends it to Media B. **A show with no tag cannot download.** + +a. **Show 1 → Media A.** Sonarr → Add New. Root folder: Media A `tv`. Tags: `drive-a`. Search for one episode. + +b. **Show 2 → Media B.** Root folder: Media B `tv`. Tags: `drive-b`. + +c. **Pick the download app.** Each tag has two entries: qBittorrent and Transmission. Sonarr takes turns. To force one: + **Settings → Download Clients**, open the other one (for example „Transmission drive-a"), remove the tick from + **Enable**, save. Put the tick back after the download starts. Try to use each app at least once. + +d. **Show 3 → the deliberate mismatch.** Root folder: Media **B** `tv`. Tags: `drive-a`. + +e. A film in Radarr, if you want. The same rules. + +f. Look in Plex. The shows must appear after Sonarr imports them. + +## 5. Passwords + +No password is on this sheet. + +- qBittorrent and Transmission: user `admin`. The password is on the app's page in the dashboard + (**Alkalmazások → the app → Megjelenítés**). +- Sonarr and Radarr: the admin you made in step 3. + +## 6. The legal line + +Only download content you have the right to. The download goes from the internet address of the connection this box +uses. Because of how torrents work, the other peers see that address. + +## 7. Do not + +- **Do not remove a media app with „delete data" / „adatok törlése".** On this test layout it deletes the whole + `media` folder of Media A (found today, R-1027). Removing without it is safe. +- Do not change the drives on the Tárhely page. + +## 8. When you are done + +Write **„done"** to CC. CC then reports where each file landed. Then CC removes one drive while the apps run. CC tells +you the time before. diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index d8ae2d29..ffed0fca 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -112,7 +112,7 @@ match what the reader sees is how an instrument stops being believed (R-421). It **nothing was proven on the day this line was drawn** and a stopping line that moves a status is a stopping line that lies. -## Install & onboarding — 8 rows (P3 6, P4 2) +## Install & onboarding — 7 rows (P3 5, P4 2) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -124,7 +124,7 @@ stopping line that lies. | **R-494** | Install & onboarding | P4 | **NARROWED 2026-09-14 by operator ruling → [P3-LOW] the hub COULD create the tunnel at customer creation, for a domain already on Cloudflare. Not blocking: every customer has their own domain and the operator creates the tunnel per day-0 A.1 (`architecture/01-topology-and-trust.md`).** *Original finding, kept:* **[P1-HIGH] A new customer's dashboard has NO reachable address unless the operator hand-makes a Cloudflare tunnel — the link in the setup-code mail is dead.** MEASURED 2026-09-14 on a fresh install from the public ISO (drill intervention **I1**): the claim mail points at `https://felhom.drill0242.felhom.eu`; that name has **no A and no AAAA** record (`dig @1.1.1.1`, control `felhom.enkisfelhom.hu` resolves); the hub has **no tunnel- or DNS-creation code** (`hub/internal/cloudflare/` holds only geo-rule removal; `cf_tunnel_token` is a pasted, optional form field, `configs.go:1478`) — day-0 runbook A.1 makes it a manual Cloudflare-dashboard step that nothing on the customer-create page asks for; the box's own split-horizon resolver on the appliance LAN IP answered `google.com` but not the dashboard name at 13:27:39Z; the agent applied the record at **13:27:44Z** (`lanresolver: applied split-horizon record … ip=192.168.0.158`, 3 m 46 s after the controller started), so the box CAN answer the name — **but only to a device that uses the box as its DNS server, and no document, screen or mail tells a household to do that**; the router and the installer-offered DNS answer nothing. The page was reachable only at the guest's LAN address with the name forced (`curl --resolve …:443:192.168.0.158`). **A volunteer could not have done that.** **What it needs:** an operator ruling — the hub creates the tunnel and DNS at customer creation, or the product gives a household a LAN address that works with no DNS change. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: operator-side automation; the operator creates the tunnel by hand per the day-0 runbook.** | — | — | CC | | **R-504** | Install & onboarding | P4 | **[P3-LOW] `iso.felhom.eu` cannot show an index page on its own — its root returns 404, and the download page lives on the website instead.** MEASURED 2026-09-14: `https://iso.felhom.eu/` and `/index.html` → 404; only named objects answer. The host is an R2 bucket behind a custom domain; whether R2 would serve an uploaded `index.html` at `/` was **not measured** (uploading anything to the public bucket is a publication). The ISO v1.27.0 task puts the Hungarian download page at `felhom.eu/letoltes` (published with the ISO, after the operator's yes). **Remaining:** a redirect from `iso.felhom.eu/` to that page needs a Cloudflare rule the session has no credential for. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (Cloudflare rule)** **Re-ranked 2026-10-03: P3→P4: households are sent to the website's download page; the bare address is cosmetic.** | — | — | operator | -## Apps & catalog — 12 rows (P2 2, P3 5, P4 5) +## Apps & catalog — 14 rows (P2 2, P3 7, P4 5) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -135,6 +135,8 @@ stopping line that lies. | **R-994** | Apps & catalog | P3 | **Seven apps peak above 80 % of a memory limit, and 23 apps' typical use is above their catalog `mem_request`.** MEASURED 2026-10-13 (`documentation/audits/sizing-2026-10-13/SIZING.md` §3, `evidence/catalog-check.json`). Near a limit — risk: the container is stopped for using too much memory: docmost (container 475 of 512 MB), romm (703 of 768), termix (117 of 128), uptime-kuma (227 of 256, tester-1 telemetry), grafana (435 of 512), immich (`immich-server` 1,665 of 2,048 during a 200-photo import), tandoor (827 of 1,024). No container was killed during the bench. Under-stated request (typical ÷ request): calcom ×4.0, adventurelog ×3.6, termix ×3.4, claper ×2.5, grafana ×2.2, komga ×2.2, docmost ×2.1, recipe-importer ×2.1, uptime-kuma ×2.1, zipline ×2.0, and 13 more ≤ ×1.8. Templates NOT changed (read-only run). | **READY** — filed 2026-10-13 | — | Raise the seven limits and the 23 requests in their templates, each through the catalog's harness; re-measure Immich with a larger library first | CC | | **R-1015** | Apps & catalog | P3 | **Three apps tell the household to use a folder that does not exist: `/media/shows`.** Sonarr's first steps (`sonarr/.felhom.yml:74,109`), Jellyfin's (`jellyfin/.felhom.yml:77,83,112,117`) and Plex's (`plex/.felhom.yml:76,111`) name `/media/shows`; the folder every template binds and the skeleton makes is `media/tv`. Found 2026-10-14 (the task named Sonarr and Jellyfin; Plex has it too). | **READY** — filed 2026-10-14 | — | Change the copy (hu + en) to the folder the layout of `15` §2 gives; the live-fix task | CC | | **R-1018** | Apps & catalog | P3 | **The OnlyOffice links (FileBrowser, Nextcloud) have no recorded live proof since the move to Proxmox.** `controller/internal/integrations/` holds two handlers; no audit after the Proxmox move shows a document opened from FileBrowser or Nextcloud in OnlyOffice (searched 2026-10-14). The media-app links of `15` §6 will copy this code, including its revoke-on-stop rule (`lifecycle.go:10-13`). | **OPEN** — filed 2026-10-14 | — | One live walk on a Tier-0 box: install OnlyOffice + FileBrowser, open a document, save, read it back; then stop one app and record what the link does | CC | +| **R-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-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 | @@ -186,10 +188,12 @@ 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 — 3 rows (P3 2, P4 1) +## Storage & devices — 5 rows (P2 2, 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-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/` 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-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 | @@ -283,13 +287,14 @@ stopping line that lies. | **R-989** | Business & legal | P4 | **Operator: read `documentation/audits/selfhst-survey-2026-10-12/FELHOM.md` §6 and rule on each recommendation.** READ-ONLY research 2026-10-12: the selfh.st self-hosting survey 2023–2025 (1,869 / 3,655 / 4,081 respondents, hobby users) against our catalog, features, pitch and hardware. Ten ranked recommendations, none acted on; the top three: a supported-hardware page (none exists — finding, §4), off-site backup as a *why* reason in the pitch (only 25.2 % of the crowd backs up off-site), Syncthing as the largest catalog gap (25.9 %). Tables: `TABLE.md`; method and limits: `METHOD.md`. | **WAITING-ON-OPERATOR** — filed 2026-10-12 | the operator's reading | Operator: rule on each of the ten (yes / no / later). If nothing is done: nothing changes; the research ages (the 2026 survey replaces it) | operator | | **R-990** | Business & legal | P4 | **Operator: read `documentation/audits/forum-research-2026-10-12/FELHOM.md` §5 and rule on each recommendation.** READ-ONLY research 2026-10-12: three years (#7943–#11092, 2,908 posts, 195 people, 82 beginner threads) of the public PROHARDVER! topic „Home server / házi szerver építése”, tagged and counted by person (`FINDINGS.md`, method and spot-check in `METHOD.md`; no username or post text in git). Ten ranked recommendations, none acted on; the top three: a „which machine works” page (8 of 33 beginners who name their hardware own only a Synology, a router disk or a Raspberry Pi — the installer cannot run there; the same gap is R-989's first recommendation from a different source, and the shipped-page half is R-991), „you do not choose an operating system — we do” (the biggest beginner question, 20 of 82 threads), and „bring your old office PC” as the cheap entry. Catalog gap (≥ 3 people): a DLNA server (7), an ad/DNS blocker (5), a camera recorder (5). | **WAITING-ON-OPERATOR** — filed 2026-10-12 | the operator's reading | Operator: rule on each of the ten (yes / no / later). If nothing is done: nothing changes; the research ages | operator | -## Process & tooling — 23 rows (P3 9, P4 14) +## Process & tooling — 25 rows (P3 10, P4 15) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| | **R-1021** | Process & tooling | P3 | **`build-hub.sh` builds STALE hub code when its `git pull` fails — it only warns.** 2026-10-14: the shared checkout `git/felhom.eu` was behind `origin/main` (`52b6599d` vs `822d0393`) and its pull aborted on untracked leftovers (`audits/sizing-2026-10-13/`, `audits/usenet-2026-10-12/`, a Facebook folder, a modified `walk.py`) — the script would have shipped hub 0.149-era code labelled 0.150.0. This session built from its own clean clone instead (a copy of the script with only `REPO_DIR` changed). | **OPEN** — filed 2026-10-14 | operator: whose are the leftovers in the shared checkout | CC: make the build refuse unless the source tree is clean and `HEAD == origin/main` (the clean-tree gate, in the script); the operator or their owner cleans the shared checkout | CC | | **R-992** | Process & tooling | P3 | **The drill-catalog route that `09` §6.5 documents cannot be followed: `admin/app-catalog-drill` is PRIVATE and no box has catalog credentials.** Measured 2026-10-12 while publishing the Usenet pair: the box's clone fails with `fatal: could not read Username for 'https://gitea.dooplex.hu'` (exit 128), the Gitea API reports the drill repo `private=true` and the live catalog `private=false`, and NEITHER the current controller.yaml on 9202 NOR any of its 30 saved copies has ever carried `git.username` or `git.token` (the controller supports both — `internal/config/config.go` GitConfig). The route worked for the campaigns of 2026-10-10 and the torrent pair earlier the same day, so the repository's visibility changed between then and now. **Worked around, not fixed:** that campaign staged its two templates in the box's own catalog cache (`audits/usenet-2026-10-12/tools/stage_in_cache.py`), which is where the sync copies a catalog change anyway, with the sync interval raised to 12h for the duration and rescan used in place of sync. The real fix is an operator decision: either a read-only catalog credential on the scratch box (a secret on a Tier-0 guest) or making the drill repository readable. Until then every new-app campaign hits this. | WAITING-ON-OPERATOR | the operator's choice between a box credential and a readable drill repo | Decide; then either set `git.username`/`git.token` on 9202 from the credentials store, or flip the drill repo, and correct `09` §6.5 either way | operator | | **R-1017** | Process & tooling | P3 | **A comment names a guard test that does not exist.** `felhom-controller/controller/internal/appbackup/paths.go:58` says „`TestNoBareDrivePathToUserdataDir` is the guard that keeps the count from growing”; no test of that name (or `NoBareDrive*`) exists in the repo (grep 2026-10-14; positive control: `TestRemoveStack_R800_UserdataKeptIsNamed` is found). A comment asserting an invariant with no test pinning it — the class in the `felhom-testing` skill. | **READY** — filed 2026-10-14 | — | Write the guard (a test that fails when a bare drive path reaches `UserdataDir`), red-proof it, or correct the comment | CC | +| **R-1022** | Process & tooling | P3 | **Tester 1 follows the drill catalog and carries two test drives — teardown owed (operator ruling (e), 2026-10-14).** Steps, only on the operator's word: (1) remove the test apps (Sonarr, Radarr, qBittorrent, Transmission, Plex, Jellyfin) through the dashboard WITHOUT „delete data” (R-1027); (2) decommission Media A and Media B through Tester 1's dashboard; (3) `repoint.py restore` — `controller.yaml` from `controller.yaml.pre-media-2026-10-14`, read back byte-identical, `repo_url` the live catalog; remove `drill-catalog.git` from the controller volume; (4) on demo-hp: `qm set 341 --delete scsi2,scsi3`, then delete the two volumes BY NAME (`nvme-scratch:341/vm-341-disk-3.raw`, `-disk-4.raw`) — never `qm destroy`; (5) VM memory unchanged (8192, not raised); (6) drill repo reset to live `main`. Tools: `audits/media-all-drives-2026-10-14/``tools/`. | **WAITING-ON-OPERATOR** — filed 2026-10-14 | the operator's teardown word | Run the six steps; quote each read-back | CC | | **R-987** | Process & tooling | P4 | **The 53 catalog app images have never been scanned for known vulnerabilities.** Fix: a monthly trivy pass over the images the catalog pins, reported, not gating. | OPEN | — | Add to the monthly re-test | CC | | **R-986** | Process & tooling | P3 | **No dependency or secret scanner runs on any push, so a known-vulnerable library or a committed key is found only by a review like this one.** Proposed gate: govulncheck (called vulns only), gitleaks on the diff, trivy on our own images; start as WARN with an accepted-hits file, then FAIL. Tools and pinned versions: `audits/security-review-2026-10-12/SCANNERS.md`. Note: gitleaks' default rules did not find R-908's key. | OPEN | — | Build the gate, warn-first | CC | | **R-733** | Process & tooling | P3 | **[P3-LOW] The test bench has NO swap and the boxes have 512 MiB — so a box proof can pass on swap where the bench fails, and nobody records whether a customer guest has swap.** MEASURED 2026-09-30 (R-732): immich's first start was OOM-killed 61–104 times on the bench (swap 0) and passed on 9202 by swapping ~108 MB; the bench given 512 MiB swap passed too. demo-hp 9201, 9202 and demo-felhom 9201 all read `swap: 512`; the golden's guest config is not recorded in its bake evidence, so a customer guest's swap is NOT measured. The harness's memory watch judges `anon` against the limit and never reads `memory.swap.current`. **Needs:** the golden's `swap` read and recorded; the box walk and the harness report `memory.swap.peak` beside `anon`; a decision whether proofs run with swap off (the stricter venue, as R-732's fix was proven). | **READY — rank P3-LOW; owner: CC (harness + golden evidence)** **2026-10-06 night: NARROWED** — the harness records each container's `swap_peak` and the venue's swap (catalog branch `night-held-2026-10-06`, `3f4611c`; SwapRecorded red-proved; bench SwapTotal 0 kB, 9202 524288 kB). LEFT: the golden's swap in its bake evidence, the box walk's record, and whether proofs run with swap off. | — | — | CC | @@ -310,6 +315,7 @@ stopping line that lies. | **R-786** | Process & tooling | P4 | **[P3-LOW] SparkyFitness's onboarding record has six open rows** (`app-catalog-felhom.eu/onboarding/sparkyfitness.md`): 0.5 runtime internet (food search providers), 0.7 the phone app's sign-in route through traefik, 1.6 the env names the server reads, 1.7 the entrypoint read, 5.4 a second memory watch at another limit, 8.3 no logo/screenshots on felhom.eu (404). Everything else measured this session (bench + 9202). **Needs:** each row measured, or n/a with a reason. | **READY — rank P3-LOW; owner: CC (catalog)** **Re-ranked 2026-10-03: P3→P4: onboarding record completeness; no household meets it directly.** | — | — | CC | | **R-807** | Process & tooling | P4 | **[P3-LOW] 13 apps stay UNDETERMINED because their seeds write only to the database, so their upload / media / cache / redis volumes stay empty.** MEASURED 2026-10-02 (`audits/persistence-sweep-2026-10-02/A/sweep/TABLE.md`): claper, crafty-controller, dawarich (redis), docmost, gramps-web, immich (ML cache), outline (+redis), sparkyfitness, tandoor, vikunja, wger, wishlist, zipline — plus plex (no seed route: a plex.tv claim token) and wanderer (no fixture; unhealthy under the gate). None is BROKEN: nothing was written outside a preserved folder. **Needs:** per app, a seed that uploads one file (or the volume named n/a with a reason: a redis cache, an ML model cache). | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: test coverage; nothing was found broken.** | — | — | CC | | **R-996** | Process & tooling | P4 | **`scripts/dooplex-offsite/test_dooplex_offsite.py` fails 28 of 30 tests when the push comes from a git WORKTREE, so the pre-push hook refuses a correct push.** MEASURED 2026-10-13 (sizing session): git runs the hook with `GIT_DIR` set; in a worktree that is an absolute path (`.git/worktrees/`), and the test's own scratch repos inherit it — its `git commit` fails. Standalone: 30/30 OK; with `GIT_DIR=`: 28 errors; main clone with `GIT_DIR=.git` (relative): OK. Workaround used: push from a full clone. | **READY** — filed 2026-10-13 | — | Drop `GIT_DIR`/`GIT_WORK_TREE`/`GIT_INDEX_FILE` from the env the test's git calls use; red-proof with the absolute GIT_DIR | CC | +| **R-1025** | Process & tooling | P4 | **A power drill on demo-hp cuts Tester 1 too, and nothing warns the session using it.** 2026-10-14 17:25 CEST another session cut demo-hp's plug (`plugcut.py --plug hp`, the Immich drill) while this session ran downloads on Tester 1 (VM 341 runs on demo-hp); Tester 1 rebooted and came back by itself. The drill's fence checks Tester 1 for backups and package jobs only. | **OPEN** — filed 2026-10-14 | — | Add „a test runs on Tester 1” to the fence (a marker file the testing session writes), or name the rule in `runbooks/target-selection.md` | CC |