diff --git a/STATUS.md b/STATUS.md index 6f8372d9..706282fc 100644 --- a/STATUS.md +++ b/STATUS.md @@ -2,7 +2,7 @@ **Ready for the first real tester (Tester-2): yes. Tester 2 (a laptop) is off; nothing was sent to it.** -**Updated 2026-10-15 (evening).** Agent 0.156.1 + its bundle on all three boxes; controller 0.314.0 is the floor since 13:11 UTC — raised by CC with its own token after all three test boxes ran it (the first release with no golden; vouched golden 0.313.0); hub 0.153.0 (access tokens — CC uses its own token now, not your password). Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day: +**Updated 2026-10-15 (evening).** Agent 0.156.1 + its bundle on all three boxes; controller 0.315.0 built (one app page with four tabs); 0.314.0 is still the floor since 13:11 UTC — raised by CC with its own token after all three test boxes ran it (the first release with no golden; vouched golden 0.313.0); hub 0.153.0 (access tokens — CC uses its own token now, not your password). Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day: `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 @@ -45,6 +45,7 @@ | D9 | R-1062 | The N100's USB drive is also its whole-box backup drive. Split it in two anyway? | **No — keep one drive**; the lived-in box keeps its nightly whole-box backup | Split: the box loses its local whole-box backup (or add a second drive first) | it stays one drive; multi-drive tests stay on Tester 1 | — | ## Waiting on you — do +- **Nézd meg az új alkalmazás oldalt a demo-hp-n (egy telepített és egy nem telepített app).** Egy alkalmazás mostantól egy oldal négy füllel (Beállítások · Adatok és mentés · Útmutató · Napló), az Indítás/Újraindítás/Leállítás a „Működés" kártyán van, és a „Vissza" az alkalmazáshoz visz, nem a listához. Mondd meg, jó-e így az elrendezés. Ha nem nézed meg: a felület él, csak senki nem látta még böngészőben — a robot a DooPlexen nem tud kattintani. - **The N100 sheet (about 30 minutes):** `documentation/audits/n100-lived-in-2026-10-15/SHEET.md` — install Plex and claim it, make the admins in Prowlarr/Sonarr/Radarr, add your download sources in Prowlarr, add one show and one film. Then say „done". If not: the media apps wait, safe behind their gate. - **Delete the test customer „Tester3” in the hub (browser only).** Its test box is already gone and switched off on purpose, so no alarm is coming. If not: a dead test customer stays in your list. - **Hub 0.153.0 (your rulings 13:20 + R-1059 „A"): no token can resend a claim code, create or edit a customer, make a passphrase or a link, or re-issue an off-site password.** Live since 13:39 (checked with CC's token: all six refused). diff --git a/documentation/architecture/02-controller-module-map.md b/documentation/architecture/02-controller-module-map.md index ab0b0399..7f7ed0fe 100644 --- a/documentation/architecture/02-controller-module-map.md +++ b/documentation/architecture/02-controller-module-map.md @@ -397,6 +397,86 @@ by its words — the earlier Hungarian-substring test would have moved the warni page the day the sentence was translated (R-553, pinned by `TestR553_DiskWarningPlacementSurvivesWordingChange`). A new inline warning needs its own kind, not a text match. +#### The app page — one page, four tabs (v0.315.0) + +**[DESIGN]** One app is ONE page. Until v0.314.0 it was six — details, install/settings, logs, +export, the per-app backup panel, and a details card for moving data — and every „Vissza" on them +went to the app LIST, so there was no way back to the app you were looking at. + +| URL | Tab | Holds | +|---|---|---| +| `/apps/{slug}` | — | the default tab | +| `/apps/{slug}/settings` | „Beállítások" | „Működés" (Start / Restart / Stop), the cards that want something from the household, the install-time and optional settings | +| `/apps/{slug}/data` | „Adatok és mentés" | the data folders, move to another drive, the nightly copy, the 2nd-copy setting, export | +| `/apps/{slug}/guide` | „Útmutató" | tagline, screenshots, use cases, first steps, the catalog's guide, the FAQ, the docs link | +| `/apps/{slug}/logs` | „Napló" | the log | +| `/apps/{slug}/install` | — | the install wizard — a page, not a tab | + +**[DESIGN] The default tab is RENDERED at the bare URL, never redirected to.** `/apps/{slug}` serves +the settings tab's content for an installed app and the guide for a not-installed one, with the URL +unchanged, so the address an alert, an e-mail, a bookmark or the app list carries stays the app's one +address. A redirect would make every such link bounce and would put a tab name into addresses nobody +chose. + +**[DESIGN] The tabs are links, not panels.** A link in an alert or an e-mail has to land ON a tab, +and the log tab's poller must not run while the household reads the settings; one page with hidden +panels gives neither. Each tab is an `` with `aria-current`, and the log poller is inside the log +tab's template. + +**[DESIGN] A not-installed app has one view and one URL.** No tab bar — a bar of one tab is +furniture — and every tab suffix answers 302 to the base. + +**[DESIGN] The old URLs are a bridge, and only for reads.** `/stacks/{name}/{deploy,logs,export,backup}` +GET answer **302** to the tab that now holds them, with the query string carried through; +`/deploy` goes to the settings tab for an installed app and to the install wizard for one that is +not. **302 and never 301:** a 301 lives in the browser's cache beyond anyone's reach, and these +paths are a bridge for one release, not an address. `name → slug` is resolved through the stack +list, never assumed equal — the two are equal for every catalog app today, which is exactly why +assuming it would break on the first app where they differ, on a page nobody tests. + +**Every POST on those paths is untouched.** The backup form, the app-email toggle and the +`auto-field/reveal` endpoint keep their URLs and their handlers; the redirect cases are matched on +`GET`. Redirecting a POST would turn a save into a page fetch and lose the household's input. + +#### After-start fields — the catalog asks, Go answers (v0.315.0, D2) + +**[DESIGN]** Some values can only be given once the app runs: Plex's claim code is minted on plex.tv +and handed to a server that is already up (R-1028/R-1060). A catalog field marked `after_start: true` +is skipped by the install wizard and gets a card on the settings tab. + +**[DESIGN] The catalog declares THAT a value is asked, and what to call it. It never declares what +happens to the value.** The action is Go, in a registry keyed by `slug + "/" + ENV_VAR` +(`web/after_start.go`), with `Needed(stack)` and `Apply(…, value)`. The household's text is therefore +an argument to a function in the controller's own source, and never a fragment of a command, a URL +or a container name that a catalog file chose. Adding a template cannot add an action. + +**[DESIGN] Both halves are required, and each is checked on its own.** A field the catalog declares +with no registered action renders no card, answers its POST with 404, and logs one WARN naming the +env var KEY — a field that silently does nothing is the kind of absence nobody notices. An action +registered for a field **this box's catalog does not declare** is equally refused: the registry alone +is not an invitation. The second check looks redundant and is not — it was deletable with the whole +suite green until a test was written for it, and without it a POST for an undeclared field reached +Plex and opened the setup gate. + +The value is a credential and is never logged. `/apps/plex/plex-claim` remains and runs the same +registry entry, so a page open across the update still works. + +#### The catalog's user guide (v0.315.0) + +**[DESIGN]** `app_info.guide` carries `sections` (title, optional `intro`, `steps`) and `faq` +(`q`, `a`), rendered on the guide tab. The copy model is `10-localisation.md` §7; the guide block is +described there. + +**[DESIGN] `checked_version` is the render gate, and it is not copy.** It names the image tag the +guide's author actually clicked through. A guide is rendered only when it is set; otherwise the tab +shows its other content and says a guide is still to come. A guide walking a household through a +screen two releases old is worse than no guide, and „someone wrote this against a version" is the +only cheap evidence that it was ever true. Being outside the copy model is deliberate: a translator +does not get to vouch for a version, so an overlay can neither set nor clear it. + +**[DESIGN] Guide text is DATA.** Plain text through `html/template` — no markdown, no raw HTML — so +a step containing a tag is shown as the characters its author typed. + ### `scripts/` | File | Class | Reason | Risk | |---|---|---|---| diff --git a/documentation/architecture/10-localisation.md b/documentation/architecture/10-localisation.md index c0ab4b44..6dab98d2 100644 --- a/documentation/architecture/10-localisation.md +++ b/documentation/architecture/10-localisation.md @@ -248,6 +248,45 @@ reached only through `LocalizeStacks` / `LocalizeStackPtr` / `Stack.MetaFor`. still looks right. - **`For` never writes through.** The metadata lives in the stack manager's cache and is shared by concurrent requests; an in-place merge would put one household's language on another's page. + +**[DESIGN] The guide block (v0.315.0).** `app_info.guide` joins the copy model with one exception it +has to state out loud: + +```yaml +app_info: + guide: + checked_version: "1.2.3" # NOT copy — the render gate; see below + sections: + - title: "Első bekapcsolás" + intro: "…" # optional + steps: ["…", "…"] + faq: + - q: "…" + a: "…" +i18n: + en: + app_info: + guide: + sections: [...] # same shape; checked_version is NOT overlaid + faq: [...] +``` + +- **`checked_version` is not copy and is not overlaid.** It names the image tag the guide's author + clicked through, and the guide renders only when it is set (`02-controller-module-map.md`, „The + catalog's user guide"). A translator does not vouch for a version, so an overlay can neither set + nor clear it. +- **Sections and FAQ entries are matched BY INDEX — the only lists here that are.** The rule above + says every list is matched by its own key, and a guide section has no key to match on: its title + is the very thing being translated, so a `match_title` would have to be kept in step with the + Hungarian by hand, which is the drift the key rule exists to prevent. Index matching is safe here + for two reasons that must both hold: the catalog's copy gate requires a translated guide to carry + the same number of entries in the same running order, and **the fallback is still field by + field**, so a misaligned entry shows the Hungarian one and never a sentence nobody wrote. +- **`steps` replaces whole**, like the other prose lists, and for the same reason. +- An English list **longer** than the Hungarian one is ignored past the Hungarian count, with one + WARN naming the app and the counts (`Metadata.GuideOverlayExtras`, logged by the page that renders + the guide — `For` runs per request and holds no logger). A silently dropped translation is exactly + the kind of absence nobody notices. - **Two copy producers have no request and therefore no language** — the integration rows (`internal/integrations`) and the initial-credentials note (`internal/stacks/initialcreds.go`). Both are re-taken from the localised metadata in the handler. diff --git a/documentation/backlog/CLOSED-ITEMS.md b/documentation/backlog/CLOSED-ITEMS.md index 50594d25..65387467 100644 --- a/documentation/backlog/CLOSED-ITEMS.md +++ b/documentation/backlog/CLOSED-ITEMS.md @@ -26,6 +26,12 @@ --- +## 2026-10-15 (evening) — controller v0.315.0, the app page + +| Row | What | Closed | Evidence | +|---|---|---|---| +| **R-1066** | **No architecture document covered the app page.** (P3) `02-controller-module-map.md` classified `templates/` as PORT and said nothing about what an app's page IS, so the six-page sprawl, the „Vissza goes to the list" behaviour and the Plex-only claim box were all undocumented — there was nothing a reviewer could have read to call any of them wrong. Found while specifying the rebuild. | CLOSED 2026-10-15 — **controller v0.315.0**: `02-controller-module-map.md` gains „The app page" under `web/` (the URL map, the rendered-not-redirected default tab, the 302-never-301 redirect rule, the after-start registry and why its actions are Go, the guide's `checked_version` gate), and `10-localisation.md` §7 gains the guide block with its index-matching exception. Each marked `[DESIGN]`. | `felhom-controller/REPORT.md`, `architecture/02-controller-module-map.md` „The app page" | + ## 2026-10-15 (evening) — controller v0.314.0, the first release under decision 250 (the floor raised by CC's token) | Row | What | Closed | Evidence | diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 9c9c3e24..2dfb0307 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -124,7 +124,7 @@ stopping line that lies. | **R-494** | Install & onboarding | P4 | **NARROWED 2026-09-14 by operator ruling → [P3-LOW] the hub COULD create the tunnel at customer creation, for a domain already on Cloudflare. Not blocking: every customer has their own domain and the operator creates the tunnel per day-0 A.1 (`architecture/01-topology-and-trust.md`).** *Original finding, kept:* **[P1-HIGH] A new customer's dashboard has NO reachable address unless the operator hand-makes a Cloudflare tunnel — the link in the setup-code mail is dead.** MEASURED 2026-09-14 on a fresh install from the public ISO (drill intervention **I1**): the claim mail points at `https://felhom.drill0242.felhom.eu`; that name has **no A and no AAAA** record (`dig @1.1.1.1`, control `felhom.enkisfelhom.hu` resolves); the hub has **no tunnel- or DNS-creation code** (`hub/internal/cloudflare/` holds only geo-rule removal; `cf_tunnel_token` is a pasted, optional form field, `configs.go:1478`) — day-0 runbook A.1 makes it a manual Cloudflare-dashboard step that nothing on the customer-create page asks for; the box's own split-horizon resolver on the appliance LAN IP answered `google.com` but not the dashboard name at 13:27:39Z; the agent applied the record at **13:27:44Z** (`lanresolver: applied split-horizon record … ip=192.168.0.158`, 3 m 46 s after the controller started), so the box CAN answer the name — **but only to a device that uses the box as its DNS server, and no document, screen or mail tells a household to do that**; the router and the installer-offered DNS answer nothing. The page was reachable only at the guest's LAN address with the name forced (`curl --resolve …:443:192.168.0.158`). **A volunteer could not have done that.** **What it needs:** an operator ruling — the hub creates the tunnel and DNS at customer creation, or the product gives a household a LAN address that works with no DNS change. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: operator-side automation; the operator creates the tunnel by hand per the day-0 runbook.** | — | — | CC | | **R-504** | Install & onboarding | P4 | **[P3-LOW] `iso.felhom.eu` cannot show an index page on its own — its root returns 404, and the download page lives on the website instead.** MEASURED 2026-09-14: `https://iso.felhom.eu/` and `/index.html` → 404; only named objects answer. The host is an R2 bucket behind a custom domain; whether R2 would serve an uploaded `index.html` at `/` was **not measured** (uploading anything to the public bucket is a publication). The ISO v1.27.0 task puts the Hungarian download page at `felhom.eu/letoltes` (published with the ISO, after the operator's yes). **Remaining:** a redirect from `iso.felhom.eu/` to that page needs a Cloudflare rule the session has no credential for. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (Cloudflare rule)** **Re-ranked 2026-10-03: P3→P4: households are sent to the website's download page; the bare address is cosmetic.** | — | — | operator | -## Apps & catalog — 14 rows (P2 1, P3 8, P4 5) +## Apps & catalog — 15 rows (P2 1, P3 8, P4 6) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -141,6 +141,7 @@ stopping line that lies. | **R-707** | Apps & catalog | P4 | **[P2] 37 apps still start with a login a stranger can take (`09` §3 decision 45).** Audit of all 53 apps: `app-catalog-felhom.eu/FIRST-ADMIN.md` (class, fix route, status, measured or read). Open: **3 hard-coded defaults** — calibre-web (`admin / admin123`, measured working on demo-hp and 9202; its own `cps.py -s` route needs a generated password WITH a special character — our generator is letters+digits, a controller change), mealie (`changeme@example.com / MyPassword`), wger (`admin / adminadmin`); **34 open first-run screens** (the first visitor creates the admin: actualbudget, adventurelog, audiobookshelf, calcom, docmost, emby, ghost, gitea, gramps-web, home-assistant, homebox, immich, jellyfin, komga, n8n, navidrome, opengist, outline, papra, plant-it, radarr, rallly, recipe-importer, romm, seerr, sonarr, sparkyfitness, tandoor, termix, uptime-kuma, vikunja, wanderer, wishlist, zipline). **Stale notes:** romm's `default_creds` `admin / admin` answers 401 on demo-hp (like a wrong password) — the page now warns with a login that does not exist; zipline's looks stale too. **Measured on demo-hp 2026-09-28 (read-only):** bookstack's default still logs in on the INSTALLED app (the fix is for new installs; the page now warns). Each fix: route (a) env or (b) the app's own CLI/API via `after_install:`, proven on 9202 with the default failing and the generated password working; route (c) a page sentence. Several sessions (operator, 2026-09-28). **2026-09-29 (controller v0.280.0, catalog `d0e7e2e`):** every class-3 app fixed — mealie, wger, calibre-web by `after_install` (calibre-web with the new `password:24:special`), proven on 9202 fresh installs (`audits/login-gate-2026-09-29/D/`); the setup gate (decision 46, spike PASSED) built and live on immich, n8n, audiobookshelf (probes measured) and uptime-kuma (button) (`…/C/`); romm's and zipline's stale notes removed. **Left: 30 class-4 apps** — gate each (probe measured on 9202 where one exists — 11 upstream candidates listed in `…/B/B-VERDICT.md` §3; the button otherwise). **2026-09-29 afternoon (controller v0.281.0, catalog `6faf432`):** 28 more class-4 apps gated — 32 of 34 — each proven on 9202 (`audits/gate-rollout-2026-09-29/`B): stranger → gate page / 401, household reached the first-setup screen, the gate opened (9 by a measured probe, the rest by the press), the app answered after. seerr, outline, rallly: gated, their opening needs a media server / e-mail (not proven). **Left:** wanderer (R-714); plant-it is not installable. | **NARROWED** (2026-10-03 triage: the row's verdict was finished, but it names open work no other row carries — seerr, outline and rallly are gated, but the gate OPENING is not proven (needs a media server / e-mail)) — **CLOSED — 2026-09-29 (the rest → R-714)** | — | — | CC | | **R-905** | Apps & catalog | P4 | **wger's collected style files (283 MB) go into every wger backup, though the app rebuilds them at every start.** Since R-762's fix (`DJANGO_DEBUG=False` + `collectstatic`, catalog `cf1ed43`) the static files sit in a named volume that the backup legs copy like data (measured 283 MB, `audits/design-build-2026-10-06/`); `collectstatic` regenerates them, so they are rebuildable bytes in every Tier-1 unit, Tier-2 copy and off-site snapshot. wger is `lifecycle: hidden` and no box runs it (hub read 2026-10-08). Options to weigh: a non-backed-up volume class for regenerable data, or an anonymous volume. Found by the R-762 helpers 2026-10-08. | **OPEN — needs a design before wger is shown** | — | Design the „regenerable volume" exclusion (catalog + controller backup legs) | CC | | **R-995** | Apps & catalog | P4 | **Removing an app through the product can leave unnamed Docker volumes and an empty import folder behind.** MEASURED 2026-10-13 on demo-felhom after 61 removals (`documentation/audits/sizing-2026-10-13/METHOD.md` „Teardown”, `evidence/leftover-volumes-after-bench.txt`): 8 unnamed volumes (4–72 KB; immich 1, romm 2, privatebin 3, onlyoffice 2 — volumes the images declare themselves) and `userdata/import/{paperless,calibre,grimmory}` (empty, created by the deploy). The removal answered 200 and its own leftover check was clean, because it looks only at labelled volumes. No data risk; disk and tidiness only. Removed by name on the bench. | **READY** — filed 2026-10-13 | — | Make the removal account for the containers' unnamed volumes (and an empty import folder it created), with a test that deploys an image that declares a VOLUME | CC | +| **R-1067** | Apps & catalog | P4 | **The catalog still carries `pi_compatible:` on every template, and nothing reads it any more.** controller v0.315.0 removed the „Pi kompatibilis" badge and the „csak x86" tag from every page (Raspberry Pi is no longer supported, so one was noise and the other was a warning on almost every app). The `ResourceHints.PiCompatible` struct field and the catalog key were deliberately LEFT: a box still on 0.314.0 reads the key, and removing it would make that box show „csak x86" on every app. **Gated on the floor:** once the fleet floor is at or above 0.315.0, drop `pi_compatible:` from the templates and the field from `stacks.ResourceHints` in the same pair of commits. Harmless until then — an unread key costs nothing. | **BLOCKED** — filed 2026-10-15 | the fleet floor reaching 0.315.0 | Drop the catalog key and the struct field; grep the hub and the website for Pi wording at the same time (none found 2026-10-15) | CC | | **R-1043** | Apps & catalog | P4 | **Sonarr, Radarr and Prowlarr build their login redirect as an absolute `http://` URL.** They trust forwarded headers only from their „Trusted networks” (the household set `192.168.0.0/24`; traefik is `172.18.x`), so they ignore `X-Forwarded-Proto: https` and answer `Location: http:///login` to an https visitor. Since controller v0.312.0 that hop is sent back to https by traefik (a GET, no password), so it is cosmetic; a template could seed traefik's network as trusted. (`audits/https-only-2026-10-14/``1A-3-4.txt`) | **OPEN** — filed 2026-10-14 | — | Seed the docker network as a trusted proxy in the *arr templates (with the R-1030 seed), measure the redirect | CC |