diff --git a/.claude/rules/docs.md b/.claude/rules/docs.md index 22fc4419..fa516000 100644 --- a/.claude/rules/docs.md +++ b/.claude/rules/docs.md @@ -17,6 +17,7 @@ repo. Sibling repos point here; this is where the pointed-at thing must actually | host addresses, routes, node names, break-glass, what is provisioned | `operations/nodes.md` | | Tailscale topology, accept-dns/accept-routes | `operations/tailscale.md` | | which box may be broken, and what may be done to it | `runbooks/target-selection.md` | +| what happened on the lived-in N100 (dated, each line with its register row) | `lived-in/n100.md` | | an operational procedure | `runbooks/RUNBOOK-*.md` | | logging levels and phrasing | `runbooks/logging-conventions.md` | | a spike or campaign result | `audits/` | diff --git a/CLAUDE.md b/CLAUDE.md index 153dec04..63af2aa8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,6 +33,7 @@ Four surfaces in one repo, plus the design home for the whole system: | UI, tokens, badges, Hungarian copy | the **`felhom-ui-design`** skill | | host addresses, break-glass, node facts | `documentation/operations/nodes.md` — never restate them | | which box may I break | `documentation/runbooks/target-selection.md` | +| a problem seen on the N100 (the lived-in box, `09` §3 decision 254) | goes into `documentation/lived-in/n100.md` AND the register | | what version is live anywhere | ask the hub (`/hosts`, `/configs`) or the box — **never a doc** | | the authoritative design | `documentation/architecture/01..11-*.md` (00 = the capability map) | diff --git a/CONTEXT.md b/CONTEXT.md index 137332d8..aa65e4b2 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -25,6 +25,13 @@ > while a release is ahead. CC has no scoped route to the global floor (R-1051) — only the operator page with the > operator's password. A box on an `-rc` tag is never moved by the floor (R-1053). +> **2026-10-15 (evening, last) — the N100 is the lived-in box (`09` §3 decision 254).** Product paths only; no +> deliberate failure without the operator's word; diary `documentation/lived-in/n100.md`; Tier 0 is demo-hp alone. Media +> apps installed through the dashboard and wired by the box; films proven never off-site (the controller's classifier; +> vzdump excludes mp8). The USB split was NOT done: `hdd_1` is the whole-guest backup target (R-1062, operator decides); +> the decommission flow's half-state on an agent refusal is R-1063; a claimed Plex gets no libraries until a wiring run +> (R-1065). The box-walk library has no N100 row: `audits/n100-lived-in-2026-10-15/tools/n100.py` overrides it. + > **2026-10-15 (evening, later) — controller v0.314.0, the first release under decision 250.** The floor 0.314.0 (MinAgent > 0.155.0) was raised at 13:11 UTC by CC's token (`HUB_TOKEN` in `~/.config/credentials`, not `~/.felhom-hub/cc-token`) > after Tester 1, demo-hp and demo-felhom ran it through per-customer floors and the box walk's checks were green; the diff --git a/REPORT-n100-lived-in-2026-10-15.md b/REPORT-n100-lived-in-2026-10-15.md new file mode 100644 index 00000000..e629f0e4 --- /dev/null +++ b/REPORT-n100-lived-in-2026-10-15.md @@ -0,0 +1,92 @@ +# REPORT — the N100 becomes the lived-in box (2026-10-15) + +Written in ASD-STE100 Simplified Technical English. Read first: `runbooks/target-selection.md`, `operations/nodes.md`, +`07-backup-architecture.md`, `15-media-library.md`, `09` §3 decisions 240, 243, 250. Evidence: +`documentation/audits/n100-lived-in-2026-10-15/`. + +## What waits on you + +1. **The sheet** (about 30 minutes): `documentation/audits/n100-lived-in-2026-10-15/SHEET.md`. Install and claim Plex; + make the admins in Prowlarr, Sonarr and Radarr; add download sources in Prowlarr; add one show and one film. Then say + „done". If you do nothing, the apps wait, safe behind their gate. +2. **Decide (R-1062):** the USB drive is also the box's whole-box backup drive. Split it anyway? My pick: **no, keep one + drive**. If you do nothing, it stays one drive. + +## 1. The lived-in rule, and the files + +`09` §3 **decision 254**: +- The N100 changes only through the product: the dashboard, the hub, the release train, the OS update rings. +- No deliberate failure without your word for that one act. +- Reads and observation are free. A problem goes into the diary and the register. +- It is not Tier 2. It is no longer „reach here first". + +Decisions 240 and 243 point at it now. + +Files changed: +- `architecture/09-update-architecture.md`; +- `runbooks/target-selection.md` (a new „lived-in" row; Tier 0 is demo-hp alone; the N100 section rewritten); +- `operations/nodes.md` (role and drive lines); +- `architecture/11-os-updates.md` (ring 0 no longer calls the N100 disposable); +- `CLAUDE.md` and `.claude/rules/docs.md` (one line each: the diary); +- `documentation/lived-in/n100.md` (new); +- `STATUS.md`, `CONTEXT.md`, the register. +- My own memory index: it called both demo boxes disposable. + +## 2. What the N100 had, and what the 6 GB is + +- Controller 0.314.0, agent 0.156.1. Guest: 12 GB of memory, 0.2 GB used. One app (opengist). +- The five media apps need about 2 GB by the estimator's rule. They fit. +- `/dev/dri`: the host has it, the guest does not. So Plex cannot use hardware transcoding today. +- **The USB drive is not only app test data.** It holds: + - 2.7 GB: the box's nightly whole-box backups (`dump/`). The host storage `felhom-backup` points at this drive. + - 3.1 GB: app backups and an off-site restore copy. + - 422 MB: leftovers of removed apps. + - Empty media folders. + + There are no family files on it. +- **So I did not split it.** It is the box's whole-box backup target. The product refuses to retire such a drive, and the + box has no other drive to take that role. I also did not press the retire button. Reading the code showed that a + refused retire leaves the dashboard showing the drive as retired (R-1063). + +## 3. Films and off-site — proven before any download + +I ran the controller's own classifier on the deployed apps: +- off-site takes no media folder; +- the cross-drive tier would take only qBittorrent's downloads, and only if someone switches it on. It is off. With one + data drive it falls back to the internal SSD, and takes state only, not downloads; +- the whole-box backup's own log says: „excluding bind mount point mp8 ('/mnt/felhom-drives')". + +## 4. What the box linked by itself + +- I installed qBittorrent, Prowlarr, Sonarr and Radarr through the dashboard's own call, and sent no drive. Each was + healthy in under 35 s. The box picked `hdd_1` by itself. +- From the apps' own answers: + - Sonarr: `…/media/tv`. Radarr: `…/media/movies`. Both have the tag `hdd-1`, and qBittorrent as their client. + - qBittorrent: the categories `tv-hdd-1` and `movies-hdd-1`, saving under `…/media/torrents/` on the same drive. + - Prowlarr: linked to Sonarr and Radarr. +- A stranger meets the gate on Prowlarr, Sonarr and Radarr, and qBittorrent's own login. +- **Not done by itself:** a freshly claimed Plex gets no libraries until the next linking run (R-1065, from the code). + The sheet tells you what to press if it happens. + +## 5. demo-hp's warning + +Gone. I removed `/mnt/sizing-bench` through the dashboard, and the hub showed demo-hp **OK** at 14:56 UTC. The path held +two apps' cross-drive copies, and the dashboard said nothing about them (R-1064). + +## 6. Wrong claims in the prompt; commits; register; STATUS + +**Claims in the prompt that turned out wrong:** +1. „the 6.1 GB … app test data": it is mostly the box's own backups. The drive is the whole-box backup target, so the + split cannot go through the product. +2. „The box wires … Plex by itself": not right after the claim (R-1065). +3. „HD710 PRO": that is the USB enclosure. The disk inside is a TOSHIBA MQ04ABF100 (931.5 GB). True, but incomplete. + +**Commits:** felhom.eu (this commit). No product code. + +**Register:** before **152** · opened **4** (R-1062, R-1063, R-1064, R-1065) · closed **0** · after **156**. + +**STATUS.md:** 61 lines. + +**Teardown:** I installed four apps on the N100 on purpose (they stay). I deleted a temporary test file in the +controller tree; it was never committed. On demo-hp I removed one storage path. The hub was read only, through my token. +ep0, DooPlex and Tester 1 were not touched. diff --git a/STATUS.md b/STATUS.md index 85c8c7ad..6f8372d9 100644 --- a/STATUS.md +++ b/STATUS.md @@ -6,6 +6,7 @@ `documentation/backlog/STATUS-HISTORY.md`. Reports: `REPORT-night-2026-10-14.md`, `REPORT-immich-rollout-2026-10-14.md`, `REPORT-seo-felho-tarhely-2026-10-14.md`, `REPORT-immich-repair-2026-10-14.md`, `REPORT-power-cut-fixes-2026-10-14.md`, `REPORT-night-power-2026-10-13.md`, `REPORT-poweroff.md`. ## What works +- **The N100 is the lived-in box** (since 2026-10-15): used like a household's box, changed only through the product. qBittorrent, Prowlarr, Sonarr and Radarr are installed and linked by the box; films never go off-site (checked before any download). Its diary: `documentation/lived-in/n100.md`. - A restored app comes back exactly as safe as it was at the backup: closed to strangers until its first account is made (0.314.0, proven on the scratch box). A box on a test build now moves with the floor (Tester 1 did, by itself). The box claims Plex again (demo-hp's Plex claimed through the dashboard, 2026-10-15). A brand-new box from the public installer updated itself to the floor 3 minutes after it was linked (test box, since removed). - Scripts use the hub with a scoped token, not your password (hub 0.153.0): CC sets the floor with its own token; no token can see a secret, delete anything, or send, make or show a household's password or link. Your new hub password is yours alone (changed 2026-10-15). - Every public name of the three test boxes answers only over https: plain http gets a 301 to the same https address, and browsers are told to remember it for one day (29 names checked, 2026-10-14 night). @@ -41,8 +42,10 @@ | 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-1062 | The N100's USB drive is also its whole-box backup drive. Split it in two anyway? | **No — keep one drive**; the lived-in box keeps its nightly whole-box backup | Split: the box loses its local whole-box backup (or add a second drive first) | it stays one drive; multi-drive tests stay on Tester 1 | — | ## Waiting on you — do +- **The N100 sheet (about 30 minutes):** `documentation/audits/n100-lived-in-2026-10-15/SHEET.md` — install Plex and claim it, make the admins in Prowlarr/Sonarr/Radarr, add your download sources in Prowlarr, add one show and one film. Then say „done". If not: the media apps wait, safe behind their gate. - **Delete the test customer „Tester3” in the hub (browser only).** Its test box is already gone and switched off on purpose, so no alarm is coming. If not: a dead test customer stays in your list. - **Hub 0.153.0 (your rulings 13:20 + R-1059 „A"): no token can resend a claim code, create or edit a customer, make a passphrase or a link, or re-issue an off-site password.** Live since 13:39 (checked with CC's token: all six refused). - **Remove the test server „plex-k6” from your Plex account** (plex.tv → Settings → Authorized Devices). The claim test passed and the server is gone from Tester 1. If not: a dead server stays listed in your Plex apps. diff --git a/documentation/architecture/09-update-architecture.md b/documentation/architecture/09-update-architecture.md index 2113d96a..b4a3f6be 100644 --- a/documentation/architecture/09-update-architecture.md +++ b/documentation/architecture/09-update-architecture.md @@ -948,6 +948,26 @@ its length, and both fixes cost something the household would notice — operato **Extended the same day (operator, R-1059 = A):** the off-site and PBS-DR re-issues (they mint a household credential for the box) are session only too; „Off-site: write" keeps the windows and freeze. +### 2026-10-15 (evening) — the N100 is the lived-in box (recorded by the n100-lived-in task; decisions 240 and 243) + +254. **The N100 (`demo-felhom`) is the lived-in box.** *Operator ruling 2026-10-14 (decision 240), its timing + decision 243 (after the Tester 1 all-drives test — done 2026-10-15), written down 2026-10-15 at the operator's + request.* Decision 240 said the rule „is recorded by the second task of 2026-10-14" — it never was; this is it. + 1. **It changes only through the product's own paths:** the dashboard, the hub, the normal release train (the floor + under decision 250, the weekly golden), the OS update rings. No drill catalog, no hand edits inside the guest, no + `docker` command that changes state, no reinstall. + 2. **No deliberate failure** — power cut, drive pull, drill, restore test — **without the operator's word for that + one act.** A failure that happens by itself is observed and recorded, and repaired by hand only where the + household could do the same. + 3. **Reads, logs, metering and observation are free.** A problem found goes into the diary + (`documentation/lived-in/n100.md`) and the register. + 4. **It stays a demo box:** no paying customer's data; it is not Tier 2. It is no longer „reach here first" — Tier 0 + is demo-hp (and its guests and VMs) alone (`runbooks/target-selection.md`). + 5. **The one hand change granted before the rule (operator, 2026-10-15 16:31): split the USB drive in two.** NOT + done: the drive is the box's whole-guest backup target (`felhom-backup`) and holds its nightly backups, so the + product refuses to decommission it and the N100 has no other drive to move that role to (R-1062, + `audits/n100-lived-in-2026-10-15/B/`). The grant is not used; a split needs a new word. + ### 2026-10-15 (afternoon) — a restore puts the setup gate back as it was (operator brief, R-1057 raised to P2) 253. **A restore puts the setup gate back exactly as it was when the backup was taken.** *Operator brief 2026-10-15 @@ -1008,6 +1028,8 @@ its length, and both fixes cost something the household would notice — operato (`15-media-library.md` §2; R-1014) 243. **The N100 lived-in setup waits until the all-drives build has been tested on the Tester 1 rig.** Until then the N100 stays Tier 0 and is not fenced (it may host a second test rig). This replaces decision 240's timing. + **Fulfilled 2026-10-15** (the Tester 1 rig was tested; release 0.313.0): the rule is decision 254 — the N100 is no + longer Tier 0. *Operator ruling 2026-10-14.* ### 2026-10-14 (afternoon) — media apps on every drive (recorded before the work; the media-all-drives brief) @@ -1020,7 +1042,8 @@ its length, and both fixes cost something the household would notice — operato 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.* + (`TASK-media-folder-fix-and-n100-2026-10-14.md`). *Operator ruling 2026-10-14.* **That task did not record it** (its N100 part was removed the + same evening, decision 243); the rule is decision 254 (2026-10-15). 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.* diff --git a/documentation/architecture/11-os-updates.md b/documentation/architecture/11-os-updates.md index a02f18a8..46befbb3 100644 --- a/documentation/architecture/11-os-updates.md +++ b/documentation/architecture/11-os-updates.md @@ -148,8 +148,9 @@ has not ruled on it as such. ### 5.1 Two rings -- **Ring 0:** demo-felhom (N100) and demo-hp. They are disposable (`runbooks/target-selection.md`), and - they have different hardware. They take every update first. +- **Ring 0:** demo-felhom (N100) and demo-hp, on different hardware. They take every update first. demo-hp is + disposable; the N100 is the lived-in box (`09` §3 decision 254) — it takes ring 0 through the product like any box, + which is the point: a lived-in box meets an update before a customer does (`runbooks/target-selection.md`). - **Ring 1:** every other box. It takes only what ring 0 approved. ### 5.2 Two lanes diff --git a/documentation/audits/n100-lived-in-2026-10-15/B/01-state.txt b/documentation/audits/n100-lived-in-2026-10-15/B/01-state.txt new file mode 100644 index 00000000..998d2593 --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/B/01-state.txt @@ -0,0 +1,46 @@ +== host +demo-felhom +felhom-agent 0.156.1 + total used free shared buff/cache available +Mem: 15772 2408 8108 66 5664 13363 +NAME SIZE TYPE FSTYPE MOUNTPOINT MODEL SERIAL +sda 476.9G disk AirDisk 512GB SSD QDF922W009654S30EX +|-sda1 1007K part +|-sda2 1G part vfat /boot/efi +`-sda3 475.9G part LVM2_member + |-pve-swap 8G lvm swap [SWAP] + |-pve-root 96G lvm ext4 / + |-pve-data_tmeta 3.6G lvm + | `-pve-data-tpool 348.8G lvm + | |-pve-data 348.8G lvm + | |-pve-vm--9201--disk--2 32G lvm ext4 + | `-pve-vm--9201--disk--3 250G lvm ext4 + `-pve-data_tdata 348.8G lvm + `-pve-data-tpool 348.8G lvm + |-pve-data 348.8G lvm + |-pve-vm--9201--disk--2 32G lvm ext4 + `-pve-vm--9201--disk--3 250G lvm ext4 +sdb 931.5G disk ext4 /mnt/hdd_1 TOSHIBA MQ04ABF100 65NOP3HDT +VMID Status Lock Name +9201 running demo-felhom +== guest +cores: 3 +memory: 12288 +mp0: local-lvm:vm-9201-disk-3,mp=/var/lib/felhom,backup=1,size=250G +mp8: /mnt/felhom-drives,mp=/mnt/felhom-drives +mp9: /var/lib/felhom-agent/guests/9201/bootstrap,mp=/etc/felhom-bootstrap,ro=1 +rootfs: local-lvm:vm-9201-disk-2,size=32G +swap: 512 +gitea.dooplex.hu/admin/felhom-controller:0.314.0 +192.168.0.149 169.254.253.2 172.16.253.1 172.17.0.1 172.18.0.1 + total used free shared buff/cache available +Mem: 12288 222 10945 8 1128 12065 +Filesystem Size Used Avail Use% Mounted on +/dev/mapper/pve-vm--9201--disk--2 32G 1.2G 29G 4% / +/dev/sdb 916G 6.2G 864G 1% /mnt/felhom-drives/hdd_1 +ls: cannot access '/dev/dri': No such file or directory +cloudflared Up 41 hours (healthy) +felhom-controller Up 2 hours (healthy) +filebrowser Up 21 hours (healthy) +opengist Up 12 hours (healthy) +traefik Up 12 hours diff --git a/documentation/audits/n100-lived-in-2026-10-15/B/02-drive.txt b/documentation/audits/n100-lived-in-2026-10-15/B/02-drive.txt new file mode 100644 index 00000000..adec941c --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/B/02-drive.txt @@ -0,0 +1,26 @@ +NAME TRAN VENDOR MODEL SERIAL SIZE +sdb usb ADATA TOSHIBA MQ04ABF100 65NOP3HDT 931.5G +ID_BUS=ata +ID_MODEL=TOSHIBA_MQ04ABF100 +ID_SERIAL=TOSHIBA_MQ04ABF100_65NOP3HDT +ID_USB_MODEL=HD710_PRO +ID_USB_VENDOR=ADATA +by-path +card0 +renderD128 +== top +total 32 +drwxr-xr-x 5 root root 4096 Jul 28 21:25 . +drwxr-xr-x 5 root root 4096 Aug 3 09:15 .. +drwxr-xr-x 2 root root 4096 Oct 15 04:59 dump +drwxr-xr-x 6 100000 100000 4096 Jul 18 22:11 felhom-data +drwx------ 2 root root 16384 Jul 18 18:41 lost+found +== du +2.7G /mnt/hdd_1/dump +3.5G /mnt/hdd_1/felhom-data +16K /mnt/hdd_1/lost+found +== felhom-data +32K /mnt/hdd_1/felhom-data/shares +528K /mnt/hdd_1/felhom-data/userdata +422M /mnt/hdd_1/felhom-data/appdata +3.1G /mnt/hdd_1/felhom-data/backups diff --git a/documentation/audits/n100-lived-in-2026-10-15/B/03-drive-detail.txt b/documentation/audits/n100-lived-in-2026-10-15/B/03-drive-detail.txt new file mode 100644 index 00000000..37883029 --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/B/03-drive-detail.txt @@ -0,0 +1,47 @@ +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-backup dir active 960303848 6435888 905013448 0.67% +felhom-pbs pbs active 0 0 0 0.00% +local dir active 98497780 32496832 60951400 32.99% +local-lvm lvmthin active 365760512 9290317 356470194 2.54% +dir: felhom-backup + +== dump +-rw-r--r-- 1 root root 950588793 Oct 13 04:53 vzdump-lxc-9201-2026_10_13-04_52_51.tar.zst +-rw-r--r-- 1 root root 16 Oct 13 04:53 vzdump-lxc-9201-2026_10_13-04_52_51.tar.zst.notes +-rw-r--r-- 1 root root 1505 Oct 14 04:54 vzdump-lxc-9201-2026_10_14-04_54_24.log +-rw-r--r-- 1 root root 951109351 Oct 14 04:54 vzdump-lxc-9201-2026_10_14-04_54_24.tar.zst +-rw-r--r-- 1 root root 16 Oct 14 04:54 vzdump-lxc-9201-2026_10_14-04_54_24.tar.zst.notes +-rw-r--r-- 1 root root 1504 Oct 15 04:59 vzdump-lxc-9201-2026_10_15-04_59_17.log +-rw-r--r-- 1 root root 951089306 Oct 15 04:59 vzdump-lxc-9201-2026_10_15-04_59_17.tar.zst +-rw-r--r-- 1 root root 16 Oct 15 04:59 vzdump-lxc-9201-2026_10_15-04_59_17.tar.zst.notes +== backups +4.0K /mnt/hdd_1/felhom-data/backups/offsite-proof +1.6G /mnt/hdd_1/felhom-data/backups/offsite-restore +1.3G /mnt/hdd_1/felhom-data/backups/primary +274M /mnt/hdd_1/felhom-data/backups/secondary +== appdata +12K /mnt/hdd_1/felhom-data/appdata/romm +24K /mnt/hdd_1/felhom-data/appdata/paperless +63M /mnt/hdd_1/felhom-data/appdata/nextcloud +360M /mnt/hdd_1/felhom-data/appdata/immich +== userdata +/mnt/hdd_1/felhom-data/userdata +/mnt/hdd_1/felhom-data/userdata/media +/mnt/hdd_1/felhom-data/userdata/media/nzbget +/mnt/hdd_1/felhom-data/userdata/media/usenet +/mnt/hdd_1/felhom-data/userdata/media/movies +/mnt/hdd_1/felhom-data/userdata/media/music +/mnt/hdd_1/felhom-data/userdata/media/comics +/mnt/hdd_1/felhom-data/userdata/media/podcasts +/mnt/hdd_1/felhom-data/userdata/media/audiobooks +/mnt/hdd_1/felhom-data/userdata/media/transmission +/mnt/hdd_1/felhom-data/userdata/media/transmission/watch +/mnt/hdd_1/felhom-data/userdata/media/grimmory +/mnt/hdd_1/felhom-data/userdata/media/torrents +/mnt/hdd_1/felhom-data/userdata/media/tv +/mnt/hdd_1/felhom-data/userdata/media/photos +/mnt/hdd_1/felhom-data/userdata/media/metube +/mnt/hdd_1/felhom-data/userdata/media/books +/mnt/hdd_1/felhom-data/userdata/media/books/metadata.db +/mnt/hdd_1/felhom-data/userdata/media/books/metadata.db-shm +/mnt/hdd_1/felhom-data/userdata/media/books/metadata.db-wal diff --git a/documentation/audits/n100-lived-in-2026-10-15/B/04-sizing.txt b/documentation/audits/n100-lived-in-2026-10-15/B/04-sizing.txt new file mode 100644 index 00000000..ff72daef --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/B/04-sizing.txt @@ -0,0 +1,7 @@ +standing now: typical 27 MB, peak 68 MB; rule (platform 470 + peak) x 1.5 = 807 MB vs guest 12288 MB -> fits +standing + the five: typical 636 MB, peak 841 MB; rule (platform 470 + peak) x 1.5 = 1966 MB vs guest 12288 MB -> fits + plex: typical 232 MB, peak 234 MB (measured) + qbittorrent: typical 163 MB, peak 318 MB (onboarding) + prowlarr: typical 41 MB, peak 44 MB (onboarding) + sonarr: typical 77 MB, peak 77 MB (onboarding) + radarr: typical 96 MB, peak 100 MB (measured) diff --git a/documentation/audits/n100-lived-in-2026-10-15/B/05-dashboard-view.txt b/documentation/audits/n100-lived-in-2026-10-15/B/05-dashboard-view.txt new file mode 100644 index 00000000..cc0fc141 --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/B/05-dashboard-view.txt @@ -0,0 +1,16 @@ +/api/storage/paths -> 404 {"_raw": "404 page not found\n"} +/api/disks -> 200 {"data": {"vmid": 9201, "disks": [{"name": "felhom-backup", "type": "local-dir", "state": "attached", "backing_device": "/dev/sdb", "mount_path": "/mnt/hdd_1", "class": "slow", "role": "user-data", "data_bearing": true, "data_reason": "device is mounted", "total_bytes": 983351140352, "used_bytes": 6590349312, "used_fraction": 0.00670192878369056, "durable_id": "uuid:47a3361a-91e0-4831-a69d-27f540ed3f48", "wipe_durable_id": "byid:wwn-0x5000039ddb108568", "guest_attached": false, "backup_target": true, "guest_path": "/mnt/felhom-drives/hdd_1", "bound_under_parent": true, "smart": {"health": "PASSED", "model_name": "TOSHIBA MQ04ABF100", "temperature_c": 35, "power_on_hours": 5449, "reallocated_sectors": 0, "pending_sectors": 0, "offline_uncorrectable": 0, "udma_crc_errors": 0, "critical_warning": null, "media_errors": null, "percentage_used": null}}, {"name": "local", "type": "local", "stat +/api/backup/status -> 200 {"ok": true, "data": {"enabled": true, "running": false}} +/api/offbox/status -> 404 {"ok": false, "error": "endpoint not found"} +/api/backup/offsite/status -> 404 {"ok": false, "error": "endpoint not found"} +/api/selfupdate/status -> 200 {"ok": true, "data": {"running": false, "last_check": {"current_version": "0.314.0", "latest_version": "0.314.0", "update_available": false, "checked_at": "2026-10-15T12:56:24Z"}, "last_state": {"status": "success", "previous_version": "0.313.0", "previous_image": "gitea.dooplex.hu/admin/felhom-controller:0.313.0", "target_version": "0.314.0", "target_image": "gitea.dooplex.hu/admin/felhom-controller:0.314.0", "initiated_at": "2026-10-15T12:55:48Z", "initiated_by": "auto-floor", "completed_at": "2026-10-15T12:55:54Z"}}} +/api/storage/backup-target -> 200 {"data": {"degraded": false, "known": true, "label": "Külső tárhely (hdd_1)", "target": "felhom-backup"}, "ok": true} +/api/storage/media/status -> 200 {"data": {"pending": false, "drives": null, "apps": null}, "ok": true} +/api/storage/ -> 404 {"_raw": "404 page not found\n"} + + +{'path': '/mnt/felhom-drives/hdd_1', 'label': 'Külső tárhely (hdd_1)', 'schedulable': True} +keys: ['password_hash', 'filebrowser_admin_state', 'filebrowser_admin_decided_at', 'claimed', 'claim_code_hash', 'claim_code_generation', 'claim_code_issued_at', 'claim_consumed_generation', 'offbox_enlarge_notice_seeded', 'app_update_events_seeded', 'unhealthy_stop_event_seeded', 'hub_escrow_identity_present', 'hub_escrow_superseded_present', 'hub_escrow_superseded_at', 'hub_escrow_key_sha256', 'hub_escrow_key_checked_at', 'app_backup', 'offsite_data_at', 'backup_window_start', 'storage_paths', 'last_guest_boot_id', 'hub_verified', 'hub_verified_at', 'hub_last_check', 'applied_config_version', 'offbox'] +offbox: {"enabled": true, "host": "u629488-sub1.your-storagebox.de", "port": 23, "user": "u629488-sub1", "repo_path": "/home/felhom-repo", "schedule": "daily", "quota_gb": 50, "transport": "rclone-pinned", "last_run": "2026-10-15T02:15:10Z", "last_status": "ok", "last_success": "2026-10-15T02:15:10Z", "last_integrity_check": "2026-10-11T04:00:00Z", "last_integrity_ok": true, "last_integrity_depth": "100%", "proved_snapshots": {"opengist": "0ef3edb5"}, "last_proof_run": "2026-10-15T03:30:00Z", "last_proof_stack": "opengist", "last_proof_snapshot": "0ef3edb5", "last_proof_result": "pass", "last_duration": "7s", "repo_size_human": "181.6 KB", "repo_size_bytes": 185936, "snapshot_count": 15, "stats_know +app_backup: {"opengist": {"enabled": false, "cross_drive": {"enabled": true, "method": "rsync", "destination_path": "/mnt/felhom-drives/hdd_1", "schedule": "daily", "last_run": "2026-10-15T01:30:00Z", "last_status": "ok", "last_success": "2026-10-15T01:30:00Z", "success_tracked": true, "unit_package_date": "2026-10-15T00:30:00Z", "last_duration": "0s", "last_size_human": "202.8 KB"}, "offbox": true}} +offsite_data_at: {'opengist': {'pushed_at': '2026-10-15T02:15:05Z', 'data_at': '2026-10-15T02:15:00Z'}} diff --git a/documentation/audits/n100-lived-in-2026-10-15/B/06-deploy-fields.txt b/documentation/audits/n100-lived-in-2026-10-15/B/06-deploy-fields.txt new file mode 100644 index 00000000..63ec1c08 --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/B/06-deploy-fields.txt @@ -0,0 +1,10 @@ +qbittorrent 200 [('DOMAIN', 'domain', True, None), ('SUBDOMAIN', 'subdomain', True, None), ('HDD_PATH', 'path', True, None), ('QBT_ADMIN_PASSWORD', 'password', True, None)] + deployed: False +prowlarr 200 [('DOMAIN', 'domain', True, None), ('SUBDOMAIN', 'subdomain', True, None)] + deployed: False +sonarr 200 [('DOMAIN', 'domain', True, None), ('SUBDOMAIN', 'subdomain', True, None), ('HDD_PATH', 'path', True, None)] + deployed: False +radarr 200 [('DOMAIN', 'domain', True, None), ('SUBDOMAIN', 'subdomain', True, None), ('HDD_PATH', 'path', True, None)] + deployed: False +plex 200 [('DOMAIN', 'domain', True, None), ('SUBDOMAIN', 'subdomain', True, None), ('PLEX_CLAIM', 'text', True, True), ('HDD_PATH', 'path', True, None)] + deployed: False diff --git a/documentation/audits/n100-lived-in-2026-10-15/D/01-install.txt b/documentation/audits/n100-lived-in-2026-10-15/D/01-install.txt new file mode 100644 index 00000000..25d59320 --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/D/01-install.txt @@ -0,0 +1,18 @@ +16:36:53 qbittorrent: deploy (fields sent: ['DOMAIN', 'QBT_ADMIN_PASSWORD', 'SUBDOMAIN']) -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"} + qbittorrent: state=running after 13s; pinned={'qbittorrent': 'qbittorrentofficial/qbittorrent-nox:5.2.4-1'}; HDD_PATH: /mnt/felhom-drives/hdd_1 +16:37:06 prowlarr: deploy (fields sent: ['DOMAIN', 'SUBDOMAIN']) -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"} + prowlarr: state=running after 28s; pinned={'prowlarr': 'lscr.io/linuxserver/prowlarr:version-2.6.5.5623'}; +16:37:34 sonarr: deploy (fields sent: ['DOMAIN', 'SUBDOMAIN']) -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"} + sonarr: state=running after 33s; pinned={'sonarr': 'lscr.io/linuxserver/sonarr:4.0.20'}; HDD_PATH: /mnt/felhom-drives/hdd_1 +16:38:07 radarr: deploy (fields sent: ['DOMAIN', 'SUBDOMAIN']) -> 202 {"ok": true, "message": "Telepítés elindítva – az állapot a kártyán követhető"} + radarr: state=running after 28s; pinned={'radarr': 'lscr.io/linuxserver/radarr:6.4.4'}; HDD_PATH: /mnt/felhom-drives/hdd_1 +cloudflared Up 41 hours (healthy) +felhom-controller Up 2 hours (healthy) +filebrowser Up 21 hours (healthy) +opengist Up 12 hours (healthy) +prowlarr Up About a minute (healthy) +qbittorrent Up About a minute (healthy) +radarr Up 15 seconds (healthy) +sonarr Up 56 seconds (healthy) +traefik Up 12 hours + diff --git a/documentation/audits/n100-lived-in-2026-10-15/D/02-stranger.txt b/documentation/audits/n100-lived-in-2026-10-15/D/02-stranger.txt new file mode 100644 index 00000000..9c20af6c --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/D/02-stranger.txt @@ -0,0 +1,23 @@ +14:38:45 qbittorrent / -> HTTP/2 404 +14:38:46 qbittorrent /api/v3/system/status -> HTTP/2 404 +14:38:46 qbittorrent /api/v1/system/status -> HTTP/2 404 +14:38:46 qbittorrent /initialize.json -> HTTP/2 404 +14:38:46 qbittorrent /api/v2/app/version -> HTTP/2 404 +14:38:46 prowlarr / -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fprowlarr.demo-felhom.eu%2F +14:38:47 prowlarr /api/v3/system/status -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fprowlarr.demo-felhom.eu%2Fapi%2Fv3%2Fsystem%2Fstatus +14:38:47 prowlarr /api/v1/system/status -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fprowlarr.demo-felhom.eu%2Fapi%2Fv1%2Fsystem%2Fstatus +14:38:47 prowlarr /initialize.json -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fprowlarr.demo-felhom.eu%2Finitialize.json +14:38:47 prowlarr /api/v2/app/version -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fprowlarr.demo-felhom.eu%2Fapi%2Fv2%2Fapp%2Fversion +14:38:48 sonarr / -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fsonarr.demo-felhom.eu%2F +14:38:48 sonarr /api/v3/system/status -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fsonarr.demo-felhom.eu%2Fapi%2Fv3%2Fsystem%2Fstatus +14:38:48 sonarr /api/v1/system/status -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fsonarr.demo-felhom.eu%2Fapi%2Fv1%2Fsystem%2Fstatus +14:38:48 sonarr /initialize.json -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fsonarr.demo-felhom.eu%2Finitialize.json +14:38:49 sonarr /api/v2/app/version -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fsonarr.demo-felhom.eu%2Fapi%2Fv2%2Fapp%2Fversion +14:38:49 radarr / -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fradarr.demo-felhom.eu%2F +14:38:49 radarr /api/v3/system/status -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fradarr.demo-felhom.eu%2Fapi%2Fv3%2Fsystem%2Fstatus +14:38:49 radarr /api/v1/system/status -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fradarr.demo-felhom.eu%2Fapi%2Fv1%2Fsystem%2Fstatus +14:38:50 radarr /initialize.json -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fradarr.demo-felhom.eu%2Finitialize.json +14:38:50 radarr /api/v2/app/version -> HTTP/2 302 location: https://felhom.demo-felhom.eu/__gate/start?rd=https%3A%2F%2Fradarr.demo-felhom.eu%2Fapi%2Fv2%2Fapp%2Fversion +(the qbittorrent lines above used the wrong name; its address is torrent.demo-felhom.eu) +14:39:24 torrent / -> HTTP/2 200 body: HTTP/2 403 body: Forbidden diff --git a/documentation/audits/n100-lived-in-2026-10-15/D/03-wiring.txt b/documentation/audits/n100-lived-in-2026-10-15/D/03-wiring.txt new file mode 100644 index 00000000..57941f2c --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/D/03-wiring.txt @@ -0,0 +1,27 @@ +== the controller's wiring runs: +2026/10/15 14:36:47 [INFO] [stacks] media_library qbittorrent: per-drive binds 0 []; drives not bound: [] +2026/10/15 14:37:28 [INFO] [stacks] media_library sonarr: per-drive binds 0 []; drives not bound: [] +2026/10/15 14:38:01 [INFO] [stacks] media_library radarr: per-drive binds 0 []; drives not bound: [] +2026/10/15 14:39:15 [INFO] [media] wiring the drives into the media apps (a media app came up: radarr) +2026/10/15 14:39:18 [INFO] [mediawire] 1 drive(s) wired in 3.504s: folders=done(0) qbittorrent=done(3) sonarr=done(4) radarr=done(4) prowlarr=done(2) + +== per-drive binds (docker's own view): + qbittorrent: /var/lib/docker/volumes/qbittorrent_qbittorrent_config/_data -> /config; /mnt/felhom-drives/hdd_1/userdata/media/torrents -> /downloads; /mnt/felhom-drives/hdd_1/userdata/media/torrents -> /mnt/felhom-drives/hdd_1/userdata/media/torrents; + sonarr: /var/lib/docker/volumes/sonarr_sonarr_config/_data -> /config; /mnt/felhom-drives/hdd_1/userdata/media -> /mnt/felhom-drives/hdd_1/userdata/media; + radarr: /var/lib/docker/volumes/radarr_radarr_config/_data -> /config; /mnt/felhom-drives/hdd_1/userdata/media -> /mnt/felhom-drives/hdd_1/userdata/media; + prowlarr: /var/lib/docker/volumes/prowlarr_prowlarr_config/_data -> /config; +== sonarr: root folders ['/mnt/felhom-drives/hdd_1/userdata/media/tv'] + tags ['hdd-1'] + download clients [('qBittorrent hdd-1', 'QBittorrent', ['qbittorrent', 'tv-hdd-1'], [1])] +== radarr: root folders ['/mnt/felhom-drives/hdd_1/userdata/media/movies'] + tags ['hdd-1'] + download clients [('qBittorrent hdd-1', 'QBittorrent', ['qbittorrent', 'movies-hdd-1'], [1])] +== prowlarr: apps [('Radarr', 'fullSync', 'http://radarr:7878'), ('Sonarr', 'fullSync', 'http://sonarr:8989')] +== qbittorrent categories: Forbidden +== qbittorrent (its own API, after its login): +{"movies-hdd-1":{"download_path":"/mnt/felhom-drives/hdd_1/userdata/media/torrents/.temp","inactive_seeding_time_limit":-2,"name":"movies-hdd-1","ratio_limit":-2,"savePath":"/mnt/felhom-drives/hdd_1/userdata/media/torrents/movies","seeding_time_limit":-2,"share_limit_action":"Default"},"tv-hdd-1":{"download_path":"/mnt/felhom-drives/hdd_1/userdata/media/torrents/.temp","inactive_seeding_time_limit":-2,"name":"tv-hdd-1","ratio_limit":-2,"savePath":"/mnt/felhom-drives/hdd_1/userdata/media/torrents/tv","seeding_time_limit":-2,"share_limit_action":"Default"}} +"auto_tmm_enabled":true +"save_path":"/mnt/felhom-drives/hdd_1/userdata/media/torrents" +"save_path_changed_tmm_enabled":false +"temp_path":"/mnt/felhom-drives/hdd_1/userdata/media/torrents/.temp" +"temp_path_enabled":true diff --git a/documentation/audits/n100-lived-in-2026-10-15/D/04-app-backup-settings.txt b/documentation/audits/n100-lived-in-2026-10-15/D/04-app-backup-settings.txt new file mode 100644 index 00000000..c46ed36c --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/D/04-app-backup-settings.txt @@ -0,0 +1,35 @@ +{ + "opengist": { + "enabled": false, + "cross_drive": { + "enabled": true, + "method": "rsync", + "destination_path": "/mnt/felhom-drives/hdd_1", + "schedule": "daily", + "last_run": "2026-10-15T01:30:00Z", + "last_status": "ok", + "last_success": "2026-10-15T01:30:00Z", + "success_tracked": true, + "unit_package_date": "2026-10-15T00:30:00Z", + "last_duration": "0s", + "last_size_human": "202.8 KB" + }, + "offbox": true + }, + "prowlarr": { + "enabled": false, + "offbox": true + }, + "qbittorrent": { + "enabled": false, + "offbox": true + }, + "radarr": { + "enabled": false, + "offbox": true + }, + "sonarr": { + "enabled": false, + "offbox": true + } +} diff --git a/documentation/audits/n100-lived-in-2026-10-15/D/05-capture-sets.txt b/documentation/audits/n100-lived-in-2026-10-15/D/05-capture-sets.txt new file mode 100644 index 00000000..e81d22fe --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/D/05-capture-sets.txt @@ -0,0 +1,23 @@ +== qbittorrent: 1 classifiable bind(s), classified=true + bind userdata:media/torrents class=optional origin=explicit + tier offsite takes [] + tier secondary takes [/mnt/felhom-drives/hdd_1/felhom-data/userdata/media/torrents (optional)] +== sonarr: 1 classifiable bind(s), classified=true + bind userdata:media class=excluded origin=explicit + tier offsite takes [] + tier secondary takes [] +== radarr: 1 classifiable bind(s), classified=true + bind userdata:media class=excluded origin=explicit + tier offsite takes [] + tier secondary takes [] +== prowlarr: 0 classifiable bind(s), classified=true + tier offsite takes [] + tier secondary takes [] +--- PASS: TestN100CaptureSetTmp (0.00s) +2026-10-15 04:59:17 INFO: including mount point mp0 ('/var/lib/felhom') in backup +2026-10-15 04:59:17 INFO: excluding bind mount point mp8 ('/mnt/felhom-drives') from backup (not a volume) +2026-10-15 04:59:17 INFO: excluding bind mount point mp9 ('/etc/felhom-bootstrap') from backup (not a volume) +mp0: local-lvm:vm-9201-disk-3,mp=/var/lib/felhom,backup=1,size=250G +mp8: /mnt/felhom-drives,mp=/mnt/felhom-drives +mp9: /var/lib/felhom-agent/guests/9201/bootstrap,mp=/etc/felhom-bootstrap,ro=1 +Tier 2 with ONE data drive (backup/tier2.go selectTier2TargetFrom): no other drive -> the internal SSD, STATE-ONLY set (never the optional media/torrents), and only if the SSD has headroom; so no film or download reaches tier 2 on the N100 today. diff --git a/documentation/audits/n100-lived-in-2026-10-15/F/01-before.txt b/documentation/audits/n100-lived-in-2026-10-15/F/01-before.txt new file mode 100644 index 00000000..d5c2e52f --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/F/01-before.txt @@ -0,0 +1,9 @@ +{'path': '/mnt/felhom-drives/hdd_1', 'label': 'NVME 1TB', 'is_default': True, 'schedulable': True} +{'path': '/mnt/sizing-bench', 'label': 'Tárhely (sizing-bench)', 'schedulable': True} +total 16 +drwxr-xr-x 4 root root 4096 Oct 15 01:30 . +drwxr-xr-x 5 root root 4096 Oct 14 20:43 .. +drwxr-xr-x 3 root root 4096 Oct 15 01:30 backups +drwxrwsr-x 6 root 1000 4096 Oct 14 20:43 userdata +2026/10/15 14:41:46 info.go:13: [DEBUG] [system] GetDiskUsage: path="/mnt/sizing-bench" total=31.2 GB used=1.3 GB avail=28.3GB (4.3%) +2026/10/15 14:42:21 info.go:13: [DEBUG] [system] GetDiskUsage: path="/mnt/sizing-bench" total=31.2 GB used=1.3 GB avail=28.3GB (4.3%) diff --git a/documentation/audits/n100-lived-in-2026-10-15/F/02-remove.txt b/documentation/audits/n100-lived-in-2026-10-15/F/02-remove.txt new file mode 100644 index 00000000..387469db --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/F/02-remove.txt @@ -0,0 +1,9 @@ +16:44:24 POST /settings/storage/remove storage_path=/mnt/sizing-bench -> HTTP/2 302 | location: /storage?storage_msg=success&storage_detail=Adatt%C3%A1rol%C3%B3+elt%C3%A1vol%C3%ADtva%3A+%2Fmnt%2Fsizing-bench +2 +2026/10/15 14:44:24 settings.go:1632: [INFO] [settings] Removed storage path: /mnt/sizing-bench +2026/10/15 14:44:24 handlers.go:3527: [INFO] [web] Storage path removed: /mnt/sizing-bench +2026/10/15 14:44:25 handlers.go:3805: [INFO] [web] FileBrowser mounts synced (recreated) — 1 storage path(s), config updated + +/app_backup/calibre-web/cross_drive/destination_path = /mnt/sizing-bench +/app_backup/paperless-ngx/cross_drive/destination_path = /mnt/sizing-bench +storage paths now: ['/mnt/felhom-drives/hdd_1'] diff --git a/documentation/audits/n100-lived-in-2026-10-15/F/03-hub-after.txt b/documentation/audits/n100-lived-in-2026-10-15/F/03-hub-after.txt new file mode 100644 index 00000000..d4446771 --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/F/03-hub-after.txt @@ -0,0 +1,3 @@ +14:56:26 atus-dot-nominal"> Demo HP OK 1 1 just now 28% 26% 27%/22% 17/17 – + +[exited with code 0] diff --git a/documentation/audits/n100-lived-in-2026-10-15/README.md b/documentation/audits/n100-lived-in-2026-10-15/README.md new file mode 100644 index 00000000..ef9ca6c1 --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/README.md @@ -0,0 +1,19 @@ +# The N100 becomes the lived-in box (2026-10-15) + +Rule: `architecture/09-update-architecture.md` §3 decision 254. Diary: `documentation/lived-in/n100.md`. +Read first: `runbooks/target-selection.md`, `operations/nodes.md`, `07-backup-architecture.md`, `15-media-library.md`. + +| folder | what | +|---|---| +| `B/01-state.txt` | the host and guest: versions, memory, disks, `/dev/dri` (host yes, guest no) | +| `B/02-drive.txt`, `B/03-drive-detail.txt` | the USB drive (ADATA HD710 PRO / TOSHIBA MQ04ABF100 `65NOP3HDT`) and its 6.2 GB: vzdumps, app backups, leftovers — no family files; PVE storage `felhom-backup` = the whole-guest backup target | +| `B/04-sizing.txt` | the five media apps by the estimator's rule: ~2 GB needed of 12 GB | +| `B/05-dashboard-view.txt` | the dashboard's own view: one enrolled drive, `backup_target: true`, off-site on (182 KB) | +| `B/06-deploy-fields.txt` | the install fields per app | +| `D/01-install.txt` | qBittorrent, Prowlarr, Sonarr, Radarr through the deploy call — no drive sent; the box chose `hdd_1` | +| `D/02-stranger.txt` | a stranger from the internet: the gate on Prowlarr/Sonarr/Radarr; qBittorrent's own login page and 403 API | +| `D/03-wiring.txt` | what the box wired, from the apps' own APIs | +| `D/04-app-backup-settings.txt`, `D/05-capture-sets.txt` | **Part C:** the controller's classifier over the deployed files — off-site takes no media; tier 2 only qBittorrent's downloads and only if switched on (not on; with one drive state-only to the SSD); vzdump excludes the drives | +| `F/` | demo-hp: `/mnt/sizing-bench` removed through the dashboard; the hub's next report | +| `SHEET.md` | the operator's sheet | +| `tools/` | `n100.py` (the box-walk library pointed at the N100), `install_media.py`, `read_wiring.py` | diff --git a/documentation/audits/n100-lived-in-2026-10-15/SHEET.md b/documentation/audits/n100-lived-in-2026-10-15/SHEET.md new file mode 100644 index 00000000..0b1d6e1e --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/SHEET.md @@ -0,0 +1,50 @@ +# Your sheet — the N100 becomes the lived-in box (about 30 minutes) + +Do these when it suits you. Use one browser. Sign in to the dashboard first: then the apps let you through. + +## 1. The addresses + +- Dashboard: **https://felhom.demo-felhom.eu** +- qBittorrent: **https://torrent.demo-felhom.eu** +- Prowlarr: **https://prowlarr.demo-felhom.eu** +- Sonarr: **https://sonarr.demo-felhom.eu** +- Radarr: **https://radarr.demo-felhom.eu** +- Plex (after you install it): **https://plex.demo-felhom.eu** + +## 2. Plex + +1. Dashboard → Alkalmazások → Plex → install. It asks no code. +2. Wait until Plex runs. Open Plex's page in the dashboard. +3. Open **plex.tv/claim**. Copy the code. Paste it on Plex's page within 4 minutes. +4. The box opens Plex by itself when Plex is claimed. If it asks, press „Kész, beállítottam". +5. Wait 2 minutes. Open Plex. **If Plex shows no library,** press „Bekötés most" on the dashboard (the card about the + drives and the apps), and tell me that you had to. (I expect this; it is R-1065.) + +## 3. Prowlarr, Sonarr, Radarr + +1. Open each address. The app asks you to make its admin login. Make one for each. +2. On each app's page in the dashboard, press „Kész, beállítottam". Until you do, only you can reach the app. +3. Add your download sources **in Prowlarr only.** They reach Sonarr and Radarr by themselves (the box linked them). + +## 4. One show, one film + +1. Sonarr: add one show. Radarr: add one film. +2. When they finish, open Plex. Check that both are there. + +## 5. Passwords + +- qBittorrent: user `admin`. The password is on qBittorrent's page in the dashboard (the password row, show button). + The same value is in a private file on DooPlex: `~/.felhom-demofelhom/qbittorrent-admin`. +- Prowlarr, Sonarr, Radarr, Plex: the logins you make. None is written here. + +## 6. qBittorrent — the legal line + +„Csak olyan tartalmat tölts le, amihez jogod van. A letöltés a háztartásod internetcíméről történik, és a torrent +természetéből adódóan a többi résztvevő látja a címedet." + +## 7. Anything odd + +Tell any CC session. It goes into the diary (`documentation/lived-in/n100.md`) and the register. + +When you are done, say „done". I then read back: Plex claimed, its libraries, the show and the film imported as +hardlinks, and visible in Plex. diff --git a/documentation/audits/n100-lived-in-2026-10-15/tools/install_media.py b/documentation/audits/n100-lived-in-2026-10-15/tools/install_media.py new file mode 100644 index 00000000..93ee613c --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/tools/install_media.py @@ -0,0 +1,26 @@ +"""install_media.py — Part D1: qBittorrent, Prowlarr, Sonarr, Radarr through the dashboard's deploy call, with the fields +the wizard sends (no HDD_PATH for a media app — the box picks its drive). Admins and indexers are the operator's.""" +import json, os, time, n100 +w = n100.w +w.login() +for app in ("qbittorrent", "prowlarr", "sonarr", "radarr"): + if w.stack(app).get("deployed"): + print(f"{app}: already installed — skipped"); continue + c, d = w.ctl("GET", f"/api/stacks/{app}/deploy-fields") + fields = (((d.get("data") or {}).get("metadata") or {}).get("deploy_fields")) or [] + sub = next((f.get("default") for f in fields if f.get("env_var") == "SUBDOMAIN"), None) or app + values = {"DOMAIN": w.DOMAIN, "SUBDOMAIN": sub} + if any(f.get("env_var") == "QBT_ADMIN_PASSWORD" for f in fields): + values["QBT_ADMIN_PASSWORD"] = open(os.path.expanduser("~/.felhom-demofelhom/qbittorrent-admin")).read().strip() + t0 = time.time() + code, d = w.ctl("POST", f"/api/stacks/{app}/deploy", {"values": values}) + print(f"{time.strftime('%H:%M:%S')} {app}: deploy (fields sent: {sorted(values)}) -> {code} {json.dumps(d, ensure_ascii=False)[:120]}") + for _ in range(120): + time.sleep(5) + st = w.stack(app) + if st.get("deployed") and (st.get("app_config") or {}).get("pinned_images") and st.get("state") in ("running", "unhealthy"): + break + st = w.stack(app) + hdd = w.guest(f"grep -E '^\\s*HDD_PATH:' /opt/docker/stacks/{app}/app.yaml | head -1").strip() + print(f" {app}: state={st.get('state')} after {time.time()-t0:.0f}s; pinned={(st.get('app_config') or {}).get('pinned_images')}; {hdd}") +print(w.guest("docker ps --format '{{.Names}} {{.Status}}' | sort")) diff --git a/documentation/audits/n100-lived-in-2026-10-15/tools/n100.py b/documentation/audits/n100-lived-in-2026-10-15/tools/n100.py new file mode 100644 index 00000000..889ac279 --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/tools/n100.py @@ -0,0 +1,12 @@ +"""n100.py — the box-walk library pointed at the N100 (demo-felhom guest 9201). Import this, not box_walk, for the N100. +box_walk.py has no demo-felhom row; this overrides the target AFTER import (no catalog change). Every call is the +dashboard's own endpoint, as box_walk documents.""" +import os, sys +os.environ.setdefault("TARGET", "9201") +os.environ.setdefault("SC", os.path.expanduser("~/.felhom-demofelhom")) +sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") +import box_walk as w # noqa: E402 +w._T = {"hp": "demo-felhom", "ssh": "felhom-pve", "jump": None, "guest": "9201", + "base": "https://192.168.0.149", "domain": "demo-felhom.eu"} +w.GUEST, w.BASE, w.DOMAIN, w.HP = "9201", "https://192.168.0.149", "demo-felhom.eu", "demo-felhom" +w.HOSTHDR = "Host: felhom.demo-felhom.eu" diff --git a/documentation/audits/n100-lived-in-2026-10-15/tools/read_wiring.py b/documentation/audits/n100-lived-in-2026-10-15/tools/read_wiring.py new file mode 100644 index 00000000..87505ef1 --- /dev/null +++ b/documentation/audits/n100-lived-in-2026-10-15/tools/read_wiring.py @@ -0,0 +1,23 @@ +"""read_wiring.py — Part D2: what the box wired by itself, read from each app's OWN API inside its container (keys read +from the app's config at use, never printed) and from the controller's log. Read only.""" +import json, shlex, n100 +w = n100.w +def arr(app, port, path, ver="v3"): + script = ("K=$(sed -n 's:.*\\(.*\\).*:\\1:p' /config/config.xml); " + f"curl -s -H \"X-Api-Key: $K\" http://127.0.0.1:{port}/api/{ver}{path}") + out = w.guest(f"docker exec {app} sh -c {shlex.quote(script)}") + try: return json.loads(out) + except Exception: return out[:200] +print("== the controller's wiring runs:") +print(w.guest("docker logs --since 10m felhom-controller 2>&1 | grep -E '\\[media\\] wiring|\\[mediawire\\]|media_library .*binds' | cut -c1-330")) +print("== per-drive binds (docker's own view):") +for a in ("qbittorrent", "sonarr", "radarr", "prowlarr"): + print(f" {a}:", w.guest(f"docker inspect {a} -f '{{{{range .Mounts}}}}{{{{.Source}}}} -> {{{{.Destination}}}}; {{{{end}}}}'").strip()) +for a, port in (("sonarr", 8989), ("radarr", 7878)): + rf = arr(a, port, "/rootfolder"); tags = arr(a, port, "/tag"); dc = arr(a, port, "/downloadclient") + print(f"== {a}: root folders {[r.get('path') for r in rf] if isinstance(rf, list) else rf}") + print(f" tags {[t.get('label') for t in tags] if isinstance(tags, list) else tags}") + print(f" download clients {[(c.get('name'), c.get('implementation'), [f.get('value') for f in c.get('fields', []) if f.get('name') in ('host', 'tvCategory', 'movieCategory')], c.get('tags')) for c in dc] if isinstance(dc, list) else dc}") +apps = arr("prowlarr", 9696, "/applications", "v1") +print(f"== prowlarr: apps {[(a.get('name'), a.get('syncLevel'), next((f.get('value') for f in a.get('fields', []) if f.get('name') == 'baseUrl'), None)) for a in apps] if isinstance(apps, list) else apps}") +print("== qbittorrent categories:", w.guest("docker exec qbittorrent sh -c 'curl -s http://127.0.0.1:8080/api/v2/torrents/categories'").strip()[:600]) diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 3ce552b0..9c9c3e24 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -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 — 13 rows (P2 1, P3 7, P4 5) +## Apps & catalog — 14 rows (P2 1, P3 8, P4 5) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -136,6 +136,7 @@ stopping line that lies. | **R-1028** | Apps & catalog | P3 | **Plex's claim code can run out during the install, and the box cannot recover it.** plex.tv's code lives 4 minutes from the moment the household copies it; the clock covers the wizard, the image pull (`plexinc/pms-docker` is 351 MB, measured) and the first start. Plex reads `PLEX_CLAIM` only on its first run, and the field is `locked_after_deploy` — so a late code leaves an unclaimed server and the only way back is remove + install again. Reported by the operator during the 2026-10-14 test; on Tester 1's line the pull+start took 8.5 s and the claim worked (`audits/media-all-drives-2026-10-14/``G-auth/G3-plex-claim.txt`), so it bites on a slow line or a slow hand. **K6, 2026-10-14 night (`audits/media-drive-events-2026-10-14/``K6-1-unclaimed-plex.txt`):** an unclaimed Plex starts with no code at all, and `POST /myplex/claim?token=` on the RUNNING server reaches plex.tv (a wrong code → 500 after plex.tv's 401). So the box can pull + start first and ask for the code after: no race. A real claim needs the household's code — the morning step on the throwaway `plex-k6` on Tester 1. Narrowed 2026-10-14 night. **PROVEN 2026-10-15 morning (`K6-2-claim-after-start.txt`):** the operator's real code, given to the RUNNING unclaimed `plex-k6` → 200, `claimed` 0→1, `PlexOnlineToken` written, and the open door closed (no token → 401). `ADVERTISE_IP` was unset and the claim did not need it; whether clients find the server without it was not measurable (the throwaway had no published port). **BUILT 2026-10-15 (v0.313.0):** `after_start` field — not asked at install; the Plex page asks for the code once Plex runs and claims the running server; the gate opens only when Plex says claimed. Proven up to the code on 9201 (G5). | **NARROWED** — the dashboard claim with a real code is the last step | a household Plex code | Claim through the Plex page with a real code (G5c); then close | CC | | **R-1029** | Apps & catalog | P3 | **Plex's folder picker shows the drives as identical „media” folders.** With one bind per drive at the same path (`/mnt/felhom-drives//userdata/media`), the leaf of every library folder is `media`; the drive's name sits two levels up, so in Plex's picker the household sees several folders all called „media” (reported by the operator during the 2026-10-14 test on Tester 1). It conflicts with `15` §2 reason 2 (the same path everywhere). | **READY** — filed 2026-10-14 | the layout decision (STATUS D9) | Decide in `15` §2: the players (`:ro`, never linked) may get a readable path per drive (e.g. `/media/`) without breaking the hardlink rule, which binds only the download/library apps | CC | | **R-1036** | Apps & catalog | P3 | **The apps page lags the catalog by two apps.** `app-catalog-felhom.eu/templates/` holds **64** apps; `/alkalmazasok` and `/en/apps` render **63** cards, one of which (`filebrowser`) is built in and not a catalog template — so **62 catalog apps are listed**. Missing: **`plant-it`** and **`wger`**, both in the catalog since 2026-10-06/08. Their website assets are ALREADY present (`plant-it-logo.svg` + 3 screenshots, `wger-logo.png` + 3 screenshots, renamed to the catalog slug by `a3507761`, 2026-10-08) — only the cards were never written, so this is not a new-asset job. Found 2026-10-14 while reconciling the app count for the „felhő tárhely” page, where four places said **60** and the rest said **62**. **Done in that session:** every stated count on the site set to **62** (`index.html`, `szolgaltatasok.html`, `en/services.html` were 60), which is what the apps page actually shows — so no visitor can count the cards and find the number wrong. **62 is still not the catalog's number.** **To close:** write the two cards on both apps pages (gate 15 keeps the two pages' app sets equal, so they move together), then raise every stated count 62 → 64 in the same commit. **The operator chose this split at the 2026-10-14 STOP** over doing it inside the SEO change. **2026-10-15 (R-1048):** Prowlarr's card added on both pages and every stated count 62 → 63 (the media group 19 → 20), so the site still states what it shows; the two owed cards would make it 65. **Note before writing them:** `wger` is `lifecycle: hidden` and `plant-it` `lifecycle: abandoned` — neither is offered for a new install, so a card would advertise an app the wizard refuses. | **OPEN** | — | write the `plant-it` + `wger` cards on both apps pages, then 62 → 64 everywhere, one commit | CC | +| **R-1065** | Apps & catalog | P3 | **After the Plex claim the box does not set Plex's libraries until the next wiring run.** The claim handler (`felhom-controller` `internal/web/media_events.go` `appPlexClaimHandler`) opens the gate when Plex says claimed and returns; the wiring runs only when a `media_library` app comes up, on a drive event, or on „Bekötés most". Plex's own install ran it while Plex was unclaimed (`plex=household(0)`), so a freshly claimed Plex has no libraries until one of those happens — the shape of R-1058. Read from code 2026-10-15; to be measured on the N100 when the operator claims Plex (the sheet asks for „Bekötés most" only if no library appears). | **OPEN** — filed 2026-10-15 | — | Run the wiring (RunMediaWiringSoon) after a successful claim; a test; measure on the N100's claim | 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 | @@ -187,10 +188,13 @@ 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 — 6 rows (P3 3, P4 3) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| +| **R-1062** | Storage & devices | P4 | **The N100's USB drive cannot be split in two without losing the box's whole-guest backup target.** The operator asked (2026-10-15 16:31) for two halves for multi-drive tests. Read first (`audits/n100-lived-in-2026-10-15/B/`): no family files — 2.7 GB nightly vzdumps of guest 9201 (`dump/`, PVE storage `felhom-backup` = the whole-guest backup target), 3.1 GB app backups + an off-site restore copy, 422 MB leftover app data of removed apps, empty media folders. The agent refuses a decommission of a backup target (`refuseIfBackupTarget`) and the N100 has no other drive to move that role to. Nothing changed. | **WAITING-ON-OPERATOR** — filed 2026-10-15 | operator decision | Keep one drive (CC's pick: the lived-in box keeps its local whole-guest backup), or give a word to drop that backup leg / add a second drive first | operator | +| **R-1063** | Storage & devices | P3 | **„Végleges leszerelés" leaves a half-state when the agent refuses: the dashboard marks the drive decommissioned first and never undoes it.** `handleStorageDecommission` → `finalizeDecommissionWith` calls `settings.SetDecommissioned` (and may move the default) BEFORE `agent.Decommission`; on the agent's 403 (protected role) or 409 (whole-guest backup target) it returns the error and keeps the mark — the dashboard would show a retired drive the agent kept serving. Read from code 2026-10-15 (`felhom-controller` `internal/web/storage_handlers.go`); NOT pressed on the lived-in N100 on purpose. | **OPEN** — filed 2026-10-15 | — | Measure on demo-hp (a backup-target drive), then: ask the agent first (or undo the mark and the default on a refusal); a test with a refusing agent seam, red first | CC | +| **R-1064** | Storage & devices | P4 | **Removing a storage path does not mention the cross-drive backup copies it holds.** demo-hp 2026-10-15: `/mnt/sizing-bench` removed through `/settings/storage/remove` (302, „Adattároló eltávolítva") while calibre-web's and paperless-ngx's `cross_drive.destination_path` pointed at it; the copies stay on disk, the records still name the removed path. The in-use check counts HDD_PATH only (`appsUsingPath`). | **OPEN** — filed 2026-10-15 | — | Read what tonight's tier-2 run (01:30) does with the two records; then: name the copies in the removal's confirmation (or count them as in use) | 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 | diff --git a/documentation/lived-in/n100.md b/documentation/lived-in/n100.md new file mode 100644 index 00000000..5a727d48 --- /dev/null +++ b/documentation/lived-in/n100.md @@ -0,0 +1,36 @@ +# The N100 — the lived-in box (diary) + +**What it is.** `demo-felhom` (N100 mini-PC, guest 9201, `felhom.demo-felhom.eu`). From 2026-10-15 it is used the way a +household uses a box, and it changes only through the product. **The rule:** `architecture/09-update-architecture.md` +§3 decision 254 — the dashboard, the hub, the normal release train and the OS update rings only; no deliberate failure +without the operator's word for that one act; reads and observation are free. Tiers: `runbooks/target-selection.md`. + +**What it runs (2026-10-15):** controller 0.314.0, agent 0.156.1. Apps: opengist (standing), qBittorrent, Prowlarr, +Sonarr, Radarr (installed 2026-10-15 through the dashboard). Plex: the operator installs it. Drives: the system SSD and the +USB drive `hdd_1` (ADATA HD710 PRO / TOSHIBA MQ04ABF100, 931.5 GB), which is also the whole-guest backup target. + +**How to add a line.** Anyone — the operator or a session — who notices something on the N100 adds a dated line below: +what happened, who noticed, and the register row it became. Prose here is not a record; the row is. + +## Diary + +- **2026-10-15 — the rule starts.** Written as decision 254. (CC, n100-lived-in task) +- **2026-10-15 — the USB drive stays one drive.** The operator asked for a split in two. Read first: it holds no family + files — 2.7 GB of the box's nightly whole-guest backups (`dump/`), 3.1 GB of app backups and an off-site restore copy, + 422 MB of leftover app data from removed apps. It is the whole-guest backup target, so the product refuses to retire it, + and the box has no other drive for that role. Nothing was changed. → R-1062 (the operator decides), R-1063 (the + dashboard's half-state on that refusal, read from the code). (CC) +- **2026-10-15 — media apps installed through the dashboard.** qBittorrent, Prowlarr, Sonarr, Radarr — each healthy in + under 35 s; the box chose `hdd_1` for their own config (the wizard asks no drive). The box wired them by itself: + Sonarr `…/media/tv` and Radarr `…/media/movies`, tag `hdd-1`, qBittorrent as their client with categories + `tv-hdd-1` / `movies-hdd-1` saving under `…/media/torrents/` (the same drive, so imports can be hardlinks); Prowlarr + linked to both. A stranger meets the gate on Prowlarr/Sonarr/Radarr and qBittorrent's own login. (CC) +- **2026-10-15 — films never go off-site, proven before any download.** The controller's own classifier over the + deployed apps: off-site takes no media path; the cross-drive tier would take only qBittorrent's downloads and is not + on for it (with one data drive it falls back to the SSD with state only); the whole-guest backup excludes the drives + (its log: „excluding bind mount point mp8"). (CC) +- **2026-10-15 — demo-hp, not the N100:** the leftover `/mnt/sizing-bench` was removed through the dashboard; it held two + apps' cross-drive copies and the dashboard said nothing about them → R-1064. The hub showed demo-hp OK at its next report (14:56 UTC). (CC) +- **2026-10-15 — read before the operator's Plex install:** a freshly claimed Plex gets no libraries until the next + wiring run (the claim does not start one) → R-1065; the sheet asks for „Bekötés most" only if no library appears. + (CC, from the code) diff --git a/documentation/operations/nodes.md b/documentation/operations/nodes.md index 3d125420..3dade653 100644 --- a/documentation/operations/nodes.md +++ b/documentation/operations/nodes.md @@ -13,6 +13,8 @@ | Firmware | AMI AN3PLUS-class | **AMI M42 v01.10 (11/11/2020)** | | PVE node name | `demo-felhom` | `felhom-host` | | Customer | `demo-felhom` | `demo-hp` | +| Role | **the lived-in box** (`09` §3 decision 254, 2026-10-15): changes only through the product; no deliberate failure without the operator's word; diary `documentation/lived-in/n100.md` | **Tier 0** — the drill and build host (`runbooks/target-selection.md`) | +| Drives (2026-10-15) | system SSD (AirDisk 512 GB); USB `hdd_1` = ADATA HD710 PRO enclosure, TOSHIBA MQ04ABF100 s/n `65NOP3HDT`, 931.5 GB ext4 — enrolled user-data drive AND the whole-guest backup target `felhom-backup` | — | | Agent / controller | *not recorded here* — see the note below | *not recorded here* | | Control plane | **island** `169.254.253.1:8443` on `vmbr9` (R-50, migrated 2026-07-25) | **island** `169.254.253.1:8443` on `vmbr9` (R-50, migrated 2026-07-25) | | SSH alias | `felhom-pve` | **`demo-hp`** | diff --git a/documentation/runbooks/target-selection.md b/documentation/runbooks/target-selection.md index 3e79383c..8e69da0b 100644 --- a/documentation/runbooks/target-selection.md +++ b/documentation/runbooks/target-selection.md @@ -71,7 +71,8 @@ in `documentation/backlog/OPEN-ITEMS.md`, never `--no-verify`. | Tier | Meaning | Machines | |---|---|---| -| **0 — disposable. Reach here first.** | Exists to be broken; reinstalling is a routine afternoon, not an incident. **A drill that needs a victim uses one of these.** | `demo-hp` (t740), `demo-felhom` (N100) | +| **0 — disposable. Reach here first.** | Exists to be broken; reinstalling is a routine afternoon, not an incident. **A drill that needs a victim uses one of these.** | `demo-hp` (t740) and its guests and VMs — alone since 2026-10-15 | +| **Lived-in — used, not broken** (`09` §3 decision 254, 2026-10-15) | A demo box a family-like household really uses. It changes only through the product's own paths (dashboard, hub, the normal release train, the OS update rings); **no deliberate failure without the operator's word for that one act**; reads and observation are free; what is found goes into `documentation/lived-in/n100.md` and the register. Not Tier 2 (no paying customer's data), but never „reach here first". | `demo-felhom` (N100) | | **1 — create and destroy freely** | Throwaway VMs, guests, scratch customers — **hosted on a Tier 0 machine** | drill VMs, scratch guests | | **2 — protected. Never a drill target.** | Losing it costs the recovery chain or a real relationship | **DooPlex**, **`ep0`** (operator ruling 2026-08-03) — and nothing else (Peti's cluster RETIRED 2026-09-25) | @@ -112,12 +113,14 @@ felhom-pve, and **moved off DooPlex**. This page exists because that ruling sat - **Forbidden:** do not destroy or unblock **`drill-r50` (VM 300)** — the only drift fixture (R-93). **Measured 2026-09-13 (R-461): `qm list` is EMPTY on both demo-hp and demo-felhom — the VM does not exist anywhere, so the fence currently protects nothing and R-93's premise is gone. The fence stays as written for the day someone rebuilds it; do not read its presence here as evidence the fixture exists.** (Access: DooPlex's own key works — `ssh demo-hp`; G1 break-glass is the fallback. `operations/nodes.md`, R-129.) -### `demo-felhom` — N100 · **Tier 0** +### `demo-felhom` — N100 · **Lived-in** (`09` §3 decision 254, since 2026-10-15; Tier 0 until then) -- **Freely:** create/destroy guests and scratch customers; reinstall the box. -- **Care:** it carries the **PBS-DR / offsite tier** — but so does demo-hp now, so this is **no longer - a reason to prefer one over the other**. Prefer demo-hp per the 2026-07-25 ruling, on the ruling's - own grounds (it is the designated drill host), not because its backup chain is safe to disturb. +- **Freely:** read anything — logs, `docker ps`, `df`, the mount table, the hub's view, the plug's meter. +- **Through the product only:** installs, removals, settings, drives (the dashboard's own calls); releases by the floor; + OS updates by the rings. No drill catalog, no hand edit in the guest, no state-changing `docker`, no reinstall. +- **Forbidden without the operator's word for that one act:** any deliberate failure — power cut, drive pull, drill, + restore test — and any destructive test. A failure that happens by itself is recorded in `documentation/lived-in/n100.md`. +- **Care:** it carries the **PBS-DR / offsite tier**, and its USB drive (`hdd_1`) is its whole-guest backup target. -**Both Tier 0 boxes — the shared backup-target fence is DOWNGRADED to a cost, 2026-08-02 (D-d).** It +**Both Tier 0 boxes (as of 2026-08-02; the N100 is lived-in since 2026-10-15, decision 254) — the shared backup-target fence is DOWNGRADED to a cost, 2026-08-02 (D-d).** It read *"do not re-point either backup target"*, because these are the only two **correctly configured** boxes and therefore the regression path new installer logic is measured against. D-d makes both boxes freely breakable and reinstallable, which loses that reference just as thoroughly — so the fence was