The N100 becomes the lived-in box (09 §3 decision 254): media apps installed and wired through the dashboard; films proven never off-site; the USB split NOT done (it is the whole-guest backup target)
gates / gates (push) Successful in 6m59s

- 09 §3 decision 254 (operator decisions 240/243 written down); target-selection: a "lived-in" tier, Tier 0 = demo-hp
  alone; nodes.md role + drive; 11-os-updates ring 0 wording; CLAUDE.md + docs.md: the diary.
- documentation/lived-in/n100.md (new diary); audits/n100-lived-in-2026-10-15/ (state, drive read, Part C capture sets,
  installs, wiring, stranger probes, demo-hp clean-up, the operator's SHEET.md).
- Register: R-1062 (the split, operator decides), R-1063 (decommission half-state on an agent refusal), R-1064 (path
  removal silent about cross-drive copies), R-1065 (a claimed Plex gets no libraries until a wiring run).
- demo-hp: /mnt/sizing-bench removed through the dashboard; the hub shows demo-hp OK (14:56 UTC).

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-15 16:59:15 +02:00
parent 3263bb26bc
commit 06e7cf8c3f
30 changed files with 614 additions and 12 deletions
+1
View File
@@ -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/` |
+1
View File
@@ -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) |
+7
View File
@@ -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
+92
View File
@@ -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.
+3
View File
@@ -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.
@@ -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.*
+3 -2
View File
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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'}}
@@ -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
@@ -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
@@ -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: <!DOCTYPE html><html lang="C"><head> <meta charset="UTF-8"> <meta nam
14:39:25 torrent /api/v2/app/version -> HTTP/2 403 body: Forbidden
@@ -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
@@ -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
}
}
@@ -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.
@@ -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%)
@@ -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']
@@ -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]
@@ -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` |
@@ -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.
@@ -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"))
@@ -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"
@@ -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:.*<ApiKey>\\(.*\\)</ApiKey>.*:\\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])
+6 -2
View File
@@ -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/<drive>/userdata/media`), the leaf of every library folder is `media`; the drive's name sits two levels up, so in Plex's picker the household sees several folders all called „media” (reported by the operator during the 2026-10-14 test on Tester 1). It conflicts with `15` §2 reason 2 (the same path everywhere). | **READY** — filed 2026-10-14 | the layout decision (STATUS D9) | Decide in `15` §2: the players (`:ro`, never linked) may get a readable path per drive (e.g. `/media/<drive label>`) without breaking the hardlink rule, which binds only the download/library apps | CC |
| **R-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 |
+36
View File
@@ -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)
+2
View File
@@ -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`** |
+10 -7
View File
@@ -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.
<!--
CORRECTED 2026-08-06. This line used to read "(demo-hp has none)" and was the runbook's stated reason
@@ -144,7 +147,7 @@ kind of belief under which a drill lands somewhere it should not.
-->
**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