From 6fd8c8751629d85d9a1435e338e47f868f441956 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 14 Sep 2026 17:26:53 +0200 Subject: [PATCH] doorstep: console is Felhom's (ISO 1.27.0 source), passphrase hand-over copy (hub 0.113.0 source), rulings Phase 0: the public ISO never auto-installs by construction (no answer.toml, G1); the operator re-affirmed the interactive installer 2026-09-14. - felhom-bootstrap.sh: mask pvebanner.service, write a Hungarian /etc/issue (no :8006 admin URL); pairing banner names the Tulajdonosi jelmondat and paints through the CONSOLE_DEV seam (R-496). Harness: 8 checks, red first; fake hub now sends a pairing code (the banner was never tested, R-502). - hub: created flash + Credentials block tell the operator to hand the phrase over; the self-bind mail names the operator (R-497). Tests red first. - iso-release-gate G14-G16; domain ruling in 01-topology + CONTEXT; R-494 narrowed to P3; R-502..R-504 filed; volunteer guide and day-0 A.2 aligned. ISO_VERSION 1.27.0 (not built, not published). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS --- CONTEXT.md | 21 ++++++ .../architecture/01-topology-and-trust.md | 9 +++ documentation/backlog/OPEN-ITEMS.md | 5 +- .../runbooks/VOLUNTEER-first-hour.md | 36 ++++------ documentation/runbooks/day0-install.md | 5 +- documentation/runbooks/iso-release-gate.md | 47 +++++++++++++ hub/CHANGELOG.md | 19 ++++++ hub/internal/notify/templates.go | 3 +- hub/internal/web/passphrase_handover_test.go | 68 +++++++++++++++++++ .../web/templates/customer_unified.html | 3 +- scripts/CHANGELOG.md | 21 ++++++ scripts/iso/build-felhom-iso.sh | 2 +- scripts/iso/felhom-bootstrap.sh | 38 ++++++++++- scripts/iso/test/bootstrap-modes.sh | 26 +++++-- 14 files changed, 269 insertions(+), 34 deletions(-) create mode 100644 hub/internal/web/passphrase_handover_test.go diff --git a/CONTEXT.md b/CONTEXT.md index 98594e4a..735f7d53 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -14,6 +14,27 @@ > language, one screen, no identifiers in the prose. Same subjects, different readers; merging them > would make one of the two audiences stop reading. `STATUS.md` is also a **view of `OPEN-ITEMS.md`** > and holds nothing of its own; this file does hold its own content, namely the standing rulings below. +## A customer's domain is their own, and the public installer stays interactive (2026-09-14, R-494 / R-495) + +**Two operator rulings, both 2026-09-14.** + +1. **Every customer has their own domain, never a name under `felhom.eu`.** Free Cloudflare certificates + do not cover nested names; the domain (a few thousand forints a year) is included in the price. The + operator creates the tunnel per day-0 A.1; R-494 is narrowed to "the hub could create it" (P3). + Home: `architecture/01-topology-and-trust.md` §networking. +2. **The public ISO keeps the interactive Proxmox installer.** Offered an install-time rule ("use the only + internal disk, otherwise stop"), the operator kept the 2026-07-31 ruling: a person chooses the disk. + Why it was offered and not built: the auto-installer has no local chooser or stop page; a runtime rule + needs either an HTTP answer service or surgery inside the Proxmox installer, and USB-drive detection + was measured unsafe by udev property (SPIKE-universal-iso-1). Consequence, stated so it is not + re-litigated: **the installer's own screens stay English**; the Felhom-written text around them + (menu, console, first-boot screens) is Hungarian, and the volunteer guide answers each installer + screen. Release gate G14–G16. The spike that would inform a reversal is R-503. + +**Decisions a later session must not re-derive:** the ISO's "never engaged" auto-install is not a +defect — the release build has no answer file by construction (G1); `pvebanner.service` rewrites +`/etc/issue` on every boot, so a console fix must mask it (ISO v1.27.0). + ## The Update button is a guarded job, and the route back is the restore (2026-09-13, update arc slice 4 — R-448 / R-443 / R-439) **Shipped** in controller v0.237.0 (job) + v0.238.0 (page) + v0.238.1 (nightly-leg fix), **proven live** diff --git a/documentation/architecture/01-topology-and-trust.md b/documentation/architecture/01-topology-and-trust.md index 31eb7020..d23883d0 100644 --- a/documentation/architecture/01-topology-and-trust.md +++ b/documentation/architecture/01-topology-and-trust.md @@ -148,6 +148,15 @@ credentials. DNS/routing stay intact through an outage. - **Outbound only** for control/report/backup (poll to hub, push to PBS). No inbound control endpoint exists in the chosen model. +- **Every customer has their OWN domain — never a name under `felhom.eu`** (operator ruling + 2026-09-14). The free Cloudflare tier's certificate covers one level below a zone, so nested names + such as `felhom..felhom.eu` are not covered; the customer's own domain is a few thousand + forints a year and is **included in the customer's price**. The dashboard is `felhom.`, each app `.`. **The tunnel is created by the operator** per + `runbooks/day0-install.md` A.1 today; the hub creating it at customer creation (for a domain already + on Cloudflare) is register row R-494, P3, not blocking. Measured reason this was ruled now: the + 2026-09-14 first-hour drill used a `*.felhom.eu` customer domain with no tunnel, and the dashboard link + in the setup-code mail did not resolve. - **Tunnel placement: host** (resolved, Part 3 §3/§5). `cloudflared` runs on the Proxmox host as its own **agent-managed systemd service** — not inside the guest — so the data path survives control-plane death by construction. Geo-restriction WAF is **hub-enforced** (the diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index cdbbcb75..46e4ce45 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -697,7 +697,7 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server` | **R-489** | **[P3-LOW] `POST /api/stacks/{name}/remove` reports `volumes_removed: null` over named volumes it DID remove.** MEASURED 2026-09-13 on demo-hp five times (gokapi, actualbudget, adventurelog ×2, glance): `docker compose down --volumes` removed the app's named volumes (`docker volume ls` count 2 → 0) and the response carried `"volumes_removed":null`. The customer's confirmation dialog therefore cannot say what it deleted. Split out of R-474 (closed in v0.240.0 for the backups half). **Fix shape:** list the volumes before `down --volumes`, diff after, and report the difference (`[]` when none, never `null`). **PARTLY SHIPPED in v0.242.0 (`d698ce3`), measured live on 9202 the same night:** the difference is computed and a fresh compose-created volume IS reported (`["opengist_opengist_data"]`), but the listing filters on the compose project LABEL and a volume recreated by a unit restore (`docker volume create `, `restore.go:154`) carries no labels — compose still removes it and the response says `[]` (`audits/v0242-2026-09-14/19-R489-cause.txt`). **Remaining fix:** list by the `_` name prefix as well (union), or label the recreated volume as compose would. | **READY — rank P3-LOW; owner: CC (residual)** | | **R-492** | **[P3-LOW] `cfg.Paths.HDDPath` is empty on every box and still has readers; delete it.** R-465 audited its six readers and found every one falling back; R-490 (v0.242.0) gave the last one, `systemInfo`, the same fallback. The global now carries no information on any box and its deletion was deferred twice. **Fix shape:** remove the field, its env binding and the readers' fallback branches; a build proves nothing reads it. Next controller release. | **READY — rank P3-LOW; owner: CC** | | **R-493** | **[P1-HIGH] There are NO customer-facing install instructions, so a volunteer cannot begin — this blocks inviting anyone.** MEASURED 2026-09-14 (drill `audits/DRILL-fresh-install-0242-2026-09-14.md`, Phase 0.2), each source with what was searched: the website's 9 pages carry no mention of the installer, of `iso.felhom.eu`, or of any install step (`iso`, `letolt`, `telepit`); `https://iso.felhom.eu/` and `/index.html` both return **404** — only the exact object name `felhom-installer-1.26.1-pve9.2-1.iso` answers, so the file cannot be found without being told its name; `RUNBOOK-onboarding-draft-v4.md` is an operator-attended script; the R-11 tester one-pager was ruled 2026-07-21 and never written; no `email.md` exists in the workspace; the two hub mails a new customer receives (`Kösd össze a Felhom dobozodat`, `Elindult a Felhom szervered — beállító kód`) assume the box is already installed. **Stopgap written by the drill:** `documentation/runbooks/VOLUNTEER-first-hour.md` (Hungarian, the steps the product really needs, each addition over today listed at its top). **What it needs:** the operator to choose the channel and approve the text. | **WAITING-ON-OPERATOR — rank P1-HIGH; owner: operator (channel + text), CC (draft)** | -| **R-494** | **[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. | **WAITING-ON-OPERATOR — rank P1-HIGH; owner: operator (ruling), CC (build)** | +| **R-494** | **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** | | **R-495** | **[P2-MEDIUM] The public installer asks a stranger four questions nothing answers, and REFUSES its own default on one of them.** MEASURED 2026-09-14 on `felhom-installer-1.26.1` (drill screens `s04`–`s22`): every screen after the GRUB menu is **English** Proxmox (EULA, disk, locale, password, network, summary); **three disks are offered with no guidance which is the system disk** (the default happened to be right); the administrator e-mail is prefilled `mail@example.invalid`; the hostname is prefilled `pve.example.invalid` and pressing Next on it returns **„Invalid values: hostname does not look valid”** — a volunteer who accepts the defaults cannot continue; at the end, with the stick still in, the default-ticked auto-reboot boots back into the installer. None of this is wrong for Proxmox; all of it is unanswered for a Felhom household. **Fix shape (cheap first):** the volunteer instructions answer each question (done in `runbooks/VOLUNTEER-first-hour.md`); later, prefill hostname and e-mail from the ISO build. | **READY — rank P2-MEDIUM; owner: CC (ISO prefill), operator (instruction text via R-493)** | | **R-496** | **[P2-MEDIUM] The box's console tells a stranger, in English and FIRST, to open the Proxmox admin page — and calls the owner passphrase „a jelszavadat”.** MEASURED 2026-09-14 (drill screen `s29`): above the Hungarian pairing banner the console prints Proxmox's own `Welcome to the Proxmox Virtual Environment. Please use your web browser to configure this server - connect to: https://192.168.0.134:8006/` — the operator admin UI, which a household must never be sent to; the Felhom banner below says „add meg ezt a kódot és **a jelszavadat**”, while the self-bind mail and page name the same secret **„Tulajdonosi jelmondat”** (R-323 renamed the mail; `scripts/iso/felhom-bootstrap.sh:71-72` was not renamed). **Fix shape:** replace the Proxmox `/etc/issue` block on appliance installs; rename the console word. Both live in `felhom-bootstrap.sh`, a frozen ISO payload (G9), so it ships with the next ISO. | **READY — rank P2-MEDIUM; owner: CC** | | **R-497** | **[P2-MEDIUM] No product channel ever gives the customer the „Tulajdonosi jelmondat”, yet the self-bind mail says they received it at setup.** MEASURED 2026-09-14: the 5-word phrase is minted at customer creation (`hub/internal/web/configs.go`, `RandomPassphrase(5)`) and shown **only on the operator's customer page**; the self-bind mail (`FormatSelfBindEmail`) says „amelyet a beállításkor kaptál” and the console asks for it; nothing sends it. Day-0 runbook A.2 says the operator must dictate or hand it over — a step that exists only in an operator document. A volunteer whose operator forgets cannot bind the box and is told they already have the phrase. **Fix shape:** the operator's customer-create confirmation says, in one line, to hand this phrase to the customer now; the volunteer instructions say where it comes from (done in `VOLUNTEER-first-hour.md`). | **READY — rank P2-MEDIUM; owner: operator (process), CC (hub copy)** | @@ -705,6 +705,9 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server` | **R-499** | **[P2-MEDIUM] Every app without a data drive is told its data is „already in the full system backup (PBS)" and there is „nothing to do" — on a box with no PBS, whose only whole-box copy sits on the same disk.** MEASURED 2026-09-14 on a fresh 0.242.0 box with the DR tier off (drill step 7): `GET /stacks/bookstack/backup` renders „Ennek az alkalmazásnak az adatai a belső rendszerlemezen vannak, amelyek **már szerepelnek a teljes rendszermentésben (PBS)** … ehhez az alkalmazáshoz nincs külön teendő." The sentence sits under `{{if not .IsHDDApp}}` in `controller/internal/web/templates/tier2_config.html:20-26` and consults nothing about where the whole-guest backup goes. On the same box `/backups` says, correctly, „Helyi tároló (local)" and „A rendszermentés jelenleg ugyanazon a lemezen van, mint a rendszer — így hibás fájlok ellen véd, lemezhiba ellen nem." **Two pages of one product contradict each other, and the reassuring one is the false one.** **Fix shape:** branch the sentence on the box's actual whole-guest target (PBS vs local, same-disk flag the overview already computes); a render test per branch. | **READY — rank P2-MEDIUM; owner: CC** | | **R-500** | **[P3-LOW] The dashboard shows the last backup in UTC while every backup page shows it in local time — two different clock times for one backup.** MEASURED 2026-09-14 on a fresh 0.242.0 box (drill step 12): the same backup (`last_run 2026-09-14T13:41:37Z`) reads „Utolsó mentés: **2026-09-14 13:41**" on `/dashboard` and „Utolsó adatbázis mentés: **2026-09-14 15:41** (most)" on `/backups/apps`. Cause, from source: `controller/internal/web/templates/dashboard.html:154` renders `{{.BackupStatus.LastRun.Format "2006-01-02 15:04"}}` with no conversion to the box's zone, while the backup pages go through the zone-aware helpers in `funcmap.go`. A household comparing the two screens sees a backup two hours apart from itself. **Fix shape:** format through the same zone-aware helper; a render test that pins a non-UTC zone and asserts the local hour. | **READY — rank P3-LOW; owner: CC** | | **R-501** | **[P3-LOW] The documented "confirm your CI run" recipe reads only the LAST page of the jobs list, and that list is not in id order — so it can report a run as missing that exists and passed.** MEASURED 2026-09-14 for felhom.eu commit `38848ff`: `actions/jobs?limit=1` → `total_count 335`; the recipe's page `T/50+1` = page 7 held ids 473…570 and **no match for 8 minutes**; a scan of all seven pages found the job on **page 6** (ids 380…581): `job id=581 name=gates status=completed conclusion=success completed_at 2026-09-14T14:18:23Z`. Pages are not sorted (page 1 ids 1…273, page 2 84…208). `actions/tasks` listed the same run first (`id 582, run_number 335, success`). **Fix shape:** the recipe in `felhom.eu/CLAUDE.md` (end-of-session checklist) scans every page and matches `head_sha`; say so in the same sentence that warns about the id offset (R-417). | **READY — rank P3-LOW; owner: CC** | +| **R-502** | **[P3-LOW] The bootstrap regression harness is run by NO gate and NO CI — and it had never exercised the pairing banner.** MEASURED 2026-09-14: `grep -rn bootstrap-modes scripts/*.py .gitea/workflows` → nothing; the harness is run by hand in `felhom-iso-assistant:trixie`. While adding the R-496 checks, its fake hub's `register` reply turned out to carry no `pairing_code`, so `print_pairing_banner` returned early in every run: the banner that a household reads first had no test at all until ISO v1.27.0. **Fix shape:** register the harness in `repo_gates.py` behind a container-available check that reports INCONCLUSIVE (never skip-as-pass) when docker is absent, with a decoy. | **READY — rank P3-LOW; owner: CC** | +| **R-503** | **[P3-LOW] SPIKE (not built): an install-time disk rule for the public ISO — "exactly one internal disk → install; otherwise stop in Hungarian".** Offered to the operator 2026-09-14 and **not chosen**: the 2026-07-31 ruling (a person chooses the disk) stands. Recorded so a reversal starts from measurements, not from the offer. **What must be measured first:** (1) whether the Proxmox auto-installer's HTTP answer mode can serve a per-machine answer from posted system info without network being a precondition a volunteer can miss; (2) whether USB transport is reliably visible in sysfs (`/sys/block/*/device` path, `removable`) where udev properties were measured blind (SPIKE-universal-iso-1 §3.2); (3) whether any refusal can be shown in Hungarian without modifying the Proxmox installer squashfs. **Reverses two rulings if built — needs an operator word.** | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (ruling), CC (spike)** | +| **R-504** | **[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)** |