Apps burndown (controller 0.321.0 released): R-1097, R-1102, R-1101, R-1095, R-1098 closed; R-1103 opened; build-hub.sh 1.25.0
gates / gates (push) Successful in 6m52s

build-hub.sh refuses an existing tag and an unanswered registry (test_build_hub_push_once.sh, red-proved).
Register 179 -> 175. R-1067 stays BLOCKED: Tester-2 reports 0.292.0. Design notes in 02 and 07; STATUS;
evidence audits/burndown-apps-2026-10-16/; REPORT-burndown-apps-2026-10-16.md.

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-16 17:27:57 +02:00
parent 11fa13ee38
commit 836d64efa3
35 changed files with 739 additions and 10 deletions
+82
View File
@@ -0,0 +1,82 @@
# REPORT — apps burndown: RomM saves, the false „shared password" line, FileBrowser, the Data tab, the install address, the build guards, the Pi key (2026-10-16)
Written in ASD-STE100 Simplified Technical English. Operator brief „apps burndown", daytime, operator present.
Design read first: `architecture/02-controller-module-map.md` „The app page" (now with the v0.321.0 notes),
`07-backup-architecture.md` (what an app backup holds — a fact added), `09-update-architecture.md` §0.
Evidence: `documentation/audits/burndown-apps-2026-10-16/`.
**The operator's word before the start:** no RomM saves need to be kept or copied on any box. So Part 1 has no one-time
copy, and no stop for the N100.
## 1. RomM saves (R-1097) — done
- **Measured first (demo-hp 9201):** RomM writes saves to `/romm/assets`; the image keeps `/romm` as an anonymous
volume; the compose mounted nothing there. Every RomM backup on the drive held only `romm_config`, `romm_db_data`,
`romm_redis_data` — no saves. In the code, the app backup takes only the named volumes the compose declares.
`P1/p1-measure.txt`.
- **Fix (catalog `326f846`):** a named volume `romm_assets:/romm/assets`, in the compose and both older step files. A
named volume is the catalog's normal place for app data (07 §6.2), every app backup takes it, and the restore brings it
back. New catalog gate `image-volumes` (a measured list of image data paths that must be mounted).
- **Survey:** no other running app on demo-hp keeps data in an anonymous volume (PrivateBin's are temp folders).
`P1/anon-volumes-demo-hp.txt`.
- **Proof, through the product's endpoints** (`tools/romm_saves.py`, `P1/romm-saves.txt`): sync → restart (the new
volume mounted) → a 32 KiB save file written into `/romm/assets` with `docker exec` → the by-day backup run (night
chain, 5 min 32 s; its update leg did nothing) wrote `romm_romm_assets.tar` → RomM removed, keeping backups (the volume
went) → restored from the backups page (77 s) → **the save back, byte-identical (sha256)**. The dummy file removed after.
- **Boxes:** the hub shows RomM only on demo-hp (the N100 had it once, on 2026-10-13). `P1/hub-romm-boxes.txt`.
## 2. Parts 2–7
| Part | Result |
|---|---|
| 2 — the false „shared password" line (R-1102, D10 = A) | **Done.** `default_creds` removed (hu + en) from the eight; copy freeze re-captured with the reason; a catalog test, red-proved. Live on demo-hp: the line is on none of their app pages or install pages (`live/d10-check.txt`). The user name „admin" stays in the first steps of grafana, gokapi, nzbget, transmission. |
| 3.1 — FileBrowser's stale password (R-1101) | **Done** (controller 0.321.0). The „change it" line is gone for FileBrowser; the page checks the stored password with FileBrowser first and, refused, says so and offers the new-password press. Only FileBrowser's own 401/403 counts. |
| 3.2 — FileBrowser's own banners | **Done.** `server.disableUpdateCheck` (`backend/common/settings/structs.go:51`, read at `backend/cmd/root.go:66`) and `frontend.disablePWAInstall` (`structs.go:294`, sent at `backend/http/static.go:300`) at the pinned tag, set in the rendered config. In the browser, logged in: no „update is available", no „Install app", `disablePWAInstall: true`, `updateAvailable: ""`. Control: the 2026-10-16 screenshot `logins-folders-2026-10-16/browser/cold-romm-2-after-login.png` shows the banner before. |
| 4 — the Data tab's backup part | **Done.** One section, rows, plain words; the value column in the UI font; the export eye is the text „Megjelenítés"/„Elrejtés" with `aria-label`. Changed keys: `tier2_config.a_2_mentes_egy_masik`, `.csak_db_konfiguracio`, `.jelenleg_csak_a_belso_ssd`, `.nincs_elerheto_off_drive_cel`, `.nincs_masik_adatmeghajto_automatikus_cel`, `.rendszerlemez.vedett`, `note.tier2.no_space_ssd`, `note.tier2.reason_no_second`. No eye icon exists in the dashboard's icon set, and adding one would change every page's fixture, so the button is the text the other reveal buttons use. |
| 5 — the install address (R-1095) | **Done** (302 to the settings tab, GET only). **The template clean-up is NOT done:** the installed-only branches of `app_fields.html` are pinned by the R-254/R-709 guard tests, which assert a reveal control on that path; removing them means retiring those tests — filed R-1103. |
| 6 — the build guards (R-1098) | **Done.** `build-hub.sh` 1.25.0 refuses an existing tag and an unanswered registry (6 cases, red-proved). The agent's `release-agent.sh` had the check but let an unreachable registry through; now only a clean 404 proceeds (5 cases, red-proved). Script changes only: **no hub image, no hub deploy, no stop needed.** |
| 7 — the Raspberry Pi key (R-1067) | **Not done, as the brief says:** Tester-2 still reports controller **0.292.0** (newest report 2026-10-04). Its name is in the row. `P7-pi-fleet.txt`. |
## 3. Browser check (headless Chrome through `https://felhom.enkisfelhom.hu`) — `browser/`
`tools/cdp_burndown.py`. Calibre-Web's Data tab: one backup section, rows nightly/second/target, no off-drive/PBS, value
font Plus Jakarta Sans, the export toggle with its label. FileBrowser's page: the reveal, no „change it" line.
`/apps/calibre-web/install` landed on `/apps/calibre-web/settings`. FileBrowser itself: no banners. No console error; no
page wider than the window.
## 4. Versions and commits
- **felhom-controller** `c02c9d8` → `7e791d9` → docs. `0.321.0-rc.1` (demo-hp), release **0.321.0**, MinAgent 0.158.1.
Tester 1, demo-hp and the N100 took it through per-box floors; nothing unhealthy, restarting, exited or created; the
three dashboards 200; https-only 35 names OK; each box's FileBrowser config carries both switches. **Global floor
0.321.0 / MinAgent 0.158.1**, the per-box floors cleared (`release/`).
- **app-catalog-felhom.eu** `5630979` → `326f846` (RomM volume, gate `image-volumes`, D10) → docs.
- **felhom-agent** `3835224` → `dd8b2ac` (script and test only; no release).
- **felhom.eu** `11fa13ee` → this commit (`build-hub.sh` 1.25.0, its test, the register, design notes, STATUS, evidence).
- **Fixtures replaced (13):** `app_data_deployed`, `app_data_export`, `app_data_files_media`, `app_data_not_hdd`,
`_not_hdd_absent`, `_not_hdd_protected`, `_not_hdd_same_disk`, `_notarget_disabled`, `_target_auto`, `_target_ssd`,
`app_settings_deployed`, `app_settings_operator_creds`, `app_settings_infra_filebrowser`. **New (1):**
`app_settings_infra_filebrowser_stale`. Every other fixture, the install page's included, byte-identical.
- **Red-proofs:** `redproof/` (controller 6 + 1 redo, catalog 2 gates + 1 test, hub build, agent).
## 5. Rows
**Before 179, after 175. Closed 5:** R-1097, R-1102, R-1101, R-1095, R-1098. **Opened 1:** R-1103. R-1067 stays
BLOCKED, now naming Tester-2.
**System poster:** no listed fact changed — none of the three was needed.
## 6. Observations
1. The install form keeps branches no page serves any more. FILED: R-1103
2. Reading the hub's database by copying its three files one by one gave a STALE floor (0.320.0, 30 s after 0.321.0 was set); one tar of the three files and the hub's own page both read 0.321.0. NOT-A-FINDING: an evidence technique, not a product defect; the memory recipe is corrected to one tar.
3. The by-day backup run (night chain) also runs the update leg and the off-site push. NOT-A-FINDING: it is the sanctioned trigger, documented as „the night chain, by day"; here the update leg did nothing (kimai held).
4. No live control exists for the „known, shared password" line any more: the eight apps were the only ones that showed it. NOT-A-FINDING: `known_login_test.go` is the mechanism control and passes.
## 7. Teardown (three layers)
- **Machine:** demo-hp 9201 — RomM removed and restored (it runs, healthy, with its new saves volume); the dummy save
file removed. FileBrowser recreated once on each box by the config change (the password survives: its database is in
its volume). Nothing else changed. The N100: only the normal release update.
- **Host:** nothing.
- **Hub:** the three per-box floors set and cleared; the global floor raised to 0.321.0 (the release).
+2 -1
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
- **RomM game saves are in the backup now** (2026-10-16, catalog): a save survived a backup, a removal and a restore on demo-hp, byte for byte. Eight apps no longer claim „a known, shared password" (your D10 = A). If someone changes the file manager's password in the file manager, the box notices and offers a new one (controller 0.321.0).
- **Every installed app says where its login is, and has a „Fájlok ↗" button to its folder** (controller 0.320.0, 2026-10-16). The file manager is shown as part of the box again, with its password; on a box that does not know that password, one press sets a new one (done on demo-hp and, at your yes, on the N100).
- **Every app but three has a user guide and an FAQ, in Hungarian and English** (catalog, 2026-10-15 night): the „Útmutató” tab shows it on boxes with controller 0.315.0 or newer. Every button name in a guide was checked in the app's own files at the version we ship; a guide that could not be checked stays hidden (plant-it, home-assistant), and Emby has none (R-1092).
- **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`.
@@ -49,9 +50,9 @@
| 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 | — |
| D10 | R-1102 | Eight apps tell the household „starts with a known, shared password" about a login the box generated or the household typed. Drop that line on the eight (the login panel now says where the login is)? | **Drop it** — one catalog commit | Keep it with a new „hint" form: a controller change | the eight apps keep the misleading sentence at install and on the app page | — |
## Waiting on you — do
- **Nézd meg a demo-hp-n a Calibre-Web „Adatok és mentés" fülét és a Fájlkezelőt** (`felhom.enkisfelhom.hu`, controller 0.321.0, minden dobozon kiadva). A mentés egy részben, egyszerű szavakkal; a Fájlkezelő nem kér frissítést és nem ajánl „Install app"-ot.
- **Look at `roms/roms/` on the N100's drive in the file manager (R-1099).** RomM now scans `roms/<platform>/`. A game uploaded through RomM before today sits one folder deeper: if there are platform folders in `roms/roms/`, move them up into `roms/` and press „Scan” in RomM. If not: those games show under a platform called „roms”.
- **Nézd meg a Calibre-Web oldalát a demo-hp-n** (`felhom.enkisfelhom.hu`, controller 0.319.0, minden dobozon kiadva). A fejléc, a fülsor és minden rész most külön panel (az „A” stílus). A belépési adatok egy panelben vannak, egymás mellett. A Calibre-Web jelszaváról a doboz nem tud, mert az alkalmazás előbb települt, mint hogy a sablonban jelszó lett volna — ezt most egy mondat mondja ki. Ha nem tetszik, írd meg, mit változtassak. Ha nem nézed meg: semmi nem romlik el.
- **Discard two unclaimed boxes and delete Tester4** (hub, browser only): the build B scratch VMs registered as unclaimed boxes — registration ids 40 (`b43afa10…`) and 41 (`9c78ea30…`); both VMs are destroyed. Tester4 (the throwaway customer) is switched off the household way. If not: two dead entries stay in the Hosts list and Tester4 stays in Customers.
@@ -542,6 +542,20 @@ database with no lock timeout), verifies new=200 and admin/admin refused, stores
start. Spike: `audits/logins-folders-2026-10-16/P1b`. FileBrowser still asks for its own login the first time; one
sign-in for both is R-1100.
**[DESIGN] A stored FileBrowser password is checked before it is offered (v0.321.0, R-1101).** In `generated`, the
page asks FileBrowser whether the stored value still logs in; only FileBrowser's own 401/403 counts as refused (a
FileBrowser that does not answer is not a refusal — an unproven refusal must not tell the household its password
changed). Refused → „A Fájlkezelőben megváltoztattad a jelszót, ezért a doboz már nem ismeri." and „Új jelszó
beállítása". The page no longer tells the household to change FileBrowser's password, and FileBrowser's own update
notice and „Install app" prompt are switched off in its config (the box updates it; it is opened from the dashboard).
**[DESIGN] The Data tab's backup part is ONE section (v0.321.0).** „Biztonsági mentés": an „Éjszakai mentés" row, the
2nd copy's state as rows, what the 2nd copy is, its form. Plain words — no „off-drive", no „PBS", no
„DB/konfiguráció" (pinned by `TestAppData_OneBackupSection`).
**[DESIGN] An installed app's install address goes to its settings tab (v0.321.0, R-1095)** — 302, GET only, like the
`/stacks/{name}/deploy` bridge.
**[DESIGN] „Fájlok ↗" — one of the app's folders, in FileBrowser.** The header button opens the folder with the library
role, else the first declared folder, else (a media app) the first present drive's `media` folder; built only with
`fileBrowserLink` (PathEscape). The data tab lists each drive's media folders the app uses (Sonarr `tv`, Radarr
@@ -553,6 +553,13 @@ a normal night: nothing. A box off for weeks: one catch-up when it returns; the
running all along. An upgrade from an older release: the ledger is seeded, the first missed night after it is the
first one made up.
**[FACT, 2026-10-16, R-1097] An image's own anonymous VOLUME is in no backup.** The app backup takes the named volumes the
compose DECLARES (`backup.ResolveDockerVolumeNames`) and the drive folders it binds; a path the image keeps in its
`VOLUME` that the compose does not mount lands in an anonymous volume and is taken by nothing. Measured with RomM's saves
(`/romm/assets`) on demo-hp; fixed with a named volume, and catalog gate `image-volumes` keeps a measured registry of such
paths (`audits/burndown-apps-2026-10-16/P1/`). A survey of every running container on demo-hp found no other data path
in an anonymous volume (PrivateBin's are temp folders).
### 6.2 Coverage per app class — and an unresolved count
**[FACT]** Of 53 catalog templates, **52 keep data in Docker named volumes**; exactly **13** carry a
@@ -0,0 +1,2 @@
romm rommapp/romm:5.3.1 ['/redis-data', '/romm']
privatebin privatebin/pdo:2.0.6 ['/run', '/tmp', '/var/lib/nginx/tmp']
@@ -0,0 +1,7 @@
customers reporting RomM telemetry, with their newest report (hub DB + WAL, read 2026-10-16):
demo-felhom rows 1 newest 2026-10-13 09:37:46
demo-hp rows 4450 newest 2026-10-16 16:32:25
customers reporting ANY app in the last 2 hours:
demo-felhom
demo-hp
tester-1
@@ -0,0 +1,18 @@
2026/10/16 13:47:23 main.go:814: [INFO] Self-update enabled (check every 6h, auto-update: false, auto-update time: 04:30)
2026/10/16 14:51:36 night_chain.go:108: [INFO] [night-chain] manual run started from 172.18.0.4:40328: [db-dump tier2 offsite update-leg]
2026/10/16 14:51:36 night_chain.go:84: [INFO] [night-chain] manual run of tonight's chain: dump → second drive → off-site → update leg
2026/10/16 14:51:36 night_chain.go:77: [INFO] [night-chain] db-dump: started
2026/10/16 14:53:42 night_chain.go:82: [INFO] [night-chain] db-dump: done in 2m6s
2026/10/16 14:53:42 night_chain.go:77: [INFO] [night-chain] tier2: started
2026/10/16 14:54:10 night_chain.go:82: [INFO] [night-chain] tier2: done in 28s
2026/10/16 14:54:10 night_chain.go:77: [INFO] [night-chain] offsite: started
2026/10/16 14:57:08 night_chain.go:82: [INFO] [night-chain] offsite: done in 2m58s
2026/10/16 14:57:08 night_chain.go:77: [INFO] [night-chain] update-leg: started
2026/10/16 14:57:08 unattended.go:270: [INFO] [update-leg] started (manual-chain): window 02:30, no step starts at or after 20:12
2026/10/16 14:57:08 unattended.go:316: [INFO] [update-leg] kimai: skipped — held
2026/10/16 14:57:08 unattended.go:248: [INFO] [update-leg] update leg (manual-chain): done=0 undone=0 held=0 failed=0 skipped=1 in 0s [skipped: kimai=held]
2026/10/16 14:57:08 night_chain.go:82: [INFO] [night-chain] update-leg: done in 0s
2026/10/16 14:57:08 night_chain.go:93: [INFO] [night-chain] finished in 5m32s
romm Up About a minute (healthy)
romm-redis Up About a minute (healthy)
romm-db Up About a minute (healthy)
@@ -0,0 +1,40 @@
== mounts of the running romm container
volume <ANONYMOUS 64-hex> -> /redis-data
volume <ANONYMOUS 64-hex> -> /romm
volume romm_romm_config -> /romm/config
bind -> /romm/resources
bind -> /romm/library/bios
bind -> /romm/library/roms
== compose volumes
167:volumes:
168- romm_config:
169- romm_db_data:
170- romm_redis_data:
171-
172-networks:
173- traefik-public:
18:# Cover art etc → ${HDD_PATH}/appdata/romm/resources (app-internal, NOT browsable)
29: image: rommapp/romm:5.3.1@sha256:0d66b4ea152a237c7f24b95e87459f46d3c596a7c0b35a82f26bc971487278b3
40: if [ ! -f /romm/config/config.yml ]; then
42: cat > /romm/config/config.yml << 'CONF'
80: # on demo-hp 9201 (R-1089, felhom.eu audits/romm-ghost-2026-10-16/A1). Now roms/<platform>/ IS
83: - ${USERDATA_PATH}/roms:/romm/library/roms
84: - ${USERDATA_PATH}/roms-bios:/romm/library/bios
85: - ${HDD_PATH}/appdata/romm/resources:/romm/resources
86: - romm_config:/romm/config
== what earlier app backups of romm hold
/mnt/felhom-drives/hdd_1/kept/romm/2026-10-16_113539/unit/volume-dumps
total 203084
drwxr-xr-x 2 root root 4096 Oct 4 05:35 .
drwxr-xr-x 5 root root 4096 Oct 4 11:18 ..
-rw-r--r-- 1 root root 2560 Oct 4 05:35 romm_romm_config.tar
-rw-r--r-- 1 root root 163786752 Oct 4 05:35 romm_romm_db_data.tar
-rw-r--r-- 1 root root 44148736 Oct 4 05:35 romm_romm_redis_data.tar
== anonymous volume contents
assets
config
library
resources
/var/lib/docker/volumes/567f3e5915421b542fc2e67d3195d03b32ef07ef79f2851c6198653cf29e00f6/_data/assets
28K /var/lib/docker/volumes/567f3e5915421b542fc2e67d3195d03b32ef07ef79f2851c6198653cf29e00f6/_data
@@ -0,0 +1,49 @@
== 1. sync the catalog
sync -> 200 {'ok': True, 'data': {'ok': True, 'updated': ['code-server', 'gokapi', 'grafana', 'kimai', 'nextcloud', 'nzbget', 'prowlarr', 'romm', 'transmission'], 'message': 'Sablonok frissítve — frissítve: code-
applied compose names romm_assets: 3
== 2. restart RomM (Újraindítás)
restart -> 200 {'ok': True, 'data': {'state': 'starting'}, 'message': 'Stack romm restart requested — state now: starting'}
state: running
mounts after restart:
volume <ANONYMOUS> -> /redis-data
volume <ANONYMOUS> -> /romm
volume romm_romm_assets -> /romm/assets
volume romm_romm_config -> /romm/config
bind -> /romm/resources
bind -> /romm/library/bios
bind -> /romm/library/roms
== 3. a save where RomM writes saves (dummy bytes, as the app's own user)
drwxr-xr-x 3 root root 4096 Oct 16 16:51 ..
-rw-r--r-- 1 root root 32768 Oct 16 16:51 r1097-proof.sav
4a55d00c3ee6bccd55649e48fd19d84703f755f84474ac0b6a41299b114ee6a5 /romm/assets/romm/users/1/saves/gba/r1097-proof/r1097-proof.sav
== 4. the by-day backup (night chain)
night-chain -> 202 {'data': {'legs': ['db-dump', 'tier2', 'offsite', 'update-leg']}, 'message': 'started', 'ok': True}
night chain last line: 2026/10/16 14:57:08 night_chain.go:93: [INFO] [night-chain] finished in 5m32s after 342s
romm volume dumps now: /mnt/felhom-drives/hdd_1/backups/primary/romm/volume-dumps/:
total 176584
drwxr-xr-x 2 root root 4096 Oct 16 14:56 .
drwxr-xr-x 5 root root 4096 Oct 16 14:56 ..
-rw-r--r-- 1 root root 37888 Oct 16 14:56 romm_romm_assets.tar
-rw-r--r-- 1 root root 2560 Oct 16 14:56 romm_romm_config.tar
-rw-r--r-- 1 root root 162300416 Oct 16 14:56 romm_romm_db_data.tar
-rw-r--r-- 1 root root 18463744 Oct 16 14:56 romm_romm_redis_data.tar
/mnt/felhom-drives/hdd_1/backups/primary/romm/volume-dumps/romm_romm_assets.tar
== 5. remove RomM, KEEPING its backups
remove -> 200 {'ok': True, 'data': {'removed': 'romm', 'volumes_removed': ['romm_romm_assets', 'romm_romm_config', 'romm_romm_db_data', 'romm_romm_redis_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': ['/mn
volumes left: romm_romm_redis_data.pre-aof-fix-20260924T135937Z
== 6. restore from the backups page
restore: {'ok': True, 'snapshot_id': 'helyi', 'http': 'HTTP/2 302', 'seconds': 77.0}
state: running
mounts after restore:
volume <ANONYMOUS> -> /redis-data
volume <ANONYMOUS> -> /romm
volume romm_romm_assets -> /romm/assets
volume romm_romm_config -> /romm/config
bind -> /romm/resources
bind -> /romm/library/bios
bind -> /romm/library/roms
drwxr-xr-x 3 root root 4096 Oct 16 16:58 ..
-rw-r--r-- 1 root root 32768 Oct 16 16:51 r1097-proof.sav
4a55d00c3ee6bccd55649e48fd19d84703f755f84474ac0b6a41299b114ee6a5 /romm/assets/romm/users/1/saves/gba/r1097-proof/r1097-proof.sav
== VERDICT: the save came back byte-identical: True
@@ -0,0 +1,6 @@
tables with customer: ['customer_notifications', 'customer_configs', 'customer_claims', 'customer_resets']
every customer's NEWEST report (hub DB + WAL):
demo-hp 0.320.0 2026-10-16 14:58:09
tester-1 0.320.0 2026-10-16 14:49:33
demo-felhom 0.320.0 2026-10-16 14:49:28
Tester-2 0.292.0 2026-10-04 18:06:24
Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

@@ -0,0 +1,60 @@
rm: cannot remove '/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/burndown-apps-2026-10-16/browser/.profile/Default': Directory not empty
after login url: /launcher
{
"/apps/calibre-web/data": {
"path": "/apps/calibre-web/data",
"backupSections": 1,
"rows": [
"nightly",
"second",
"target"
],
"offDrivePBS": false,
"valueFont": "\"Plus Jakarta Sans\"",
"exportToggle": [
"Megjelenítés",
"Jelszó mutatása"
],
"fbReveal": false,
"fbStale": false,
"changeItLine": false,
"scrollWidth": 1270,
"console": []
},
"/apps/filebrowser": {
"path": "/apps/filebrowser",
"backupSections": 0,
"rows": [],
"offDrivePBS": false,
"valueFont": null,
"exportToggle": null,
"fbReveal": true,
"fbStale": false,
"changeItLine": false,
"scrollWidth": 1280,
"console": []
},
"/apps/calibre-web/install": {
"path": "/apps/calibre-web/settings",
"backupSections": 0,
"rows": [],
"offDrivePBS": false,
"valueFont": null,
"exportToggle": null,
"fbReveal": false,
"fbStale": false,
"changeItLine": false,
"scrollWidth": 1270,
"console": []
},
"filebrowser": {
"path": "/files/Beolvas%C3%A1s",
"updateBanner": false,
"installApp": false,
"releaseBanners": 0,
"globals": {
"disablePWAInstall": true,
"updateAvailable": ""
}
}
}
@@ -0,0 +1,22 @@
control: bookstack (keeps default_creds):
302 known-login markers: 0
grafana 200 known-login=0 answer=
grafana/install 200 known-login=0 answer=
gokapi 200 known-login=0 answer=
gokapi/install 200 known-login=0 answer=
kimai 200 known-login=0 answer=data-login-answer="fields"
kimai/install 302 known-login=0 answer=
code-server 200 known-login=0 answer=
code-server/install 200 known-login=0 answer=
nzbget 200 known-login=0 answer=
nzbget/install 200 known-login=0 answer=
nextcloud 200 known-login=0 answer=
nextcloud/install 200 known-login=0 answer=
transmission 200 known-login=0 answer=
transmission/install 200 known-login=0 answer=
prowlarr 200 known-login=0 answer=
prowlarr/install 200 known-login=0 answer=
control bookstack: 200 known-login=0 alapertelmezett=0
control wger: 200 known-login=0 alapertelmezett=0
control mealie: 200 known-login=0 alapertelmezett=0
control claper: 200 known-login=0 alapertelmezett=0
@@ -0,0 +1,3 @@
FAIL unreachable (000) -> refused (exit 1)
FAIL server error (500) -> refused (exit 1)
2 of 5 cases FAILED
@@ -0,0 +1,4 @@
XX DECOY: the PARENT /romm mounted rc=0 (expected 1)
XX DECOY: a CHILD /romm/assets/x mounted rc=0 (expected 1)
AssertionError: Lists differ: [' default_creds: "admin / x"\n'] != []
FAILED (failures=1)
@@ -0,0 +1,26 @@
## refused = any non-200 (FileBrowser down reads as refused)
=== RUN TestFileBrowserStoredPassword_RefusedOnlyOnA401
filebrowser_reset_test.go:182: FileBrowser down reads as refused — an unproven refusal must not tell the household its password changed
--- FAIL: TestFileBrowserStoredPassword_RefusedOnlyOnA401 (0.00s)
## no stored-password check on the page
=== RUN TestFileBrowserLogin_StoredPasswordRefusedOffersReset
logins_folders_test.go:365: stale: the page still offers the reveal of a password FileBrowser refuses
--- FAIL: TestFileBrowserLogin_StoredPasswordRefusedOffersReset (0.08s)
## the change-it line back on FileBrowser
=== RUN TestFileBrowserLogin_StoredPasswordRefusedOffersReset
logins_folders_test.go:380: FileBrowser still says to change the password after the first login (R-1101)
--- FAIL: TestFileBrowserLogin_StoredPasswordRefusedOffersReset (0.08s)
## no redirect for an installed app's /install
=== RUN TestInstallAddress_InstalledRedirectsToSettings
logins_folders_test.go:432: installed: 200 → "", want 302 → /apps/calibre-web/settings
--- FAIL: TestInstallAddress_InstalledRedirectsToSettings (0.08s)
## off-drive back in a Data tab string (hu)
=== RUN TestAppData_OneBackupSection
## a second backup section
=== RUN TestAppData_OneBackupSection
logins_folders_test.go:408: hu: 2 backup sections, want 1
logins_folders_test.go:408: en: 2 backup sections, want 1
--- FAIL: TestAppData_OneBackupSection (0.10s)
## (redo) off-drive/PBS back in the always-shown 2nd-copy text (hu)
logins_folders_test.go:415: hu: off-drive/PBS on the Data tab ("off-drive")
--- FAIL: TestAppData_OneBackupSection (0.09s)
@@ -0,0 +1,6 @@
FAIL --push, tag present -> refuses: exit 0, want 1
FAIL --push, tag absent -> proceeds: no 'is not present in the registry'
FAIL --multiarch, tag present -> refuses: exit 0, want 1
FAIL --push, registry unreachable -> refuses: exit 0, want 1
FAIL --push, auth failure -> refuses: exit 0, want 1
5 of 6 cases FAILED
@@ -0,0 +1,15 @@
== tester-1
unhealthy=0 restarting=0 exited=0 created=0
controller gitea.dooplex.hu/admin/felhom-controller:0.321.0 Up 18 seconds (healthy)
filebrowser Up 16 seconds (healthy)
2
== demo-hp
unhealthy=0 restarting=0 exited=0 created=0
controller gitea.dooplex.hu/admin/felhom-controller:0.321.0 Up 21 seconds (healthy)
filebrowser Up 3 minutes (healthy)
2
== demo-felhom
unhealthy=0 restarting=0 exited=0 created=0
controller gitea.dooplex.hu/admin/felhom-controller:0.321.0 Up 25 seconds (healthy)
filebrowser Up 23 seconds (healthy)
2
@@ -0,0 +1,3 @@
tester-1 Location: /customers/tester-1?flash=floor_set
demo-hp Location: /customers/demo-hp?flash=floor_set
demo-felhom Location: /customers/demo-felhom?flash=floor_set
@@ -0,0 +1,3 @@
demo-hp Location: /customers/demo-hp?flash=floor_set
tester-1 Location: /customers/tester-1?flash=floor_set
demo-felhom Location: /customers/demo-felhom?flash=floor_set
@@ -0,0 +1,3 @@
felhom.enkisfelhom.hu 200
felhom.enkicsifelhom.hu 200
felhom.demo-felhom.eu 200
@@ -0,0 +1,3 @@
17:22:20
HTTP/1.1 303 See Other
Location: /configuration?flash=floor_set
@@ -0,0 +1,40 @@
control NEGATIVE (a stub answering 200 over plain http): convicted, as it must (200 -)
== tester-1 (enkicsifelhom.hu): 13 public name(s)
OK control POSITIVE http://felhom.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkicsifelhom.hu/r1033-check?q=1
OK http://files.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://files.enkicsifelhom.hu/r1033-check?q=1
OK http://media.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://media.enkicsifelhom.hu/r1033-check?q=1
OK http://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1
OK http://paperless.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paperless.enkicsifelhom.hu/r1033-check?q=1
OK http://paste.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paste.enkicsifelhom.hu/r1033-check?q=1
OK http://plex.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://plex.enkicsifelhom.hu/r1033-check?q=1
OK http://prowlarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://prowlarr.enkicsifelhom.hu/r1033-check?q=1
OK http://radarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://radarr.enkicsifelhom.hu/r1033-check?q=1
OK http://sonarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://sonarr.enkicsifelhom.hu/r1033-check?q=1
OK http://torrent.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://torrent.enkicsifelhom.hu/r1033-check?q=1
OK http://transmission.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://transmission.enkicsifelhom.hu/r1033-check?q=1
OK http://wiki.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkicsifelhom.hu/r1033-check?q=1
== demo-hp (enkisfelhom.hu): 14 public name(s)
OK http://arcade.enkisfelhom.hu/r1033-check?q=1 -> 301 https://arcade.enkisfelhom.hu/r1033-check?q=1
OK http://books.enkisfelhom.hu/r1033-check?q=1 -> 301 https://books.enkisfelhom.hu/r1033-check?q=1
OK http://docs.enkisfelhom.hu/r1033-check?q=1 -> 301 https://docs.enkisfelhom.hu/r1033-check?q=1
OK http://felhom.enkisfelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkisfelhom.hu/r1033-check?q=1
OK http://files.enkisfelhom.hu/r1033-check?q=1 -> 301 https://files.enkisfelhom.hu/r1033-check?q=1
OK http://gist.enkisfelhom.hu/r1033-check?q=1 -> 301 https://gist.enkisfelhom.hu/r1033-check?q=1
OK http://ha.enkisfelhom.hu/r1033-check?q=1 -> 301 https://ha.enkisfelhom.hu/r1033-check?q=1
OK http://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1
OK http://papir.enkisfelhom.hu/r1033-check?q=1 -> 301 https://papir.enkisfelhom.hu/r1033-check?q=1
OK http://paste.enkisfelhom.hu/r1033-check?q=1 -> 301 https://paste.enkisfelhom.hu/r1033-check?q=1
OK http://pdf.enkisfelhom.hu/r1033-check?q=1 -> 301 https://pdf.enkisfelhom.hu/r1033-check?q=1
OK http://plex.enkisfelhom.hu/r1033-check?q=1 -> 301 https://plex.enkisfelhom.hu/r1033-check?q=1
OK http://travel.enkisfelhom.hu/r1033-check?q=1 -> 301 https://travel.enkisfelhom.hu/r1033-check?q=1
OK http://wiki.enkisfelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkisfelhom.hu/r1033-check?q=1
== demo-felhom (demo-felhom.eu): 8 public name(s)
OK http://felhom.demo-felhom.eu/r1033-check?q=1 -> 301 https://felhom.demo-felhom.eu/r1033-check?q=1
OK http://files.demo-felhom.eu/r1033-check?q=1 -> 301 https://files.demo-felhom.eu/r1033-check?q=1
OK http://gist.demo-felhom.eu/r1033-check?q=1 -> 301 https://gist.demo-felhom.eu/r1033-check?q=1
OK http://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1 -> 301 https://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1
OK http://prowlarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://prowlarr.demo-felhom.eu/r1033-check?q=1
OK http://radarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://radarr.demo-felhom.eu/r1033-check?q=1
OK http://sonarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://sonarr.demo-felhom.eu/r1033-check?q=1
OK http://torrent.demo-felhom.eu/r1033-check?q=1 -> 301 https://torrent.demo-felhom.eu/r1033-check?q=1
== every public name redirects http -> https
@@ -0,0 +1,7 @@
FIRST READ (hub.db, -wal, -shm copied ONE BY ONE with kubectl exec cat) — STALE:
('min_controller_version', '0.320.0', '2026-10-16 13:48:34')
('min_controller_version_declared_min_agent', '0.320.0=0.158.1', '2026-10-16 13:48:34')
SECOND READ (one tar of the three files) — 17:23:
('min_controller_version', '0.321.0', '2026-10-16 15:22:20')
('min_controller_version_declared_min_agent', '0.321.0=0.158.1', '2026-10-16 15:22:20')
THIRD CHANNEL — GET /configuration with CC's token: the page shows 0.321.0
@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""v0.321.0 apps-burndown browser check (Data tab, FileBrowser page and FileBrowser itself, an install address). Minimal CDP client: log in to the controller through its PUBLIC address in headless Chrome, open each
app-page tab, assert computed styles, collect console errors, save screenshots. The password is read
from a 0600 file and only ever sent inside a CDP message; it is never printed."""
import base64, json, os, socket, struct, subprocess, sys, time, urllib.request
CHROME = os.path.expanduser('~/.cache/ms-playwright/chromium_headless_shell-1243/chrome-headless-shell-linux64/chrome-headless-shell')
BASE = sys.argv[1] # https://felhom.enkisfelhom.hu
PWFILE = sys.argv[2] # the controller password (0600 file)
FBPWFILE = sys.argv[3] # FileBrowser's admin password (0600 file) — for the cold „Fajlok" click
OUT = sys.argv[4]
PORT = 9333
os.makedirs(OUT, exist_ok=True)
prof = os.path.join(OUT, '.profile')
proc = subprocess.Popen([CHROME, f'--remote-debugging-port={PORT}', '--no-sandbox', f'--user-data-dir={prof}',
'--window-size=1280,900', 'about:blank'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
for _ in range(50):
try:
tabs = json.load(urllib.request.urlopen(f'http://127.0.0.1:{PORT}/json'))
break
except Exception:
time.sleep(0.2)
ws_url = [t for t in tabs if t['type'] == 'page'][0]['webSocketDebuggerUrl']
host, path = ws_url[len('ws://'):].split('/', 1)
h, p = host.split(':')
sock = socket.create_connection((h, int(p)))
key = base64.b64encode(os.urandom(16)).decode()
sock.send(f'GET /{path} HTTP/1.1\r\nHost: {host}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n\r\n'.encode())
buf = b''
while b'\r\n\r\n' not in buf:
buf += sock.recv(4096)
st = {'pending': buf.split(b'\r\n\r\n', 1)[1]}
def send(obj):
data = json.dumps(obj).encode()
hdr = bytearray([0x81])
n = len(data)
if n < 126: hdr.append(0x80 | n)
elif n < 65536: hdr.append(0x80 | 126); hdr += struct.pack('>H', n)
else: hdr.append(0x80 | 127); hdr += struct.pack('>Q', n)
mask = os.urandom(4); hdr += mask
sock.sendall(bytes(hdr) + bytes(b ^ mask[i % 4] for i, b in enumerate(data)))
def recv_exact(n):
while len(st['pending']) < n:
st['pending'] += sock.recv(1 << 20)
out, st['pending'] = st['pending'][:n], st['pending'][n:]
return out
def recv():
msg = b''
while True:
b0, b1 = recv_exact(2)
n = b1 & 0x7f
if n == 126: n = struct.unpack('>H', recv_exact(2))[0]
elif n == 127: n = struct.unpack('>Q', recv_exact(8))[0]
msg += recv_exact(n)
if b0 & 0x80: return json.loads(msg)
console = []
mid = [0]
def call(method, **params):
mid[0] += 1; i = mid[0]
send({'id': i, 'method': method, 'params': params})
while True:
m = recv()
if m.get('method') == 'Runtime.exceptionThrown':
console.append(('exception', m['params']['exceptionDetails'].get('text', ''), m['params']['exceptionDetails'].get('exception', {}).get('description', '')[:200]))
elif m.get('method') == 'Runtime.consoleAPICalled' and m['params']['type'] in ('error', 'warning'):
console.append((m['params']['type'], ' '.join(str(a.get('value', a.get('description', ''))) for a in m['params']['args'])[:200]))
elif m.get('method') == 'Log.entryAdded' and m['params']['entry']['level'] in ('error', 'warning'):
e = m['params']['entry']; console.append(('log-' + e['level'], e.get('text', '')[:200], e.get('url', '')))
if m.get('id') == i:
if 'error' in m: raise RuntimeError(f'{method}: {m["error"]}')
return m.get('result', {})
def ev(expr):
r = call('Runtime.evaluate', expression=expr, returnByValue=True, awaitPromise=True)
return r.get('result', {}).get('value')
def goto(url):
call('Page.navigate', url=url)
for _ in range(100):
time.sleep(0.2)
if ev('document.readyState') == 'complete':
break
time.sleep(1.5) # images' onerror, layout
for d in ('Page', 'Runtime', 'Log', 'Network'):
call(d + '.enable')
pw = open(PWFILE).read().strip()
goto(BASE + '/login')
ev("(function(){var f=document.querySelector('input[type=password]'); f.value=%s; f.form.submit(); return 1})()" % json.dumps(pw))
del pw
time.sleep(3)
print('after login url:', ev('location.pathname'))
console.clear() # the login page is not under test
def shot(name):
s = call('Page.captureScreenshot', format='png', captureBeyondViewport=True)
open(os.path.join(OUT, name + '.png'), 'wb').write(base64.b64decode(s['data']))
report = {}
for p in ['/apps/calibre-web/data', '/apps/filebrowser', '/apps/calibre-web/install']:
n0 = len(console)
goto(BASE + p)
report[p] = ev("""(function(){ return {path: location.pathname,
backupSections: document.querySelectorAll('[data-backup-section]').length,
rows: [].map.call(document.querySelectorAll('[data-backup-row]'), function(e){return e.getAttribute('data-backup-row')}),
offDrivePBS: /off-drive|\\bPBS\\b/i.test(document.body.innerText),
valueFont: (function(){var v=document.querySelector('[data-backup-row] .app-field-value'); return v?getComputedStyle(v).fontFamily.split(',')[0]:null})(),
exportToggle: (function(){var b=document.getElementById('exportPwToggle'); return b?[b.textContent.trim(), b.getAttribute('aria-label')]:null})(),
fbReveal: !!document.querySelector('#initcred-reveal'), fbStale: !!document.querySelector('[data-fb-stale]'),
changeItLine: document.body.innerText.indexOf('hagyd figyelmen') >= 0,
scrollWidth: document.documentElement.scrollWidth};})()""")
report[p]['console'] = console[n0:]
shot(p.strip('/').replace('/', '-'))
# FileBrowser itself, logged in with the box's own password: no update banner, no install-app prompt.
fbpw = open(FBPWFILE).read().strip()
call('Network.clearBrowserCookies')
goto(BASE.replace('://felhom.', '://files.') + '/')
ev("""(function(pw){var u=document.querySelector('input[type=text],input[name=username],input#username');
var p=document.querySelector('input[type=password]'); if(!u||!p) return 0;
function set(el,v){var d=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value'); d.set.call(el,v); el.dispatchEvent(new Event('input',{bubbles:true}));}
set(u,'admin'); set(p,pw); var b=document.querySelector('form button[type=submit], form input[type=submit], button.button--blue');
if(b){b.click()} else {p.form && p.form.requestSubmit && p.form.requestSubmit()} return 1})(%s)""" % json.dumps(fbpw))
del fbpw
time.sleep(5)
report['filebrowser'] = ev("""(function(){ var t=document.body.innerText;
return {path: location.pathname, updateBanner: /update is available/i.test(t), installApp: /install app/i.test(t),
releaseBanners: document.querySelectorAll('.release-banner').length,
globals: (function(){ try { var g=window.globalVars||{}; return {disablePWAInstall: g.disablePWAInstall, updateAvailable: g.updateAvailable}; } catch(e) { return null; } })()};})()""")
shot('filebrowser-sidebar')
print(json.dumps(report, indent=1, ensure_ascii=False))
finally:
proc.terminate()
subprocess.run(['rm', '-rf', prof])
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""romm_saves.py — R-1097 proof on demo-hp 9201 through the product's endpoints (box_walk helpers, TARGET=9201).
Sync the catalog, restart RomM (the applied compose gains romm_assets), write a save where RomM writes saves, run the
by-day night chain (the sanctioned backup trigger), remove RomM KEEPING its backups, restore from the backups page,
and read the save back. Env: SC (0600 scratch with .ctlpw), EV. The save is a dummy file; no secret is printed."""
import os, sys, time, hashlib
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
import box_walk as w
APP = "romm"
SAVE_DIR = "/romm/assets/romm/users/1/saves/gba/r1097-proof"
SAVE = SAVE_DIR + "/r1097-proof.sav"
log = open(os.path.join(w.EV, "romm-saves.txt"), "a", buffering=1)
def say(*a):
w.say(*a); log.write(" ".join(map(str, a)) + "\n")
def mounts():
return w.guest("docker inspect romm --format '{{range .Mounts}}{{.Type}} {{.Name}} -> {{.Destination}}{{println}}{{end}}' "
"| sed -E 's/volume [0-9a-f]{64}/volume <ANONYMOUS>/'").strip()
def save_state():
return w.guest(f"docker exec romm sh -c 'ls -la {SAVE_DIR} 2>&1; sha256sum {SAVE} 2>&1' | tail -3").strip()
w.login()
say("== 1. sync the catalog")
code, d = w.ctl("POST", "/api/sync"); say("sync ->", code, str(d)[:200])
say("applied compose names romm_assets:", w.guest("grep -c romm_assets /opt/docker/stacks/romm/docker-compose.yml").strip())
say("== 2. restart RomM (Újraindítás)")
code, d = w.ctl("POST", f"/api/stacks/{APP}/restart"); say("restart ->", code, str(d)[:160])
for _ in range(40):
time.sleep(5)
if w.stack(APP).get("state") == "running":
break
say("state:", w.stack(APP).get("state")); say("mounts after restart:\n" + mounts())
say("== 3. a save where RomM writes saves (dummy bytes, written with docker exec into the running container)")
w.guest(f"docker exec romm sh -c 'mkdir -p {SAVE_DIR} && head -c 32768 /dev/urandom > {SAVE}'")
before = save_state(); say(before)
say("== 4. the by-day backup (night chain)")
t0 = time.time(); code, d = w.ctl("POST", "/api/debug/backup/night-chain"); say("night-chain ->", code, str(d)[:160])
for _ in range(180):
time.sleep(10)
tail = w.guest("docker logs --since 60m felhom-controller 2>&1 | grep 'night-chain' | tail -1").strip()
if "finished in" in tail or "REFUSED" in tail:
break
say("night chain last line:", tail[-220:], f"after {round(time.time()-t0)}s")
say("romm volume dumps now:", w.guest("ls -la /mnt/felhom-drives/hdd_1/backups/*/romm/volume-dumps/ /mnt/felhom-drives/hdd_1/*/romm/recovery-unit/volume-dumps/ 2>/dev/null; find /mnt/felhom-drives/hdd_1 -name 'romm_romm_assets.tar' -newer /proc/1 2>/dev/null | head -5"))
say("== 5. remove RomM, KEEPING its backups")
w.ctl("POST", f"/api/stacks/{APP}/stop"); time.sleep(10)
code, d = w.ctl("POST", f"/api/stacks/{APP}/remove", {"remove_hdd_data": False, "remove_backups": False})
say("remove ->", code, str(d)[:200])
time.sleep(5)
say("volumes left:", w.guest("docker volume ls --format '{{.Name}}' | grep -i romm").strip() or "(none)")
say("== 6. restore from the backups page")
r = w.restore(APP); say("restore:", {k: r.get(k) for k in ("ok", "snapshot_id", "http", "seconds")})
for _ in range(60):
time.sleep(5)
if w.stack(APP).get("state") == "running":
break
say("state:", w.stack(APP).get("state")); say("mounts after restore:\n" + mounts())
after = save_state(); say(after)
same = before.split()[-2:] == after.split()[-2:]
say("== VERDICT: the save came back byte-identical:", same)
+10
View File
@@ -26,6 +26,16 @@
---
## 2026-10-16 — apps burndown (controller v0.321.0, catalog `326f846`, agent script `dd8b2ac`, hub build script)
| Row | What | Closed | Evidence |
|---|---|---|---|
| **R-1097** | **RomM keeps the household's saves in an anonymous Docker volume, outside the backup.** (P3) | CLOSED 2026-10-16 — **measured first** on demo-hp 9201: every RomM backup held romm_config, romm_db_data, romm_redis_data and no saves; the app backup takes only compose-declared named volumes (`backup.ResolveDockerVolumeNames`). Catalog `326f846`: `romm_assets:/romm/assets` in the compose and both `steps/` files; gate `image-volumes` (measured registry, decoys, red-proved). **Proven:** a save written into `/romm/assets` → the by-day night chain wrote `romm_romm_assets.tar` → RomM removed (keeping backups, the volume went) → restored from the backups page → the save back byte-identical (sha256). No one-time copy: operator 2026-10-16, no RomM saves worth keeping; the hub shows RomM only on demo-hp. | `audits/burndown-apps-2026-10-16/P1/` |
| **R-1102** | **Eight apps said „starts with a known, shared password" about a generated or typed login.** (P3) | CLOSED 2026-10-16 — operator decision **D10 = A**: `app_info.default_creds` removed (hu + en) from grafana, gokapi, kimai, code-server, nzbget, nextcloud, transmission, prowlarr; copy freeze re-captured with the reason; `D10NoFalseKnownLogin` red-proved. **Live** on demo-hp: none of the eight shows the line on its app page or install page; the login panel gives its answer. **Reasoning kept:** the user name „admin" stays in the first steps of grafana, gokapi, nzbget, transmission; no live control exists any more (these eight were the only apps that showed the line) — `known_login_test.go` is the mechanism control. | `audits/burndown-apps-2026-10-16/live/d10-check.txt` |
| **R-1101** | **FileBrowser's stored password went stale when changed in FileBrowser, and the page said to change it.** (P4) | CLOSED 2026-10-16 — **controller v0.321.0**: the „change it" line is gone for FileBrowser; in `generated` the page asks FileBrowser first (only its own 401/403 counts; not answering is not a refusal) and, refused, says so and offers „Új jelszó beállítása", which accepts that case. Red-proved both ways. Also: FileBrowser's own update banner and „Install app" prompt switched off in the rendered config (`disableUpdateCheck`, `disablePWAInstall`), checked in the browser. | `audits/burndown-apps-2026-10-16/browser/report.json` |
| **R-1095** | **`/apps/{slug}/install` still opened for an installed app.** (P4) | CLOSED 2026-10-16 — **controller v0.321.0**: GET answers 302 to `/apps/{slug}/settings` (POST untouched); red-proved; **live** in the browser. The installed-only template branches stay — R-1103. | `audits/burndown-apps-2026-10-16/browser/report.json` |
| **R-1098** | **The hub build did not refuse a version already in the registry.** (P3) | CLOSED 2026-10-16 — `scripts/build-hub.sh` v1.25.0 refuses before the build when the tag exists, and when the registry cannot say (the controller `build.sh` rule); `scripts/test_build_hub_push_once.sh`, 6 cases, red-proved (5 fail with the check removed). The agent's `release-agent.sh` had the check but let an unreachable registry through: now only a clean 404 proceeds (`test_release_push_once.sh`, red-proved). No hub image, no deploy. | `audits/burndown-apps-2026-10-16/redproof/` |
## 2026-10-16 — RomM, Ghost, withdrawn controller builds (catalog `fb18471`, hub v0.155.0)
| Row | What | Closed | Evidence |
+4 -8
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 — 25 rows (P2 1, P3 14, P4 10)
## Apps & catalog — 23 rows (P2 1, P3 12, P4 10)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
@@ -144,15 +144,12 @@ stopping line that lies.
| **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 |
| **R-995** | Apps & catalog | P4 | **Removing an app through the product can leave unnamed Docker volumes and an empty import folder behind.** MEASURED 2026-10-13 on demo-felhom after 61 removals (`documentation/audits/sizing-2026-10-13/METHOD.md` „Teardown”, `evidence/leftover-volumes-after-bench.txt`): 8 unnamed volumes (4–72 KB; immich 1, romm 2, privatebin 3, onlyoffice 2 — volumes the images declare themselves) and `userdata/import/{paperless,calibre,grimmory}` (empty, created by the deploy). The removal answered 200 and its own leftover check was clean, because it looks only at labelled volumes. No data risk; disk and tidiness only. Removed by name on the bench. | **READY** — filed 2026-10-13 | — | Make the removal account for the containers' unnamed volumes (and an empty import folder it created), with a test that deploys an image that declares a VOLUME | CC |
| **R-1067** | Apps & catalog | P4 | **The catalog still carries `pi_compatible:` on every template, and nothing reads it any more.** controller v0.315.0 removed the „Pi kompatibilis" badge and the „csak x86" tag from every page (Raspberry Pi is no longer supported, so one was noise and the other was a warning on almost every app). The `ResourceHints.PiCompatible` struct field and the catalog key were deliberately LEFT: a box still on 0.314.0 reads the key, and removing it would make that box show „csak x86" on every app. **Gated on the floor:** once the fleet floor is at or above 0.315.0, drop `pi_compatible:` from the templates and the field from `stacks.ResourceHints` in the same pair of commits. Harmless until then — an unread key costs nothing. | **BLOCKED** — filed 2026-10-15 | the fleet floor reaching 0.315.0 | Drop the catalog key and the struct field; grep the hub and the website for Pi wording at the same time (none found 2026-10-15) | CC |
| **R-1067** | Apps & catalog | P4 | **The catalog still carries `pi_compatible:` on every template, and nothing reads it any more.** controller v0.315.0 removed the „Pi kompatibilis" badge and the „csak x86" tag from every page (Raspberry Pi is no longer supported, so one was noise and the other was a warning on almost every app). The `ResourceHints.PiCompatible` struct field and the catalog key were deliberately LEFT: a box still on 0.314.0 reads the key, and removing it would make that box show „csak x86" on every app. **Gated on the floor:** once the fleet floor is at or above 0.315.0, drop `pi_compatible:` from the templates and the field from `stacks.ResourceHints` in the same pair of commits. Harmless until then — an unread key costs nothing. | **BLOCKED** — filed 2026-10-15. Checked 2026-10-16 (apps burndown): the floor is 0.321.0 and demo-hp, Tester 1 and the N100 run 0.321.0, but **Tester-2 still reports 0.292.0** (its newest report 2026-10-04; hub DB with WAL) — so nothing removed. | every box on ≥ 0.315.0 — today Tester-2 | Drop the catalog key and the struct field; grep the hub and the website for Pi wording at the same time (none found 2026-10-15) | CC |
| **R-1043** | Apps & catalog | P4 | **Sonarr, Radarr and Prowlarr build their login redirect as an absolute `http://` URL.** They trust forwarded headers only from their „Trusted networks” (the household set `192.168.0.0/24`; traefik is `172.18.x`), so they ignore `X-Forwarded-Proto: https` and answer `Location: http://<name>/login` to an https visitor. Since controller v0.312.0 that hop is sent back to https by traefik (a GET, no password), so it is cosmetic; a template could seed traefik's network as trusted. (`audits/https-only-2026-10-14/``1A-3-4.txt`) | **OPEN** — filed 2026-10-14 | — | Seed the docker network as a trusted proxy in the *arr templates (with the R-1030 seed), measure the redirect | CC |
| **R-1095** | Apps & catalog | P4 | **`/apps/{slug}/install` still opens for an app that is INSTALLED, and shows the install form read-only.** Found 2026-10-16 (app page tuning, controller v0.319.0): the settings tab stopped rendering the install form, but `appInstallPageHandler` renders `deploy` for any app, so a typed or stale `/install` address shows „<app> — Telepítés" with disabled fields and no button. Its secret guards hold (R-254/R-709 tests; parity cases `deploy_installed*`). | OPEN | the task forbade route changes | a 302 from `/apps/{slug}/install` to `/apps/{slug}/settings` for an installed app (the `redirectStackDeployGET` shape); then the deployed-only branches of `app_fields.html` can go | CC |
| **R-1096** | Apps & catalog | P3 | **After a disk-full, an app's damaged Redis file keeps the app down for good, and nothing says what to do.** FOUND 2026-10-16 on demo-hp 9201: the standing RomM (installed 2026-08-21) was stopped; the dashboard's Start re-created it, `romm-redis` crash-looped (unhealthy), the box stopped it again (`crash_loop`, `09` §3 decision 28) and mailed `app_start_failed`. Its Redis volume dates from the 2026-09-24 thin-pool fill (R-672): Docmost's damaged append-only file was repaired by hand that day with `redis-check-aof --fix` (`audits/r672-2026-09-24/B7-redis-aof-fix.txt`); RomM's was not (the copy `romm_romm_redis_data.pre-aof-fix-20260924T135937Z` is still on the box). The Redis log was not read: the box removed the containers first. That RomM was then removed by operator ruling (`audits/romm-ghost-2026-10-16/A1/`). | OPEN — filed 2026-10-16 | — | List the catalog apps whose Redis runs with `--appendonly yes` (romm does); for each, decide whether its Redis needs persistence at all; reproduce on 9202 by damaging an append-only file, and make the box repair a copy or say what to do | CC |
| **R-1100** | Apps & catalog | P3 | **One sign-in for the dashboard and FileBrowser (proxy auth) — spike first.** Since controller 0.320.0 every app with a folder has „Fájlok ↗", and the box can show FileBrowser's password — but FileBrowser still asks for its OWN login the first time, so the household signs in twice. FileBrowser Quantum has a proxy-auth method (`auth.methods.proxyAuth`, header-trusted) that could take the dashboard session through traefik; nobody has measured it. Design: `architecture/02-controller-module-map.md` „Logins and folders". | OPEN — filed 2026-10-16 (task logins-folders). | — | Spike on demo-hp: proxy auth at the pinned tag (source + measured), a forwardAuth in traefik that answers only for a dashboard session, what a stranger reaching `files.<domain>` with a forged header gets, and what happens to FileBrowser's shares. | CC |
| **R-1102** | Apps & catalog | P3 | **Eight apps say „starts with a known, shared password" about a login that is generated, set at install, or made on the first visit.** `app_info.default_creds` with no `after_install` keeps the known-login sentence in effect (`web/known_login.go`), so the install dialog and the app page say „Ez az alkalmazás egy ismert, közös jelszóval indul: <default_creds>. Telepítés után azonnal változtasd meg." for grafana, gokapi, kimai, code-server, nzbget, nextcloud, transmission (a password the box generated or the household typed, shown in the login panel) and prowlarr („Az első belépéskor te hozod létre"). Found 2026-10-16 while adding the login panel's answers. | OPEN | — | Catalog: drop `default_creds` on these eight (hu + `i18n.en`, `check-copy-i18n.py --capture-freeze`) now that the login panel says where the login is — or keep the hint and give the controller a „hint, not a known password" form. An operator word on which. | CC |
| **R-1097** | Apps & catalog | P3 | **RomM keeps the household's saves, save states and screenshots in an anonymous Docker volume, outside the backup.** FOUND 2026-10-16 from RomM 5.3.1's source and the running container: `ASSETS_BASE_PATH = /romm/assets` (`backend/config/__init__.py:39`); the image declares `VOLUME /romm`; the compose mounts nothing at `/romm/assets`, and `docker inspect` on 9201 showed an anonymous volume at `/romm`. Compose keeps an anonymous volume across a recreate, so an update does not lose it; a remove + reinstall or a restore does, and the app backup (named volumes + drive folders) probably does not hold it — NOT measured. Moving it to a named volume or the drive would hide what a box already has, so a fix needs a one-time copy. | OPEN — filed 2026-10-16 | — | Measure on 9202: a save in a browser game, back up, restore, read the save; then a named `romm_assets` volume with a one-time copy from the anonymous one | CC |
| **R-1099** | Apps & catalog | P4 | **The N100 may hold RomM games in the old upload place, now shown under a fake „roms” platform.** Until 2026-10-16 RomM's own upload wrote to the drive's `roms/roms/<platform>/`; after the mapping fix (catalog `fb18471`, R-1089) those files are at RomM's `library/roms/roms/…`. Measured on 9201: such a game showed as missing in its platform and again under a platform „roms” (`audits/romm-ghost-2026-10-16/B/b-after.txt`). Files put in with the file manager are not affected — they are now found. The N100 runs RomM (hub `/apps/romm`); CC does not log in to the lived-in box. | OPEN — filed 2026-10-16 | Operator: look at `roms/roms/` on the N100's drive in the file manager | If it holds platform folders: move them up one level into `roms/`, then run „Scan” in RomM; if it is empty, close | Operator |
| **R-1101** | Apps & catalog | P4 | **FileBrowser's stored password goes stale when the household changes it in FileBrowser — and the page tells them to.** In the `generated` state the panel shows „Az első bejelentkezés után azonnal változtasd meg!" under FileBrowser's login (R-513 copy). Changing it in FileBrowser leaves the box's copy wrong: „Megjelenítés" then shows a password that is refused, and the state stays `generated`, so „Új jelszó beállítása" is not offered. Found 2026-10-16. | OPEN | — | Either drop the „change it" line for FileBrowser, or have the reveal (or a tick) check the stored value against FileBrowser's login and fall back to `operator` when it is refused. | CC |
| **R-1103** | Apps & catalog | P4 | **`app_fields.html` still carries the install form's INSTALLED-only branches, which nothing serves since v0.321.0.** R-1095 made `/apps/{slug}/install` of an installed app answer 302 to its settings tab, so `deploy.html` now renders only for a not-installed app. The `.AlreadyDeployed` branches (disabled inputs, the eye reveals, `DeployedFieldValues`) stayed because the R-254/R-709 guard tests (`deploy_secret_exposure_test.go`, `r709_password_field_test.go`, `r694_restored_login_test.go`) render that path by hand and assert a reveal control on it; removing the branches means retiring those tests for an unreachable path. Found 2026-10-16. | OPEN | — | Remove the branches and the parity cases `deploy_installed*`; move each guard's assertion to the settings tab it now protects (most already exist there: `TestAppPage_NoStoredValueShowsSentence`, `TestR709_*`), red-proved | CC |
## App updates — 4 rows (P3 4)
@@ -315,7 +312,7 @@ stopping line that lies.
| **R-990** | Business & legal | P4 | **Operator: read `documentation/audits/forum-research-2026-10-12/FELHOM.md` §5 and rule on each recommendation.** READ-ONLY research 2026-10-12: three years (#7943–#11092, 2,908 posts, 195 people, 82 beginner threads) of the public PROHARDVER! topic „Home server / házi szerver építése”, tagged and counted by person (`FINDINGS.md`, method and spot-check in `METHOD.md`; no username or post text in git). Ten ranked recommendations, none acted on; the top three: a „which machine works” page (8 of 33 beginners who name their hardware own only a Synology, a router disk or a Raspberry Pi — the installer cannot run there; the same gap is R-989's first recommendation from a different source, and the shipped-page half is R-991), „you do not choose an operating system — we do” (the biggest beginner question, 20 of 82 threads), and „bring your old office PC” as the cheap entry. Catalog gap (≥ 3 people): a DLNA server (7), an ad/DNS blocker (5), a camera recorder (5). | **WAITING-ON-OPERATOR** — filed 2026-10-12 | the operator's reading | Operator: rule on each of the ten (yes / no / later). If nothing is done: nothing changes; the research ages | operator |
| **R-1035** | Business & legal | P4 | **Did the „felhő tárhely” page earn anything? Two dated checks.** **Baseline (reported by the operator from Search Console, 2026-10-14, last 3 months):** 1 click, 48 impressions, CTR 2.1 %, average position **73.6**; „felhő tárhely” 19 impressions, „felhőtárhely” 5, „felhő alapú tárhely” 3. CC measured separately that the phrase „felhő tárhely” appeared **0 times** on the home, FAQ and backup pages — Google was showing the home page for those queries by accident. `/sajat-felho-tarhely` + `/en/own-cloud-storage` published **2026-10-14**; full baseline, price sources and the power provenance in `audits/seo-2026-10-14/BASELINE.md`. **What to measure, both times:** impressions and average position for „felhő tárhely” and „saját felhő tárhely”, plus the new page's own impressions, clicks and position. **A null result at +4 weeks means nothing on its own** — a new page on a site at position 73.6 takes weeks to months to move, and „felhő tárhely” alone is held by Microsoft, Google and the big list sites; the page aims at the two longer phrases. That is why there are two checks and not one, and why neither should be read as a verdict on the copy. **There is no Search Console connector in this project, so CC cannot read these numbers** — the operator reads them, CC records and compares. **Blocked until the operator asks for indexing** (Search Console → URL-ellenőrzés → the URL → „Indexelés kérése”): until that happens, or Google finds the page itself, both checks read zero for a reason that is not the page. **This is a slow channel and does not replace talking to people.** | **WATCHING** | operator: request indexing, then supply the Search Console read | 2026-11-11: first read (+4 weeks); 2026-12-09: second (+8 weeks), compared with the first | operator + CC |
## Process & tooling — 30 rows (P3 12, P4 18)
## Process & tooling — 29 rows (P3 11, P4 18)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
@@ -348,7 +345,6 @@ stopping line that lies.
| **R-1025** | Process & tooling | P4 | **A power drill on demo-hp cuts Tester 1 too, and nothing warns the session using it.** 2026-10-14 17:25 CEST another session cut demo-hp's plug (`plugcut.py --plug hp`, the Immich drill) while this session ran downloads on Tester 1 (VM 341 runs on demo-hp); Tester 1 rebooted and came back by itself. The drill's fence checks Tester 1 for backups and package jobs only. | **OPEN** — filed 2026-10-14 | — | Add „a test runs on Tester 1” to the fence (a marker file the testing session writes), or name the rule in `runbooks/target-selection.md` | CC |
| **R-1039** | Process & tooling | P4 | **qBittorrent stalls on the LAST piece of an archive.org torrent; Transmission does not.** Seen three times on 2026-10-14 (Tester 1 S2 and S3, demo-hp 9201 live read-back): the film reaches 99.994 %, the piece shared with archive.org's side files (`__ia_thumb.jpg`, `_meta.sqlite`, `_meta.xml`) never arrives, qBittorrent's log shows „500 Internal Server Error” from the archive.org web seed, while Transmission finishes the same torrent and curl range requests on the same files answer 206. A household that uses archive.org torrents with qBittorrent hits it too; our tests finish only with a local peer. | **OPEN** — filed 2026-10-14 | — | Note it in qBittorrent's onboarding record; for test films use Transmission, or a public-domain torrent with real peers | CC |
| **R-1078** | Process & tooling | P4 | **A controller comment still describes `mp1 /mnt/sys_drive` as a separate volume.** `C/cmd/controller/main.go:2546` („the SYSTEM-DATA volume (`mp1`, `/mnt/sys_drive`)…”) — `mp1` was removed by R-165; `sys_drive` is a folder of `mp0` (measured on all three boxes, `audits/storage-identity-2026-10-15/README.md`). The same stale cell in `07-backup-architecture.md` §6.1 was corrected in this session. Not fixed here: this run allowed no product-repo change. | READY | — | Correct the comment in the next controller change. | CC |
| **R-1098** | Process & tooling | P3 | **The hub build does not refuse a version that already exists in the registry.** `scripts/build-hub.sh` v1.24.0 refuses a dirty tree or one behind `origin/main`, but not an existing tag; the controller's `build.sh` refuses a second push of a tag (`09-update-architecture.md` §0). Found 2026-10-16 building hub 0.155.0; checked by hand that time (`docker manifest inspect`: 0.155.0 absent, control 0.154.0 present; `audits/romm-ghost-2026-10-16/B/hub-build-0.155.0.txt`). | OPEN — filed 2026-10-16 | — | Give `build-hub.sh` the controller's refusal (registry check; an unreachable registry refuses), with test cases seen failing; check the agent build the same way | CC |
<!-- DUE-CHECKS-BEGIN — machine-readable. Parsed by scripts/due_checks_gate.py.
One row per dated check. The R-number must have a row above. Dates are UTC.
+7
View File
@@ -1,3 +1,10 @@
## 2026-10-16 — build-hub.sh 1.25.0: a version number is pushed once (R-1098)
`scripts/build-hub.sh` refuses `--push`/`--multiarch` of a tag the registry already has, and refuses when the registry
cannot say (unreachable, auth failure) — before the build, the controller `build.sh` rule (`09-update-architecture.md`
§0). Local builds are unaffected. Test: `scripts/test_build_hub_push_once.sh` (6 cases, the real script, a fake
`docker`), red-proved: with the check removed, 5 cases fail. The tree-gate test still passes. No hub image change.
## 2026-10-16 — felhom-host-install.sh 1.33.0: the data volume by the ruled table; the internal data place (step 5b)
`architecture/16-storage-physical-identity.md` §5, §7 (sizes ruled by the operator 2026-10-16 after the build B space check,
+27 -1
View File
@@ -19,7 +19,7 @@
# =============================================================================
set -euo pipefail
SCRIPT_VERSION="1.24.0"
SCRIPT_VERSION="1.25.0"
# --- Configuration (edit these if your paths differ) ---
# FELHOM_HUB_REPO_DIR builds from another clone (a session's own clean clone; the script's own test) — the same
@@ -56,6 +56,32 @@ if ! command -v docker &>/dev/null; then
exit 1
fi
# =========================================================================
# Step 0: a version number is pushed ONCE (R-1098, v1.25.0) — the controller build.sh's check, word for word in effect
# =========================================================================
# A box (and the hub's floor) compares VERSIONS; two images under one number mean a pod can run either and nobody can
# tell (`09-update-architecture.md` §0: controller 0.315.0 was pushed three times). The check runs BEFORE the build, so
# a refusal costs no minutes. An unreachable registry is a REFUSAL too: the point is to prove the tag is new, and
# "I could not ask" is not that proof. Local builds (no --push) are unaffected. `docker manifest inspect` is the probe,
# so a test puts a fake `docker` on PATH: scripts/test_build_hub_push_once.sh.
if [[ "${ACTION}" == "--push" || "${ACTION}" == "--multiarch" ]]; then
step "0/3 — Checking the registry: is ${VERSION} already published?"
probe_rc=0
probe_out=$(docker manifest inspect "${IMAGE}:${VERSION}" 2>&1) || probe_rc=$?
if [[ ${probe_rc} -eq 0 ]]; then
error "${IMAGE}:${VERSION} already exists in the registry — bump the version."
error "A version number is pushed once. If this is a fix for something already pushed, release it as the next"
error "patch version instead."
exit 1
fi
if ! grep -qiE 'no such manifest|manifest unknown|not found|does not exist|NAME_UNKNOWN|MANIFEST_UNKNOWN' <<<"${probe_out}"; then
error "Could not establish whether ${IMAGE}:${VERSION} exists — refusing to build."
error "The registry answered: ${probe_out}"
exit 1
fi
info "${VERSION} is not present in the registry — proceeding."
fi
# =========================================================================
# Step 1: the clean-tree gate (R-1021) — fetch, pull, and REFUSE a stale or dirty tree
# =========================================================================
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# test_build_hub_push_once.sh — build-hub.sh refuses to push a version the registry already has (R-1098).
#
# Same shape as felhom-controller/controller/scripts/test_build_push_once.sh: a fake `docker` first on PATH answers
# `manifest inspect` as FAKE_MODE says and reports any build/push it is asked for, so nothing is ever built or pushed.
# The REAL build-hub.sh runs (a copy next to a scratch clean clone, so its workspace/ is scratch too) — the test cannot
# pass with the check deleted, which is the red-proof.
# tag present -> refused, "already exists", no build reached
# tag absent -> "is not present", proceeds
# registry unreachable -> refused, "Could not establish"
# auth failure -> refused, "Could not establish"
# --multiarch, present -> refused
# local build, present -> not refused (the check does not run)
# Run: bash scripts/test_build_hub_push_once.sh (exit 0 all cases right · 1 a case wrong)
set -uo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
WS="$(mktemp -d)"
trap 'rm -rf "${WS:?}"' EXIT
fails=0; ran=0
mkdir -p "$WS/bin" "$WS/build"
cat > "$WS/bin/docker" <<'FAKE'
#!/bin/bash
case "$1 $2" in
"manifest inspect")
case "${FAKE_MODE}" in
present) echo '{"schemaVersion":2}'; exit 0 ;;
absent) echo "manifest unknown: manifest unknown" >&2; exit 1 ;;
down) echo "Get \"https://gitea.dooplex.hu/v2/\": dial tcp: connection refused" >&2; exit 1 ;;
denied) echo "unauthorized: authentication required" >&2; exit 1 ;;
esac ;;
"build "*|"buildx "*|"push "*) echo "FAKE-DOCKER-REACHED: $*"; exit 0 ;;
esac
exit 0
FAKE
chmod +x "$WS/bin/docker"
cp "$HERE/build-hub.sh" "$WS/build/build.sh"
git init -q --bare -b main "$WS/origin.git"
git clone -q "$WS/origin.git" "$WS/clone" 2>/dev/null
(cd "$WS/clone" && mkdir -p hub/cmd/hub hub/internal/api hub/internal/store hub/internal/web \
&& printf 'module x\n\ngo 1.22\n' > hub/go.mod && echo a > hub/cmd/hub/a.txt \
&& git add -A && git -c user.name=t -c user.email=t@t commit -q -m init && git push -q origin main)
run() { # name, mode, expected rc ("any" = do not assert), required fragment, forbidden fragment, args...
local name="$1" mode="$2" want="$3" need="$4" forbid="$5"; shift 5
local out rc
out=$(cd "$WS/build" && FAKE_MODE="$mode" PATH="$WS/bin:$PATH" FELHOM_HUB_REPO_DIR="$WS/clone" bash ./build.sh "$@" 2>&1); rc=$?
ran=$((ran+1))
if [[ "$want" != "any" && "$rc" != "$want" ]]; then
fails=$((fails+1)); echo " FAIL $name: exit $rc, want $want"; echo "$out" | tail -5 | sed 's/^/ /'; return
fi
if [[ -n "$need" ]] && ! grep -qi -- "$need" <<<"$out"; then
fails=$((fails+1)); echo " FAIL $name: no '$need'"; echo "$out" | tail -5 | sed 's/^/ /'; return
fi
if [[ -n "$forbid" ]] && grep -qi -- "$forbid" <<<"$out"; then
fails=$((fails+1)); echo " FAIL $name: contains '$forbid'"; echo "$out" | tail -5 | sed 's/^/ /'; return
fi
echo " ok $name"
}
echo "build-hub.sh — a version number is pushed once"
run "--push, tag present -> refuses" present 1 "already exists in the registry" "FAKE-DOCKER-REACHED" 9.9.9 --push
run "--push, tag absent -> proceeds" absent any "is not present in the registry" "already exists" 9.9.9 --push
run "--multiarch, tag present -> refuses" present 1 "already exists in the registry" "FAKE-DOCKER-REACHED" 9.9.9 --multiarch
run "--push, registry unreachable -> refuses" down 1 "Could not establish whether" "FAKE-DOCKER-REACHED" 9.9.9 --push
run "--push, auth failure -> refuses" denied 1 "Could not establish whether" "FAKE-DOCKER-REACHED" 9.9.9 --push
run "local build, tag present -> not refused" present any "" "already exists in the registry" 9.9.9
echo
if [[ $fails -eq 0 ]]; then echo "all $ran cases behaved"; exit 0; fi
echo "$fails of $ran cases FAILED"; exit 1