diff --git a/REPORT-logins-folders-2026-10-16.md b/REPORT-logins-folders-2026-10-16.md new file mode 100644 index 00000000..f058e322 --- /dev/null +++ b/REPORT-logins-folders-2026-10-16.md @@ -0,0 +1,126 @@ +# REPORT — every app says where its login is · FileBrowser shows its password again · a „Fájlok" button (2026-10-16) + +Written in ASD-STE100 Simplified Technical English. Task: „logins and folders" (operator brief, daytime). +Design read first: `architecture/02-controller-module-map.md` „The app page" (now has „Logins and folders", `[DESIGN]`), +`15-media-library.md` §2 and §11, `CLOSED-ITEMS.md` R-513 and R-75, `app-catalog-felhom.eu/FIRST-ADMIN.md`. +Evidence: `documentation/audits/logins-folders-2026-10-16/` (P0, P1b, live, browser, tools). + +## 0. Decisions I took (inside the brief; each reversible) + +1. **Media folders get no backup sentence.** The brief said „the class-driven backup sentence where a class exists". The + only class there is `excluded`, and its sentence says „this folder is temporary and not backed up". That is false for + the family's films. The class means „not this app's data". So: no sentence (the rule `consequenceFor` already follows + when it cannot be sure). +2. **An infra app has no data tab.** Export, move and the per-app backup are not the household's for a part of the box. + `/apps/filebrowser/data` answers 302 to the page. +3. **A sixth wording inside answer „none".** MeTube and Glance have no login of their own, but the family gate asks for + the family name. „No sign-in needed" is false there. With `family_gate`, „none" says: you sign in with your family + name and password. No new catalog value. +4. **The reset uses FileBrowser's own CLI, not its environment variable** (the variable re-applies at every start). +5. **traefik, cloudflared and samba get no login panel** — nobody signs in to them. + +## 1. Part 0 — why FileBrowser showed „Telepítés" and lost its password + +- FileBrowser is part of the box. It has no `app.yaml`, so the box marks it „not deployed" (and „protected"). +- Every page counted `deployed OR protected` as „on the box". The new app page (v0.315.0, commit `e66d466`) counted + `deployed` only. So it showed the not-installed view: an Install button, no tabs, and no login. The password data was + still there, but only the settings tab shows it, and a not-installed app has no settings tab. +- Worse, found here: the Install button led to a deploy that had **no guard** for a protected stack. A press could have + installed the infra stack as an app. Now refused twice (the deploy API and the deploy itself). +- **State on both boxes:** `operator` on demo-hp (since 2026-09-15) and on the N100 (since 2026-09-16). Nobody knows + either password. +- **Sources:** each box has one drive source (`NVME 1TB` on demo-hp, `Külső tárhely (hdd_1)` on the N100), `Beolvasás` + and `Megőrzött adatok`. The drive source is the drive's `userdata/`, so every `media/` is reachable. + +## 2. What a household sees now (controller 0.320.0) + +- **FileBrowser** is part of the box: running state, „Megnyitás ↗" to `files.`, Restart only, no Install, no + Remove, a two-line guide. Its login: user `admin` and „Megjelenítés". If the box does not know the password: the + sentence „Ezt a jelszót te állítottad be a Fájlkezelőben; a doboz nem ismeri." and the button „Új jelszó + beállítása" (confirm: „A régi jelszó nem fog működni."). +- **Every installed app** has „Belépés az alkalmazásba" with exactly one answer: + - the box made it → „Megjelenítés" (calibre-web, kimai, nextcloud… — every app with a `type: password` field); + - the app wrote it → „Megjelenítés" (crafty-controller; FileBrowser's is this answer too); + - the box never had one → the v0.319.0 sentence; + - first-visit admin → „Az admin fiókot az első megnyitáskor te hozod létre…" (the default: every other app); + - e-mail → rallly, outline; no login → bentopdf, homepage, privatebin, onlyoffice; family sign-in → glance, metube. +- **„Fájlok ↗"** in the header: RomM → `roms`, Calibre-Web → `media/books`, Plex → the first drive's `media`. Immich → + no button (its data is in a named volume). The data tab lists each drive's films and series for Plex, Jellyfin and + Emby, series for Sonarr, films for Radarr, the download folder for qBittorrent and Transmission. A folder on a drive + that is not there now is text, not a link. Under the list: „A Fájlkezelő a saját jelszavát kéri az első alkalommal…" + with a link to the FileBrowser page. + +## 3. Part 1b — the „new password" button + +- **Spike (demo-hp only):** FileBrowser 1.5.6's CLI `set -u admin,` (`backend/cmd/cli.go:206-269`) sets the + password with no old one. The server must be stopped first: the CLI opens the database with no lock timeout + (`backend/database/storage/storage.go:26`). Measured: unknown → K1 200; K1 → K2: K2 200, K1 401, admin/admin 401; a + test user, a test share and the settings unchanged; cleaned up. `P1b/`. +- **Built and pressed on demo-hp through the endpoint the button calls** (`live/fb-reset-press.txt`, + `live/fb-reset-verify.txt`): 200 in 3.4 s; the page then offers „Megjelenítés"; the reveal returns a 16-character + password; a second press is refused (409, the box already knows it). Checked from a second channel, FileBrowser's + own login inside the box: new 200, the old 401, admin/admin 401; users, shares and settings unchanged; state + `generated`. The controller log names the act and never the value. +- **The N100: NOT pressed.** It took 0.320.0 through the normal update path. It waits for the operator's yes in chat. + +## 4. Browser check (headless Chrome through `https://felhom.enkisfelhom.hu`, `browser/`) + +Method: `tools/cdp_logins.py` (the last session's CDP client, extended). FileBrowser, RomM, Calibre-Web and Plex pages +and data tabs; screenshots for each. **The cold „Fájlok" click**, with every cookie cleared first: RomM, Calibre-Web +and Plex each went to `files…/login?redirect=`; after FileBrowser's login each landed ON the folder +(`/files/NVME%201TB/roms`, `…/media/books`, `…/media`). No console error on any page, except the guide tab's missing +screenshot for FileBrowser (404; the section hides itself, as for every app without screenshots). No page wider than +the window. The FileBrowser line's link was dark blue in rc.1 — fixed in rc.2 / the release. + +To be able to show RomM, RomM was installed fresh on demo-hp (its old data kept aside by the product's own „fresh" +choice). It stays for the operator's look. + +## 5. Version, commits, fixtures, rows + +- **felhom-controller** `39bf562` → `f30a808` (the work) → `1296e7a` (the link style) → docs. Test images + `0.320.0-rc.1`, `rc.2` (demo-hp only); release **0.320.0**, MinAgent 0.158.1. +- **app-catalog-felhom.eu** `806d2d0` → `5630979` (`login_kind` on 8 apps, gate `login-kind`). +- **felhom.eu** `bc9a8713` → this commit (design, register, STATUS, evidence). +- **Fixtures replaced (25):** the 24 `app_settings_*` fixtures and `app_data_deployed` (the login panel; the FileBrowser + line). **New (6):** `app_settings_infra_filebrowser`, `app_guide_infra_filebrowser`, `app_settings_login_email`, + `app_settings_login_none`, `app_settings_login_family`, `app_data_files_media`. Every other fixture, the install page + included, byte-identical. +- **Tests, each red-proved** (the mutation and its failure: `audits/logins-folders-2026-10-16/redproof.txt`): + `TestAppPage_InfraAppHasNoInstall`, `_EveryInstalledAppHasOneLoginAnswer`, `_FilesButtonTarget`, + `_UnknownSourceIsTextNotLink`, `TestFileBrowserReset_SetsVerifiesAndStoresEncrypted`, `_FailedVerifyKeepsOperator`, + `_OnlyInOperatorState`, `TestDeployStack_RefusesProtectedStack`; catalog gate `login-kind` (decoys). +- **Register: before 176, after 179, opened 3 (R-1100, R-1101, R-1102), closed 0.** + +**Release (`09` §3 decision 250, `.claude/rules/unprompted-work.md` item 6):** per-box floors 0.320.0 for Tester 1, +demo-hp and the N100; each took 0.320.0 through its normal update path (the N100 and Tester 1 within minutes, demo-hp +at its next 15-minute report). Box checks green on all three (`release/containers.txt`: nothing unhealthy, restarting, +exited or created; `dashboards.txt`: 200 ×3; `https-only.txt`: 35 names → 301, the negative control convicted). Then +the **global floor 0.320.0 / MinAgent 0.158.1** with CC's token (303 `floor_set`), read back from the hub DB with its +WAL (`release/hubdb-floors-after.txt`), and the three per-box floors cleared. On the N100, read only: the FileBrowser +page shows the „operator" sentence and the button (`live/n100-readonly-0.320.0.txt`). + +**System poster:** no listed fact changed (no machine, role, path, tier, time, retention or key) — none of the three +was needed. **`unproven.py --summary`:** not walked 35 of 55, unchanged. + +## 6. Observations + +1. One sign-in for the dashboard and FileBrowser. FILED: R-1100. +2. FileBrowser's stored password goes stale when changed in FileBrowser, and the page says to change it. FILED: R-1101. +3. Eight apps say „starts with a known, shared password" about a generated or typed login. FILED: R-1102 (decision D10). +4. The install button could deploy a protected stack. NOT-A-FINDING: small, fixed in this release with a test. +5. Plex gets the first-visit sentence, but its login is a plex.tv account. NOT-A-FINDING: what the sentence says is + true (the box does not know it; the household makes it at the claim). +6. The build tags every image `latest`, test images too. NOT-A-FINDING: nothing installs or offers `latest` + (searched the hub, the agent and the scripts). +7. The guide tab asks for screenshots FileBrowser does not have (404 in the console). NOT-A-FINDING: the section hides + itself, the same for every app without screenshots. + +## 7. Teardown (three layers) + +- **Machine:** demo-hp guest 9201 — the spike's files (`/root/fbspike`: test passwords as files, the database backup) + removed; the spike's test user and share deleted. RomM installed fresh and **kept** (for the operator's look). + FileBrowser's password on demo-hp is now the box's own (`generated`), shown by „Megjelenítés". The N100: nothing + changed but the normal release update. +- **Host:** demo-hp `/root/fbspike-tmp` and the pushed password files removed. Nothing else. +- **Hub:** the three per-box floors set and cleared; the global floor raised to 0.320.0 / MinAgent 0.158.1 (this is the + release, not a leftover). diff --git a/STATUS.md b/STATUS.md index 27f8281a..0cb63bf1 100644 --- a/STATUS.md +++ b/STATUS.md @@ -6,6 +6,7 @@ `documentation/backlog/STATUS-HISTORY.md`. Reports: `REPORT-night-2026-10-14.md`, `REPORT-immich-rollout-2026-10-14.md`, `REPORT-seo-felho-tarhely-2026-10-14.md`, `REPORT-immich-repair-2026-10-14.md`, `REPORT-power-cut-fixes-2026-10-14.md`, `REPORT-night-power-2026-10-13.md`, `REPORT-poweroff.md`. ## What works +- **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 (proven on demo-hp). - **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`. - 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). @@ -48,8 +49,11 @@ | 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 Fájlkezelő, a RomM és a Plex oldalát a demo-hp-n** (`felhom.enkisfelhom.hu`, controller 0.320.0). A Fájlkezelő a doboz része: nincs Telepítés gomb, a jelszava a „Megjelenítés" gombbal látszik. Minden alkalmazás megmondja, hol a belépése. A „Fájlok ↗" gomb a Fájlkezelőben nyitja az app mappáját. +- **Say yes in chat to press „Új jelszó beállítása" for the file manager on the N100.** The N100 does not know its file-manager password (set by hand in September), so the household cannot open it. The press was proven on demo-hp (3 s; the old password stops working, nothing else changes). If you do nothing: the N100's file manager stays locked, and every „Fájlok" link there ends at a login nobody can pass. - **Look at `roms/roms/` on the N100's drive in the file manager (R-1099).** RomM now scans `roms//`. 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. diff --git a/documentation/architecture/02-controller-module-map.md b/documentation/architecture/02-controller-module-map.md index 28f696bb..0c0e8ac9 100644 --- a/documentation/architecture/02-controller-module-map.md +++ b/documentation/architecture/02-controller-module-map.md @@ -516,6 +516,41 @@ only meet by pressing. The page and the endpoint decide „stored" with ONE func template gained a `type: password` field — `InjectMissingFields` never fills one (`audits/app-page-tuning-2026-10-16/P0`). +#### Logins and folders (v0.320.0, task 2026-10-16 „logins-folders") + +**[DESIGN] A protected infra stack is part of the box, and its app page says so.** FileBrowser (and traefik, +cloudflared, samba) has no `app.yaml`, so `Deployed` is false. Every other surface counts `Deployed || Protected` as +on the box; v0.315.0's app page counted `Deployed` alone and showed FileBrowser „not installed", with „Telepítés" and +without its login (measured on both demo boxes, `audits/logins-folders-2026-10-16/P0`). The app page now treats a +protected stack as installed: settings, guide and log tabs; Restart only (as the app list); the curated name +(`inframeta.go`); no data tab and no install page (both 302 to the page). The deploy API and `DeployStack` refuse a +protected stack — until then the Install press would have deployed the infra compose as an app. + +**[DESIGN] Every installed app says where its login is — ONE answer.** The settings tab's „Belépés az alkalmazásba" +panel is on every installed app with exactly one of: the box's login fields (on demand; a field with no stored value +is the v0.319.0 sentence) · the login the app wrote, or FileBrowser's (`InitialCreds`) · a sentence chosen by the +catalog's `app_info.login_kind` — `first_visit` (the default: „you create the admin on the first visit; the box does +not know the password"), `email` (a sign-in link), `none` (no sign-in; behind `family_gate` the sentence says the +family sign-in is the login). `login_kind` is an enum, not copy: the sentence is the controller's. An infra stack with +no login of its own (traefik, cloudflared, samba) gets no panel. Code: `web/app_login.go` `loginAnswerFor`. + +**[DESIGN] „Új jelszó beállítása" — FileBrowser's password when the box does not know it.** In R-513's `operator` +state (both demo boxes) the panel says „Ezt a jelszót te állítottad be a Fájlkezelőben; a doboz nem ismeri." and offers +the press. The box uses FileBrowser's own CLI (`set -u admin,`) with the server STOPPED (the CLI opens the +database with no lock timeout), verifies new=200 and admin/admin refused, stores the value encrypted and records +`generated`; a failed verify leaves `operator`. Not the `FILEBROWSER_ADMIN_PASSWORD` env: it re-applies on every +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] „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 +`movies`, the players both, a download client its `media_library.folder`) with NO backup sentence — the bind's class +there means „not this app's data", which is not the class-driven „temporary, not backed up". **A folder whose drive +is not bound into FileBrowser now is text, never a link** (`drivePresent` = the same gates the FileBrowser sync +applies). The button never promises one click: FileBrowser's router brings a cold visitor back to the folder after its +login (measured in the headless browser, `audits/logins-folders-2026-10-16/browser/`). + **[DESIGN] „Cím és kulcsok" is rows, not the install form.** The settings tab does not re-render the install wizard's fields: the address is the „Cím" row, the storage is a „Tárhely: " row, every other install answer and generated key is a row (keys keep their eye). The read-only hint sits under diff --git a/documentation/audits/logins-folders-2026-10-16/P0/P0-state.txt b/documentation/audits/logins-folders-2026-10-16/P0/P0-state.txt new file mode 100644 index 00000000..03a7f3f5 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/P0/P0-state.txt @@ -0,0 +1,237 @@ +=== demo-hp +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch +9401 stopped upgrade-harness +felhom-controller gitea.dooplex.hu/admin/felhom-controller:0.319.0 +filebrowser gtstef/filebrowser:1.5.6-stable +SETTINGS: +config.yaml +docker-compose.yml + - path: "/srv/beolvasas" + name: "Beolvasás" + - path: "/srv/hdd_1" + name: "NVME 1TB" + - path: "/srv/megorzott" + name: "Megőrzött adatok" + - /mnt/felhom-drives/hdd_1/userdata:/srv/hdd_1 + - /mnt/sys_drive/felhom-data/userdata/import:/srv/beolvasas + - /mnt/felhom-drives/hdd_1/appdata/romm:/srv/megorzott/hdd_1-romm:ro + - /mnt/felhom-drives/hdd_1/kept/romm/2026-10-16_120728:/srv/megorzott/hdd_1-romm-2026-10-16_120728:ro + - /mnt/felhom-drives/hdd_1/kept/romm/2026-10-16_113539:/srv/megorzott/hdd_1-romm-2026-10-16_113539:ro +=== felhom-pve +VMID Status Lock Name +9201 running demo-felhom +felhom-controller gitea.dooplex.hu/admin/felhom-controller:0.319.0 +filebrowser gtstef/filebrowser:1.5.6-stable +SETTINGS: +config.yaml +docker-compose.yml + - path: "/srv/beolvasas" + name: "Beolvasás" + - path: "/srv/hdd_1" + name: "Külső tárhely (hdd_1)" + - path: "/srv/megorzott" + name: "Megőrzött adatok" + - /mnt/felhom-drives/hdd_1/userdata:/srv/hdd_1 + - /mnt/sys_drive/felhom-data/userdata/import:/srv/beolvasas + - /mnt/felhom-drives/hdd_1/appdata/romm:/srv/megorzott/hdd_1-romm:ro + - /mnt/felhom-drives/hdd_1/appdata/paperless:/srv/megorzott/hdd_1-paperless:ro + - /mnt/felhom-drives/hdd_1/appdata/nextcloud:/srv/megorzott/hdd_1-nextcloud:ro + - /mnt/felhom-drives/hdd_1/appdata/immich:/srv/megorzott/hdd_1-immich:ro +=== demo-hp +/mnt->/mnt /etc/felhom-bootstrap->/etc/felhom-bootstrap /var/lib/docker/volumes/felhom-controller-data/_data->/opt/docker/felhom-controller /opt/docker/stacks->/opt/docker/stacks /var/run/docker.sock->/var/run/docker.sock + +/mnt/felhom-drives/hdd_1/userdata/: +documents +downloads +import +media +roms +roms-bios + +/mnt/felhom-drives/hdd_1/userdata/media/: +audiobooks +books +comics +grimmory +metube +movies +music +nzbget +photos +podcasts +torrents +transmission +tv +usenet +actualbudget +adventurelog +audiobookshelf +bentopdf +bookstack +calcom +calibre-web +claper +cloudflared +code-server +crafty-controller +dawarich +docmost +emby +filebrowser +ghost +gitea +glance +gokapi +grafana +gramps-web +grimmory +grocy +home-assistant +homebox +homepage +immich +jellyfin +karakeep +kimai +komga +lubelogger +mealie +metube +n8n +navidrome +nextcloud +nzbget +onlyoffice +opengist +outline +paperless-ngx +papra +plant-it +plex +privatebin +prowlarr +qbittorrent +radarr +radicale +rallly +recipe-importer +romm +sabnzbd +seerr +sonarr +sparkyfitness +tandoor +termix +traefik +transmission +uptime-kuma +vaultwarden +vikunja +wanderer +wger +wishlist +zipline +=== felhom-pve +/mnt->/mnt /etc/felhom-bootstrap->/etc/felhom-bootstrap /var/lib/docker/volumes/felhom-controller-data/_data->/opt/docker/felhom-controller /opt/docker/stacks->/opt/docker/stacks /var/run/docker.sock->/var/run/docker.sock + +/mnt/felhom-drives/hdd_1/userdata/: +documents +downloads +import +media +roms +roms-bios + +/mnt/felhom-drives/hdd_1/userdata/media/: +audiobooks +books +comics +grimmory +metube +movies +music +nzbget +photos +podcasts +torrents +transmission +tv +usenet +actualbudget +adventurelog +audiobookshelf +bentopdf +bookstack +calcom +calibre-web +claper +cloudflared +code-server +crafty-controller +dawarich +docmost +emby +filebrowser +ghost +gitea +glance +gokapi +grafana +gramps-web +grimmory +grocy +home-assistant +homebox +homepage +immich +jellyfin +karakeep +kimai +komga +lubelogger +mealie +metube +n8n +navidrome +nextcloud +nzbget +onlyoffice +opengist +outline +paperless-ngx +papra +plant-it +plex +privatebin +prowlarr +qbittorrent +radarr +radicale +rallly +recipe-importer +romm +sabnzbd +seerr +sonarr +sparkyfitness +tandoor +termix +traefik +transmission +uptime-kuma +vaultwarden +vikunja +wanderer +wger +wishlist +zipline +=== demo-hp +FILE /var/lib/docker/volumes/felhom-controller-data/_data/data/settings.json +"filebrowser_admin_state": "operator" +"filebrowser_admin_decided_at": "2026-09-15T08:52:10Z" +=== felhom-pve +FILE /var/lib/docker/volumes/felhom-controller-data/_data/data/settings.json +"filebrowser_admin_state": "operator" +"filebrowser_admin_decided_at": "2026-09-16T15:37:26Z" diff --git a/documentation/audits/logins-folders-2026-10-16/P0/README.md b/documentation/audits/logins-folders-2026-10-16/P0/README.md new file mode 100644 index 00000000..fe5c03a5 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/P0/README.md @@ -0,0 +1,48 @@ +# P0 — why FileBrowser showed „Telepítés" and lost its password (2026-10-16, read only) + +Method: source read (controller `main` 39bf562 = v0.319.0) + the live page through +`https://felhom.enkisfelhom.hu` (curl, logged in) + names/states read with `pct exec 9201` on both boxes. +No value of any secret was read or printed; only the state word of `filebrowser_admin_state`. + +## 1. Why `/apps/filebrowser` renders as not installed, with an Install button + +- FileBrowser is base infrastructure: `stacks/infra.go` `EnsureBaseStack` writes `/opt/docker/stacks/filebrowser/` + (compose + config.yaml) and starts it. It never writes `app.yaml`. +- `stacks/manager.go` `ScanStacks` lists every dir under the stacks dir with a compose file. FileBrowser is in the list + with `Deployed = appCfg != nil && appCfg.Deployed` = **false** (no app.yaml) and `Protected = true`. + Its metadata is the no-`.felhom.yml` default (`Slug = "filebrowser"`, DisplayName „Filebrowser"). +- Every other surface already counts `Deployed || Protected` as „on the box" (dashboard, launcher, app list — + `handlers.go` ~154/315/361, `stacks.html` L30/L76). **The v0.315.0 app page (`e66d466`) uses `found.Deployed` only** + (`web/app_page.go` `appPageHandler`): not deployed → no tab bar, the guide tab is the whole page, and the guide + renders the Install CTA (`app_guide.html` L45, `canInstall` is true for metadata with no lifecycle). +- The R-513 block in `appCommonData` (handlers.go ~L856) still sets `InitialCreds` for FileBrowser — but the new + page shows `InitialCreds` only on the SETTINGS tab, which a „not deployed" app never reaches. The old + `app_info.html` rendered `{{if .InitialCreds}}` without a Deployed gate; that is how it showed before. +- **Commit that changed it: `e66d466` (v0.315.0, one app page with four tabs).** +- Extra finding: `DeployStack` has NO protected-stack guard (it refuses only Deployed / not installable). The Install + button led to a wizard whose POST would have deployed the infra stack as an app. Guarded in this task. + +Live confirmation (demo-hp 9201, controller 0.319.0): `demo-hp-apps-filebrowser-markers.txt` — the page carries +`data-install-cta`, „Telepítés", no `data-tab`, no `initcred`. + +## 2. FileBrowser admin state (state word only) — `P0-state.txt` + +| box | state | decided at | +|---|---|---| +| demo-hp 9201 | `operator` | 2026-09-15T08:52:10Z | +| demo-felhom 9201 (N100, lived-in) | `operator` | 2026-09-16T15:37:26Z | + +Both boxes: nobody knows the password (R-513 found admin/admin refused and left it). + +## 3. FileBrowser sources (rendered `config.yaml`) — `P0-state.txt` + +| box | drive source | import | kept | +|---|---|---|---| +| demo-hp | `NVME 1TB` → `/srv/hdd_1` = `/mnt/felhom-drives/hdd_1/userdata` | `Beolvasás` | `Megőrzött adatok` | +| demo-felhom | `Külső tárhely (hdd_1)` → `/srv/hdd_1` = `/mnt/felhom-drives/hdd_1/userdata` | `Beolvasás` | `Megőrzött adatok` | + +Each box has ONE drive. The drive source root is the drive's `userdata/`, so `media/` and every +`media/` folder is reachable as `/media/` — measured: the +folders exist on both boxes (`ls /mnt/felhom-drives/hdd_1/userdata/media/`). Source names come from the storage +path's label, else the mount basename — the same rule `fbSourceNameForApp` uses, so the link builder and the +sidebar agree by construction. diff --git a/documentation/audits/logins-folders-2026-10-16/P0/demo-hp-apps-filebrowser-markers.txt b/documentation/audits/logins-folders-2026-10-16/P0/demo-hp-apps-filebrowser-markers.txt new file mode 100644 index 00000000..72a03a9e --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/P0/demo-hp-apps-filebrowser-markers.txt @@ -0,0 +1,5 @@ + 1 /install" class="btn btn-primary" data-install-cta="1" + 1 Telepítés + 1 Telepítés gombbal. + 1 Telepítési konfiguráció ( + 1 app-hero-name">Filebrowser diff --git a/documentation/audits/logins-folders-2026-10-16/P1b/README.md b/documentation/audits/logins-folders-2026-10-16/P1b/README.md new file mode 100644 index 00000000..46cbf858 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/P1b/README.md @@ -0,0 +1,33 @@ +# P1b — resetting FileBrowser's admin password WITHOUT the old one (spike, demo-hp guest 9201 only, 2026-10-16) + +**Verdict: a safe reset exists. Built.** + +## The mechanism, read at the pinned tag + +`gtstef/filebrowser:1.5.6-stable` = `gtsteffaniak/filebrowser` tag `v1.5.6-stable` (`5d9b4df`). + +- **CLI `filebrowser set -u , -c `** — `backend/cmd/cli.go:60-67` (the flag set), `:117-126` + (dispatch), `:206-269` `setUser`: an existing user gets `user.Password = password`, its TOTP secret cleared, and + `store.Users.Save(user, true, false)` (hashes the password). It needs NO old password. +- It opens the database with `storm.Open(path)` (`backend/database/storage/storage.go:26`) with no timeout: while the + server runs, the bolt lock is held and the CLI would wait forever. So the server is STOPPED for the CLI, and the CLI + runs in a one-shot container with the running container's volumes (`docker run --rm --volumes-from filebrowser`). +- Not chosen: `FILEBROWSER_ADMIN_PASSWORD` / `auth.adminPassword` (`backend/common/settings/config.go:923-926`, + applied in `backend/cmd/user.go:50-57` `validateUserInfo`) — it re-applies on EVERY start (R-513 measured the same on + 1.3.3), so it would need a compose change plus two recreates, and the value would sit in the container env. + +## Measured (`spike-step1.txt`, `spike-step2.txt`, `spike-step3.txt`, `spike-cleanup.txt`; script `fbspike.sh`) + +1. State `operator`, password unknown (R-513 left it 2026-09-15). A DB backup copied first. +2. Reset to K1 with the server stopped: CLI rc 0, „successfully updated user", healthy 7 s after the stop; **K1 → 200**. +3. A test user and a test share made as K1 (user 201, share 200). Settings hash `12a6170ac405fa72`. +4. Reset to K2 without K1: **K2 → 200, K1 → 401, admin/admin → 401**; users `admin` + `fbspike`, the share, and + the settings hash **unchanged**. +5. Test user and share deleted (200/200). + +Passwords were generated on DooPlex (16 letters and digits — never a comma, which the CLI's `user,password` form would +split on), sent as files (0600), used from files, and never printed. The CLI carries the password on its command line +for about one second inside the guest; this is the only exposure and it is inside the household's own box. + +After the spike demo-hp's FileBrowser password is K2 (kept out-of-band in the session scratchpad) and the controller +still records `operator` — the built button replaces it (Part 1b build). diff --git a/documentation/audits/logins-folders-2026-10-16/P1b/fbspike.sh b/documentation/audits/logins-folders-2026-10-16/P1b/fbspike.sh new file mode 100644 index 00000000..8ba54e40 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/P1b/fbspike.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# P1b spike — runs INSIDE demo-hp guest 9201. Passwords come from files in $D (0600); never printed. +# Usage: fbspike.sh steps: backup | reset | login | inventory | seed | cleanup +set -u +D=/root/fbspike +IMG=gtstef/filebrowser:1.5.6-stable +IP=$(docker inspect filebrowser --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 2>/dev/null) +B="http://$IP:80" +hdr() { printf 'X-Password: %s\n' "$(cat "$1")" > $D/.h; chmod 600 $D/.h; } +login_code() { hdr "$1"; curl -s -o $D/.tok -w '%{http_code}' -X POST -H @$D/.h "$B/api/auth/login?username=admin"; rm -f $D/.h; } +tok() { login_code "$1" >/dev/null; tr -d '"' < $D/.tok; } +case "$1" in +backup) + V=$(docker volume inspect filebrowser_filebrowser_data --format '{{.Mountpoint}}') + cp -a "$V/database.db" $D/database.db.before-spike && ls -l $D/database.db.before-spike | awk '{print "backup bytes",$5}' + ;; +reset) + t0=$(date +%s) + docker stop filebrowser >/dev/null && echo "stopped" + P=$(cat "$2") + timeout 60 docker run --rm --volumes-from filebrowser --user 1000:1000 --workdir /home/filebrowser \ + --entrypoint /home/filebrowser/filebrowser "$IMG" set -u "admin,$P" -c /home/filebrowser/config.yaml >$D/.cli 2>&1 + rc=$?; unset P + echo "cli rc=$rc"; grep -o 'successfully [a-z]* user[^ ]*' $D/.cli; rm -f $D/.cli + docker start filebrowser >/dev/null && echo "started" + for i in $(seq 1 60); do s=$(docker inspect filebrowser --format '{{.State.Health.Status}}'); [ "$s" = healthy ] && break; sleep 2; done + IP=$(docker inspect filebrowser --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'); echo "health=$s after $(( $(date +%s) - t0 ))s" + ;; +login) + echo "login $(basename $2): $(login_code "$2")"; rm -f $D/.tok + ;; +inventory) + T=$(tok "$2") + echo "users:"; curl -s -H "Authorization: Bearer $T" "$B/api/users" | python3 -c 'import json,sys;[print(" ",u.get("id"),u.get("username"),"admin" if u.get("permissions",{}).get("admin") else "-") for u in json.load(sys.stdin)]' + echo "shares:"; curl -s -H "Authorization: Bearer $T" "$B/api/share/list" | python3 -c 'import json,sys;d=json.load(sys.stdin);d=d if isinstance(d,list) else [];[print(" ",s.get("hash"),s.get("path"),s.get("source")) for s in d];print(" count",len(d))' + echo "settings:"; curl -s -H "Authorization: Bearer $T" "$B/api/settings" | python3 -c 'import json,sys,hashlib;d=json.load(sys.stdin);d.get("auth",{}).pop("key",None);print(" sources",[x.get("name") for x in d.get("server",{}).get("sources",[])]);print(" sha256",hashlib.sha256(json.dumps(d,sort_keys=True).encode()).hexdigest()[:16])' + rm -f $D/.tok + ;; +seed) + T=$(tok "$2") + hdr "$2"; curl -s -o /dev/null -w 'create user %{http_code}\n' -X POST -H @$D/.h -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \ + -d '{"which":[],"data":{"username":"fbspike","password":"fbspike-'"$(head -c6 /dev/urandom | od -An -tx1 | tr -d ' \n')"'","loginMethod":"password","scopes":[]}}' "$B/api/users" + rm -f $D/.h; [ -n "${3:-}" ] && { rm -f $D/.tok; exit 0; } + curl -s -o /dev/null -w 'create share %{http_code}\n' -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \ + -d '{"path":"/media","source":"NVME 1TB","expires":"","unit":"hours","password":null}' "$B/api/share" + rm -f $D/.tok + ;; +probe) + T=$(tok "$2"); echo "toklen ${#T}" + for p in api/users api/shares api/settings "api/users?id=self"; do + curl -s -o $D/.o -w "$p %{http_code} " -H "Authorization: Bearer $T" "$B/$p" + head -c 160 $D/.o | sed 's/"password":"[^"]*"/"password":"REDACTED"/g'; echo + done + rm -f $D/.o $D/.tok + ;; +cleanup) + T=$(tok "$2") + id=$(curl -s -H "Authorization: Bearer $T" "$B/api/users" | python3 -c 'import json,sys;print(next((u["id"] for u in json.load(sys.stdin) if u["username"]=="fbspike"),""))') + hdr "$2"; [ -n "$id" ] && curl -s -o /dev/null -w "delete user %{http_code}\n" -X DELETE -H @$D/.h -H "Authorization: Bearer $T" "$B/api/users?id=$id"; rm -f $D/.h + for h in $(curl -s -H "Authorization: Bearer $T" "$B/api/share/list" | python3 -c 'import json,sys;d=json.load(sys.stdin);d=d if isinstance(d,list) else [];[print(s["hash"]) for s in d if s.get("path","").rstrip("/")=="/media"]'); do + curl -s -o /dev/null -w "delete share %{http_code}\n" -X DELETE -H "Authorization: Bearer $T" "$B/api/share?hash=$h"; done + rm -f $D/.tok + ;; +esac diff --git a/documentation/audits/logins-folders-2026-10-16/P1b/spike-cleanup.txt b/documentation/audits/logins-folders-2026-10-16/P1b/spike-cleanup.txt new file mode 100644 index 00000000..d5fa9306 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/P1b/spike-cleanup.txt @@ -0,0 +1,10 @@ +## 2026-10-16T12:17:12Z cleanup: the test user and the test share +delete user 200 +delete share 200 +users: + 1 admin admin +shares: + count 0 +settings: + sources ['Beolvasás', 'NVME 1TB', 'Megőrzött adatok'] + sha256 12a6170ac405fa72 diff --git a/documentation/audits/logins-folders-2026-10-16/P1b/spike-step1.txt b/documentation/audits/logins-folders-2026-10-16/P1b/spike-step1.txt new file mode 100644 index 00000000..c5e6c3de --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/P1b/spike-step1.txt @@ -0,0 +1,33 @@ +## 2026-10-16T12:14:17Z step 1: state operator, password unknown; admin/admin and K1 before +login K1: 401 +## reset to K1 with the CLI, server stopped +stopped +cli rc=0 +successfully updated user: +started +health=healthy after 7s +login K1: 200 +users: +Traceback (most recent call last): + File "", line 1, in + import json,sys;[print(" ",u.get("id"),u.get("username"),"admin" if u.get("permissions",{}).get("admin") else "-") for u in json.load(sys.stdin)] + ^^^^^ +AttributeError: 'str' object has no attribute 'get' +shares: +Traceback (most recent call last): + File "", line 1, in + import json,sys;d=json.load(sys.stdin);d=d if isinstance(d,list) else [];[print(" ",s.get("hash"),s.get("path"),s.get("source")) for s in d];print(" count",len(d)) + ~~~~~~~~~^^^^^^^^^^^ + File "/usr/lib/python3.13/json/__init__.py", line 293, in load + return loads(fp.read(), + cls=cls, object_hook=object_hook, + parse_float=parse_float, parse_int=parse_int, + parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw) + File "/usr/lib/python3.13/json/__init__.py", line 346, in loads + return _default_decoder.decode(s) + ~~~~~~~~~~~~~~~~~~~~~~~^^^ + File "/usr/lib/python3.13/json/decoder.py", line 348, in decode + raise JSONDecodeError("Extra data", s, end) +json.decoder.JSONDecodeError: Extra data: line 1 column 5 (char 4) +settings sources: + keys ['message', 'status'] diff --git a/documentation/audits/logins-folders-2026-10-16/P1b/spike-step2.txt b/documentation/audits/logins-folders-2026-10-16/P1b/spike-step2.txt new file mode 100644 index 00000000..771ac49b --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/P1b/spike-step2.txt @@ -0,0 +1,22 @@ +## 2026-10-16T12:16:26Z step 2: seed a user and a share as K1, inventory +create user 401 +create share 200 +users: + 1 admin admin +shares: + aoSyMglz7IyGgR9e_WVg4Q /media/ NVME 1TB + count 1 +settings: + sources ['Beolvasás', 'NVME 1TB', 'Megőrzött adatok'] + sha256 12a6170ac405fa72 +## 2026-10-16T12:16:43Z step 2b: seed the user (with X-Password), inventory +create user 201 +users: + 1 admin admin + 2 fbspike - +shares: + aoSyMglz7IyGgR9e_WVg4Q /media/ NVME 1TB + count 1 +settings: + sources ['Beolvasás', 'NVME 1TB', 'Megőrzött adatok'] + sha256 12a6170ac405fa72 diff --git a/documentation/audits/logins-folders-2026-10-16/P1b/spike-step3.txt b/documentation/audits/logins-folders-2026-10-16/P1b/spike-step3.txt new file mode 100644 index 00000000..060fe580 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/P1b/spike-step3.txt @@ -0,0 +1,19 @@ +## 2026-10-16T12:16:51Z step 3: reset to K2 without K1 (CLI, server stopped) +stopped +cli rc=0 +successfully updated user: +started +health=healthy after 7s +login K2: 200 +login K1: 401 +admin/admin: +login ADM: 401 +users: + 1 admin admin + 2 fbspike - +shares: + aoSyMglz7IyGgR9e_WVg4Q /media/ NVME 1TB + count 1 +settings: + sources ['Beolvasás', 'NVME 1TB', 'Megőrzött adatok'] + sha256 12a6170ac405fa72 diff --git a/documentation/audits/logins-folders-2026-10-16/browser/apps-calibre-web-data.png b/documentation/audits/logins-folders-2026-10-16/browser/apps-calibre-web-data.png new file mode 100644 index 00000000..a4e6cc1a Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/apps-calibre-web-data.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/apps-calibre-web.png b/documentation/audits/logins-folders-2026-10-16/browser/apps-calibre-web.png new file mode 100644 index 00000000..80fd5df0 Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/apps-calibre-web.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/apps-filebrowser-guide.png b/documentation/audits/logins-folders-2026-10-16/browser/apps-filebrowser-guide.png new file mode 100644 index 00000000..1024264d Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/apps-filebrowser-guide.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/apps-filebrowser.png b/documentation/audits/logins-folders-2026-10-16/browser/apps-filebrowser.png new file mode 100644 index 00000000..0e75ee0e Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/apps-filebrowser.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/apps-plex-data.png b/documentation/audits/logins-folders-2026-10-16/browser/apps-plex-data.png new file mode 100644 index 00000000..00789910 Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/apps-plex-data.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/apps-plex.png b/documentation/audits/logins-folders-2026-10-16/browser/apps-plex.png new file mode 100644 index 00000000..4544c9a0 Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/apps-plex.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/apps-romm-data.png b/documentation/audits/logins-folders-2026-10-16/browser/apps-romm-data.png new file mode 100644 index 00000000..b10db99a Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/apps-romm-data.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/apps-romm.png b/documentation/audits/logins-folders-2026-10-16/browser/apps-romm.png new file mode 100644 index 00000000..b6991d73 Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/apps-romm.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/cold-calibre-web-1-login.png b/documentation/audits/logins-folders-2026-10-16/browser/cold-calibre-web-1-login.png new file mode 100644 index 00000000..a6adf562 Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/cold-calibre-web-1-login.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/cold-calibre-web-2-after-login.png b/documentation/audits/logins-folders-2026-10-16/browser/cold-calibre-web-2-after-login.png new file mode 100644 index 00000000..34dac0b4 Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/cold-calibre-web-2-after-login.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/cold-plex-1-login.png b/documentation/audits/logins-folders-2026-10-16/browser/cold-plex-1-login.png new file mode 100644 index 00000000..282f5dc4 Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/cold-plex-1-login.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/cold-plex-2-after-login.png b/documentation/audits/logins-folders-2026-10-16/browser/cold-plex-2-after-login.png new file mode 100644 index 00000000..81104ab2 Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/cold-plex-2-after-login.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/cold-romm-1-login.png b/documentation/audits/logins-folders-2026-10-16/browser/cold-romm-1-login.png new file mode 100644 index 00000000..282f5dc4 Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/cold-romm-1-login.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/cold-romm-2-after-login.png b/documentation/audits/logins-folders-2026-10-16/browser/cold-romm-2-after-login.png new file mode 100644 index 00000000..a3bc9493 Binary files /dev/null and b/documentation/audits/logins-folders-2026-10-16/browser/cold-romm-2-after-login.png differ diff --git a/documentation/audits/logins-folders-2026-10-16/browser/report.json b/documentation/audits/logins-folders-2026-10-16/browser/report.json new file mode 100644 index 00000000..3b4e1b45 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/browser/report.json @@ -0,0 +1,219 @@ +after login url: /launcher +{ + "/apps/filebrowser": { + "path": "/apps/filebrowser", + "title": "FileBrowser", + "tabs": [ + "settings", + "guide", + "logs" + ], + "install": false, + "loginAnswer": [ + "initial" + ], + "loginKind": [], + "fbOperator": false, + "fbReset": false, + "reveal": true, + "open": "https://files.enkisfelhom.hu/", + "files": null, + "folders": [], + "fbNote": false, + "scrollWidth": 1280, + "console": [] + }, + "/apps/filebrowser/guide": { + "path": "/apps/filebrowser/guide", + "title": "FileBrowser", + "tabs": [ + "settings", + "guide", + "logs" + ], + "install": false, + "loginAnswer": [], + "loginKind": [], + "fbOperator": false, + "fbReset": false, + "reveal": false, + "open": "https://files.enkisfelhom.hu/", + "files": null, + "folders": [], + "fbNote": false, + "scrollWidth": 1280, + "console": [ + [ + "log-error", + "Failed to load resource: the server responded with a status of 404 ()", + "https://felhom.enkisfelhom.hu/static/assets/filebrowser-screenshot-3.webp" + ] + ] + }, + "/apps/romm": { + "path": "/apps/romm", + "title": "RomM", + "tabs": [ + "settings", + "data", + "guide", + "logs" + ], + "install": false, + "loginAnswer": [ + "first_visit" + ], + "loginKind": [ + "first_visit" + ], + "fbOperator": false, + "fbReset": false, + "reveal": false, + "open": "https://arcade.enkisfelhom.hu/", + "files": "https://files.enkisfelhom.hu/files/NVME%201TB/roms", + "folders": [], + "fbNote": false, + "scrollWidth": 1270, + "console": [] + }, + "/apps/romm/data": { + "path": "/apps/romm/data", + "title": "RomM", + "tabs": [ + "settings", + "data", + "guide", + "logs" + ], + "install": false, + "loginAnswer": [], + "loginKind": [], + "fbOperator": false, + "fbReset": false, + "reveal": false, + "open": "https://arcade.enkisfelhom.hu/", + "files": "https://files.enkisfelhom.hu/files/NVME%201TB/roms", + "folders": [ + "https://files.enkisfelhom.hu/files/NVME%201TB/roms", + "https://files.enkisfelhom.hu/files/NVME%201TB/roms-bios" + ], + "fbNote": true, + "scrollWidth": 1270, + "console": [] + }, + "/apps/calibre-web": { + "path": "/apps/calibre-web", + "title": "Calibre-Web Automated", + "tabs": [ + "settings", + "data", + "guide", + "logs" + ], + "install": false, + "loginAnswer": [ + "fields" + ], + "loginKind": [], + "fbOperator": false, + "fbReset": false, + "reveal": false, + "open": "https://books.enkisfelhom.hu/", + "files": "https://files.enkisfelhom.hu/files/NVME%201TB/media/books", + "folders": [], + "fbNote": false, + "scrollWidth": 1270, + "console": [] + }, + "/apps/calibre-web/data": { + "path": "/apps/calibre-web/data", + "title": "Calibre-Web Automated", + "tabs": [ + "settings", + "data", + "guide", + "logs" + ], + "install": false, + "loginAnswer": [], + "loginKind": [], + "fbOperator": false, + "fbReset": false, + "reveal": false, + "open": "https://books.enkisfelhom.hu/", + "files": "https://files.enkisfelhom.hu/files/NVME%201TB/media/books", + "folders": [ + "https://files.enkisfelhom.hu/files/Beolvas%C3%A1s/calibre", + "https://files.enkisfelhom.hu/files/NVME%201TB/media/books" + ], + "fbNote": true, + "scrollWidth": 1270, + "console": [] + }, + "/apps/plex": { + "path": "/apps/plex", + "title": "Plex", + "tabs": [ + "settings", + "data", + "guide", + "logs" + ], + "install": false, + "loginAnswer": [ + "first_visit" + ], + "loginKind": [ + "first_visit" + ], + "fbOperator": false, + "fbReset": false, + "reveal": false, + "open": "https://plex.enkisfelhom.hu/", + "files": "https://files.enkisfelhom.hu/files/NVME%201TB/media", + "folders": [], + "fbNote": false, + "scrollWidth": 1270, + "console": [] + }, + "/apps/plex/data": { + "path": "/apps/plex/data", + "title": "Plex", + "tabs": [ + "settings", + "data", + "guide", + "logs" + ], + "install": false, + "loginAnswer": [], + "loginKind": [], + "fbOperator": false, + "fbReset": false, + "reveal": false, + "open": "https://plex.enkisfelhom.hu/", + "files": "https://files.enkisfelhom.hu/files/NVME%201TB/media", + "folders": [ + "https://files.enkisfelhom.hu/files/NVME%201TB/media/movies", + "https://files.enkisfelhom.hu/files/NVME%201TB/media/tv" + ], + "fbNote": true, + "scrollWidth": 1270, + "console": [] + }, + "cold:romm": { + "target": "https://files.enkisfelhom.hu/files/NVME%201TB/roms", + "first": "/login?redirect=/files/NVME%25201TB/roms", + "after_login": "/files/NVME%201TB/roms" + }, + "cold:calibre-web": { + "target": "https://files.enkisfelhom.hu/files/NVME%201TB/media/books", + "first": "/login?redirect=/files/NVME%25201TB/media/books", + "after_login": "/files/NVME%201TB/media/books" + }, + "cold:plex": { + "target": "https://files.enkisfelhom.hu/files/NVME%201TB/media", + "first": "/login?redirect=/files/NVME%25201TB/media", + "after_login": "/files/NVME%201TB/media" + } +} diff --git a/documentation/audits/logins-folders-2026-10-16/live/endpoints-rc1.txt b/documentation/audits/logins-folders-2026-10-16/live/endpoints-rc1.txt new file mode 100644 index 00000000..c04df47a --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/live/endpoints-rc1.txt @@ -0,0 +1,16 @@ +200 /apps/filebrowser :: 1 data-fb-operator 1 data-fb-reset 1 data-login-answer="initial" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings" +302 /apps/filebrowser /apps/filebrowser/install :: +302 /apps/filebrowser /apps/filebrowser/data :: +200 /apps/filebrowser/guide :: 1 data-infra-guide 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings" +200 /apps/romm :: 1 data-install-cta +302 /apps/romm /apps/romm/data :: +200 /apps/calibre-web :: 1 data-app-files="1" 1 data-login-answer="fields" 1 data-tab="data" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings" href="https://files.enkisfelhom.hu/files/NVME%201TB/media/books" target="_blank" rel="noopener" class="btn btn-outline" data-app-files + +200 /apps/calibre-web/data :: 1 data-app-files="1" 1 data-fb-login-note 1 data-tab="data" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings" href="https://files.enkisfelhom.hu/files/NVME%201TB/media/books" target="_blank" rel="noopener" class="btn btn-outline" data-app-files + +200 /apps/plex :: 1 data-app-files="1" 1 data-login-answer="first_visit" 1 data-login-kind="first_visit" 1 data-tab="data" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings" href="https://files.enkisfelhom.hu/files/NVME%201TB/media" target="_blank" rel="noopener" class="btn btn-outline" data-app-files + +200 /apps/plex/data :: 1 data-app-files="1" 1 data-fb-login-note 2 data-media-folder 1 data-tab="data" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings" href="https://files.enkisfelhom.hu/files/NVME%201TB/media" target="_blank" rel="noopener" class="btn btn-outline" data-app-files + +200 /apps/immich :: 1 data-install-cta +200 /apps/bentopdf :: 1 data-login-answer="first_visit" 1 data-login-kind="first_visit" 1 data-tab="data" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings" diff --git a/documentation/audits/logins-folders-2026-10-16/live/fb-reset-press.txt b/documentation/audits/logins-folders-2026-10-16/live/fb-reset-press.txt new file mode 100644 index 00000000..504a2fa8 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/live/fb-reset-press.txt @@ -0,0 +1,12 @@ +## 2026-10-16T13:26:04Z press reset (POST /apps/filebrowser/initial-credentials/reset) +{"data":null,"error":"","ok":true} + HTTP 200 in 3.362612s +## the page after + 1 data-login-answer="initial" + 1 id="initcred-reveal" +## reveal (value kept out of this file; length only) +HTTP 200 +ok True len 16 +## a second press (now generated) must be refused +{"data":null,"error":"A Fájlkezelő jelszavát a doboz már ismeri — a Megjelenítés gomb mutatja.","ok":false} + HTTP 409 diff --git a/documentation/audits/logins-folders-2026-10-16/live/fb-reset-verify.txt b/documentation/audits/logins-folders-2026-10-16/live/fb-reset-verify.txt new file mode 100644 index 00000000..9f7be487 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/live/fb-reset-verify.txt @@ -0,0 +1,20 @@ +login NEWPW: 200 +login K2: 401 +login ADM: 401 +users: + 1 admin admin +shares: + count 0 +settings: + sources ['Beolvasás', 'NVME 1TB', 'Megőrzött adatok'] + sha256 12a6170ac405fa72 +e920c91f524e filebrowser filebrowser gtstef/filebrowser:1.5.6-stable +2026/10/16 13:20:19 handlers.go:3869: [INFO] [web] FileBrowser sync — no config/compose change, ensured running without recreate (1 storage path(s)) +e920c91f524e filebrowser filebrowser gtstef/filebrowser:1.5.6-stable +2026/10/16 13:26:04 handlers.go:2742: [INFO] [web] filebrowser: new admin password requested from 37.191.56.193 +2026/10/16 13:26:04 filebrowser_password.go:220: [INFO] [infra] filebrowser: the household asked for a new admin password — stopping the file browser for its CLI +2026/10/16 13:26:08 filebrowser_password.go:258: [INFO] [infra] filebrowser: admin password replaced at the household's press (value never logged); verified new=200 admin=refused in 3s +2026/10/16 13:26:08 handlers.go:2780: [INFO] [web] filebrowser admin password revealed from 37.191.56.193 (value never logged) +2026/10/16 13:26:08 handlers.go:2742: [INFO] [web] filebrowser: new admin password requested from 37.191.56.193 +"filebrowser_admin_state": "generated" +filebrowser Up 19 seconds (healthy) diff --git a/documentation/audits/logins-folders-2026-10-16/live/n100-readonly-0.320.0.txt b/documentation/audits/logins-folders-2026-10-16/live/n100-readonly-0.320.0.txt new file mode 100644 index 00000000..9e7a9d25 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/live/n100-readonly-0.320.0.txt @@ -0,0 +1,5 @@ +200 /apps/filebrowser :: data-fb-operator data-fb-reset data-login-answer="initial" +302 /apps/filebrowser/install :: +200 /apps/romm :: data-install-cta +200 /apps/plex :: data-install-cta +200 /apps/immich :: data-install-cta diff --git a/documentation/audits/logins-folders-2026-10-16/live/romm-install.txt b/documentation/audits/logins-folders-2026-10-16/live/romm-install.txt new file mode 100644 index 00000000..945e7dc0 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/live/romm-install.txt @@ -0,0 +1,2 @@ +{"ok":true,"message":"Telepítés elindítva – az állapot a kártyán követhető"} + HTTP 202 diff --git a/documentation/audits/logins-folders-2026-10-16/redproof.txt b/documentation/audits/logins-folders-2026-10-16/redproof.txt new file mode 100644 index 00000000..cc51f444 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/redproof.txt @@ -0,0 +1,50 @@ +## infra counted as not installed (v0.315.0 line) +=== RUN TestAppPage_InfraAppHasNoInstall + logins_folders_test.go:193: FileBrowser offers Install — an infrastructure app is part of the box +--- FAIL: TestAppPage_InfraAppHasNoInstall (0.09s) +## no fallback login answer (v0.319.0 behaviour) +=== RUN TestAppPage_EveryInstalledAppHasOneLoginAnswer + logins_folders_test.go:256: immich: 0 login answers, want exactly 1 + logins_folders_test.go:256: rallly: 0 login answers, want exactly 1 + logins_folders_test.go:256: bentopdf: 0 login answers, want exactly 1 +--- FAIL: TestAppPage_EveryInstalledAppHasOneLoginAnswer (0.10s) +## no library preference in the Files target +## absent drive still linked +=== RUN TestAppPage_UnknownSourceIsTextNotLink + logins_folders_test.go:315: romm links a folder on a drive that is not on the box +--- FAIL: TestAppPage_UnknownSourceIsTextNotLink (0.08s) +## plaintext stored +=== RUN TestFileBrowserReset_SetsVerifiesAndStoresEncrypted + filebrowser_reset_test.go:56: the stored value is the plaintext password (or empty) +--- FAIL: TestFileBrowserReset_SetsVerifiesAndStoresEncrypted (0.00s) +## verify of the new password removed +=== RUN TestFileBrowserReset_FailedVerifyKeepsOperator + filebrowser_reset_test.go:73: a reset whose new password does not log in reported success +--- FAIL: TestFileBrowserReset_FailedVerifyKeepsOperator (0.00s) +## state check removed +=== RUN TestFileBrowserReset_OnlyInOperatorState + filebrowser_reset_test.go:95: state "": err = , want ErrFileBrowserNotOperator + filebrowser_reset_test.go:98: state "": the apply ran + filebrowser_reset_test.go:101: state "" changed to "generated" + filebrowser_reset_test.go:95: state "generated": err = , want ErrFileBrowserNotOperator + filebrowser_reset_test.go:98: state "generated": the apply ran +## protected deploy guard removed +=== RUN TestDeployStack_RefusesProtectedStack +--- FAIL: TestDeployStack_RefusesProtectedStack (0.00s) +## no library preference in the Files target (rerun) +=== RUN TestAppPage_FilesButtonTarget + logins_folders_test.go:292: calibre-web: Files → https://files.example.hu/files/Beolvas%C3%A1s/calibre, want https://files.example.hu/files/M%C3%A9dia%20&%20k%C3%B6nyvt%C3%A1r/media/books +--- FAIL: TestAppPage_FilesButtonTarget (0.08s) +## protected deploy guard removed (rerun) +=== RUN TestDeployStack_RefusesProtectedStack +--- FAIL: TestDeployStack_RefusesProtectedStack (0.00s) +panic: runtime error: invalid memory address or nil pointer dereference [recovered, repanicked] +panic({0xac07c0?, 0x116c870?}) + /usr/local/go/src/runtime/panic.go:860 +0x13a + filebrowser_reset_test.go:128: filebrowser (Protected=true): err = went on into the deploy (panicked on the bare manager), want ErrProtectedStack + filebrowser_reset_test.go:131: filebrowser: left Deploying set + filebrowser_reset_test.go:128: filebrowser (Protected=false): err = went on into the deploy (panicked on the bare manager), want ErrProtectedStack + filebrowser_reset_test.go:131: filebrowser: left Deploying set + filebrowser_reset_test.go:128: samba (Protected=false): err = went on into the deploy (panicked on the bare manager), want ErrProtectedStack + XX DECOY: a bad value only in the English block rc=1 (expected 0) + XX DECOY: the key under ANOTHER mapping rc=1 (expected 0) diff --git a/documentation/audits/logins-folders-2026-10-16/release/containers.txt b/documentation/audits/logins-folders-2026-10-16/release/containers.txt new file mode 100644 index 00000000..d78e5b5e --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/release/containers.txt @@ -0,0 +1,12 @@ +== tester-1 +unhealthy=0 restarting=0 exited=0 created=0 +controller gitea.dooplex.hu/admin/felhom-controller:0.320.0 Up 13 minutes (healthy) +filebrowser Up 11 hours (healthy) +== demo-hp +unhealthy=0 restarting=0 exited=0 created=0 +controller gitea.dooplex.hu/admin/felhom-controller:0.320.0 Up 34 seconds (healthy) +filebrowser Up 15 minutes (healthy) +== demo-felhom +unhealthy=0 restarting=0 exited=0 created=0 +controller gitea.dooplex.hu/admin/felhom-controller:0.320.0 Up 13 minutes (healthy) +filebrowser Up 11 hours (healthy) diff --git a/documentation/audits/logins-folders-2026-10-16/release/customer-floors-cleared.txt b/documentation/audits/logins-folders-2026-10-16/release/customer-floors-cleared.txt new file mode 100644 index 00000000..8600ce1c --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/release/customer-floors-cleared.txt @@ -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 diff --git a/documentation/audits/logins-folders-2026-10-16/release/dashboards.txt b/documentation/audits/logins-folders-2026-10-16/release/dashboards.txt new file mode 100644 index 00000000..7635a913 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/release/dashboards.txt @@ -0,0 +1,3 @@ +felhom.enkisfelhom.hu 200 +felhom.enkicsifelhom.hu 200 +felhom.demo-felhom.eu 200 diff --git a/documentation/audits/logins-folders-2026-10-16/release/global-floor.txt b/documentation/audits/logins-folders-2026-10-16/release/global-floor.txt new file mode 100644 index 00000000..e9eb5e31 --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/release/global-floor.txt @@ -0,0 +1,3 @@ +15:48:34 +HTTP/1.1 303 See Other +Location: /configuration?flash=floor_set diff --git a/documentation/audits/logins-folders-2026-10-16/release/https-only.txt b/documentation/audits/logins-folders-2026-10-16/release/https-only.txt new file mode 100644 index 00000000..ad440aff --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/release/https-only.txt @@ -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 diff --git a/documentation/audits/logins-folders-2026-10-16/release/hubdb-floors-after.txt b/documentation/audits/logins-folders-2026-10-16/release/hubdb-floors-after.txt new file mode 100644 index 00000000..72893f1f --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/release/hubdb-floors-after.txt @@ -0,0 +1,3 @@ +('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') +customers: no such table: customers diff --git a/documentation/audits/logins-folders-2026-10-16/tools/cdp_logins.py b/documentation/audits/logins-folders-2026-10-16/tools/cdp_logins.py new file mode 100644 index 00000000..0a4fe82a --- /dev/null +++ b/documentation/audits/logins-folders-2026-10-16/tools/cdp_logins.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""v0.320.0 logins & folders browser check. 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 = {} + MARK = """(function(){ function all(sel,attr){return [].map.call(document.querySelectorAll(sel),function(e){return e.getAttribute(attr)})} + return {path: location.pathname, title: (document.querySelector('.app-hero-name')||{}).textContent, + tabs: all('[data-tab]','data-tab'), install: !!document.querySelector('[data-install-cta]'), + loginAnswer: all('[data-login-answer]','data-login-answer'), loginKind: all('[data-login-kind]','data-login-kind'), + fbOperator: !!document.querySelector('[data-fb-operator]'), fbReset: !!document.querySelector('[data-fb-reset]'), + reveal: !!document.querySelector('#initcred-reveal'), open: (document.querySelector('[data-app-open]')||{}).href || null, + files: (document.querySelector('[data-app-files]')||{}).href || null, + folders: [].map.call(document.querySelectorAll('.datapath-row'), function(r){var a=r.querySelector('a'); return (a?a.href:'TEXT:'+r.textContent.trim().slice(0,60))}), + fbNote: !!document.querySelector('[data-fb-login-note]'), scrollWidth: document.documentElement.scrollWidth};})()""" + for p in ['/apps/filebrowser', '/apps/filebrowser/guide', '/apps/romm', '/apps/romm/data', '/apps/calibre-web', + '/apps/calibre-web/data', '/apps/plex', '/apps/plex/data']: + n0 = len(console) + goto(BASE + p) + r = ev(MARK); r['console'] = console[n0:] + report[p] = r + shot(p.strip('/').replace('/', '-')) + # The cold „Fajlok" click: this browser has NO FileBrowser session. Follow each app's button target. + fbpw = open(FBPWFILE).read().strip() + for slug in ['romm', 'calibre-web', 'plex']: + target = report['/apps/' + slug].get('files') + if not target: + report['cold:' + slug] = 'no Files button' + continue + call('Network.clearBrowserCookies') # cold: no FileBrowser session (the controller one is re-made below) + goto(target) + first = ev('location.pathname + location.search') + shot('cold-' + slug + '-1-login') + 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)) + time.sleep(4) + after = ev('location.pathname') + shot('cold-' + slug + '-2-after-login') + report['cold:' + slug] = {'target': target, 'first': first, 'after_login': after} + del fbpw + print(json.dumps(report, indent=1, ensure_ascii=False)) +finally: + proc.terminate() + subprocess.run(['rm', '-rf', prof]) diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index a7602686..52eb509a 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -124,7 +124,7 @@ stopping line that lies. | **R-494** | Install & onboarding | P4 | **NARROWED 2026-09-14 by operator ruling → [P3-LOW] the hub COULD create the tunnel at customer creation, for a domain already on Cloudflare. Not blocking: every customer has their own domain and the operator creates the tunnel per day-0 A.1 (`architecture/01-topology-and-trust.md`).** *Original finding, kept:* **[P1-HIGH] A new customer's dashboard has NO reachable address unless the operator hand-makes a Cloudflare tunnel — the link in the setup-code mail is dead.** MEASURED 2026-09-14 on a fresh install from the public ISO (drill intervention **I1**): the claim mail points at `https://felhom.drill0242.felhom.eu`; that name has **no A and no AAAA** record (`dig @1.1.1.1`, control `felhom.enkisfelhom.hu` resolves); the hub has **no tunnel- or DNS-creation code** (`hub/internal/cloudflare/` holds only geo-rule removal; `cf_tunnel_token` is a pasted, optional form field, `configs.go:1478`) — day-0 runbook A.1 makes it a manual Cloudflare-dashboard step that nothing on the customer-create page asks for; the box's own split-horizon resolver on the appliance LAN IP answered `google.com` but not the dashboard name at 13:27:39Z; the agent applied the record at **13:27:44Z** (`lanresolver: applied split-horizon record … ip=192.168.0.158`, 3 m 46 s after the controller started), so the box CAN answer the name — **but only to a device that uses the box as its DNS server, and no document, screen or mail tells a household to do that**; the router and the installer-offered DNS answer nothing. The page was reachable only at the guest's LAN address with the name forced (`curl --resolve …:443:192.168.0.158`). **A volunteer could not have done that.** **What it needs:** an operator ruling — the hub creates the tunnel and DNS at customer creation, or the product gives a household a LAN address that works with no DNS change. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: operator-side automation; the operator creates the tunnel by hand per the day-0 runbook.** | — | — | CC | | **R-504** | Install & onboarding | P4 | **[P3-LOW] `iso.felhom.eu` cannot show an index page on its own — its root returns 404, and the download page lives on the website instead.** MEASURED 2026-09-14: `https://iso.felhom.eu/` and `/index.html` → 404; only named objects answer. The host is an R2 bucket behind a custom domain; whether R2 would serve an uploaded `index.html` at `/` was **not measured** (uploading anything to the public bucket is a publication). The ISO v1.27.0 task puts the Hungarian download page at `felhom.eu/letoltes` (published with the ISO, after the operator's yes). **Remaining:** a redirect from `iso.felhom.eu/` to that page needs a Cloudflare rule the session has no credential for. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (Cloudflare rule)** **Re-ranked 2026-10-03: P3→P4: households are sent to the website's download page; the bare address is cosmetic.** | — | — | operator | -## Apps & catalog — 22 rows (P2 1, P3 12, P4 9) +## Apps & catalog — 25 rows (P2 1, P3 14, P4 10) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -148,8 +148,11 @@ stopping line that lies. | **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:///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 „ — 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.` 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: . 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//`; 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 | ## App updates — 4 rows (P3 4)