Logins and folders (controller 0.320.0 released): design, R-1100..R-1102 opened, STATUS, evidence, report
gates / gates (push) Successful in 7m4s
02 "Logins and folders" [DESIGN]: an infra stack is part of the box on the app page; one login answer per installed app (catalog app_info.login_kind); FileBrowser's new-password press (its CLI, server stopped); "Fájlok ↗" and the per-drive media folders, a folder with no source is text. Register 176 -> 179. STATUS: look at FileBrowser, RomM and Plex on demo-hp; say yes for the N100's press; decision D10 (R-1102). Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
No files matched your search
@@ -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/<folder>` is reachable.
|
||||||
|
|
||||||
|
## 2. What a household sees now (controller 0.320.0)
|
||||||
|
|
||||||
|
- **FileBrowser** is part of the box: running state, „Megnyitás ↗" to `files.<domain>`, 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,<pw>` (`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=<the folder>`; 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).
|
||||||
@@ -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`.
|
`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
|
## 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).
|
- **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`.
|
- **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).
|
- 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 | — |
|
| 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 | — |
|
| 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 | — |
|
| 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
|
## 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/<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”.
|
- **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.
|
- **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.
|
- **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.
|
||||||
|
|||||||
@@ -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
|
template gained a `type: password` field — `InjectMissingFields` never fills one
|
||||||
(`audits/app-page-tuning-2026-10-16/P0`).
|
(`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,<pw>`) 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
|
**[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: <név>" row, every
|
install wizard's fields: the address is the „Cím" row, the storage is a „Tárhely: <név>" row, every
|
||||||
other install answer and generated key is a row (keys keep their eye). The read-only hint sits under
|
other install answer and generated key is a row (keys keep their eye). The read-only hint sits under
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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/<movies|tv|music|torrents|transmission|…>` folder is reachable as `<drive source>/media/<sub>` — 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.
|
||||||
@@ -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
|
||||||
@@ -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 <user>,<password> -c <config>`** — `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).
|
||||||
@@ -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 <step> steps: backup | reset <pwfile> | login <pwfile> | inventory <pwfile> | seed <pwfile> | cleanup <pwfile>
|
||||||
|
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
|
||||||
@@ -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
|
||||||
@@ -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 "<string>", line 1, in <module>
|
||||||
|
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 "<string>", line 1, in <module>
|
||||||
|
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']
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 46 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{"ok":true,"message":"Telepítés elindítva – az állapot a kártyán követhető"}
|
||||||
|
HTTP 202
|
||||||
@@ -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 = <nil>, 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 = <nil>, 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)
|
||||||
@@ -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)
|
||||||
@@ -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 @@
|
|||||||
|
felhom.enkisfelhom.hu 200
|
||||||
|
felhom.enkicsifelhom.hu 200
|
||||||
|
felhom.demo-felhom.eu 200
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
15:48:34
|
||||||
|
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,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
|
||||||
@@ -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])
|
||||||
@@ -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-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 |
|
| **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 |
|
| 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://<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-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-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-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-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-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 |
|
||||||
|
|
||||||
|
|
||||||
## App updates — 4 rows (P3 4)
|
## App updates — 4 rows (P3 4)
|
||||||
|
|||||||