# felhom.eu — task reports > **Overwrite** this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in [hub/CHANGELOG.md](hub/CHANGELOG.md). --- # REPORT — Configuration-tab move + online setup command + vmid auto-detect (hub v0.28.0 / host-install v1.2.0) **Date:** 2026-07-01 · **Repo:** `felhom.eu` (`hub/` + `scripts/` + `manifests/`) Three operator-requested improvements. ## Baselines hub `v0.27.0` @ `54daaf9` → **`v0.28.0`**; `scripts/felhom-host-install.sh` `v1.1.0` → **`v1.2.0`**. Trunk-based `main`. Commits: `562c0df` (Parts A/B/C code), `629196c` (hub image manifest), `b77df3c` (script header version). ## Part A — global settings moved to the Configuration tab The two **global** cards ("Managed updates — global floor", "Day-0 artifacts — agent & golden") were on the Customers page; they now live on Configuration. - `internal/web/server.go` — `handleConfiguration` supplies `GlobalFloor` + `Artifacts` + `CSRFField`; the two POST routes moved to `/configuration/global-floor` + `/configuration/artifacts`. - `internal/web/configs.go` — `handleSetGlobalFloor`/`handleSetArtifacts` redirects `/configs?flash=…` → `/configuration?flash=…` (5 redirects); `handleConfigList` data trimmed to `{Customers, ActiveNav, Flash}` (dropped the now-unused `GlobalFloor`/`Artifacts`/`CSRFField`; `globalFloor` still computed for per-customer effective-floor). - `templates/configuration.html` — the two cards + their 5 flash banners added. `templates/configs.html` — cards + flashes removed; back to customer list + "Add Customer". - `internal/web/render_test.go` — updated: asserts the global cards are **gone** from configs.html and **present** (with `/configuration/*` actions + a flash) on configuration.html. ## Part B — online setup command + serving fix + passphrase hardening - **B1 (`scripts/…`):** `read_passphrase` now reads `read -rsp … < /dev/tty` — the no-echo prompt works whether the script is a local file or piped to bash. `--passphrase-file` unchanged; passphrase still never on argv/logs/state. - **B2 (serving):** `https://felhom.eu/scripts/felhom-host-install.sh` was **404** — the webpage nginx doc-root is `.../current/website` and git-sync only synced `/website/`. Fixed in `manifests/webpage.yaml`: added `/scripts/` to the git-sync sparse-checkout + an nginx `location /scripts/ { root .../current; default_type text/plain; }`. Single source of truth (the repo's `/scripts`), no duplicated copy. **Now 200** (verified live, serves `SCRIPT_VERSION="1.2.0"`). - **B3 (`customer_unified.html`):** new **"Option 1: Online install (recommended)"** — download-then-run (`curl -fsSL … -o … && sudo bash … --customer-id {{.CustomerID}}`) with a copy button; the passphrase is **not** templated (entered at the prompt). Former local-file command demoted to Option 2, debug curl to Option 3. Rationale note: fetched over TLS, inspect-before-run — not `curl | sudo bash`. ## Part C — vmid auto-detect (`scripts/…`) - New `VMID_EXPLICIT` flag (set by `--vmid`). The pre-flight guard now determines "in use" against the **`pct list` + `qm list`** id-set (LXC + VMs share the space) via new helpers `used_vmids` / `_vmid_in_use` / `next_free_vmid`. - **explicit `--vmid` + in use** → deterministic: die unless `--force`. - **default 9201 + in use + no `--force`** → auto-pick the next free id (scan upward) + **confirm** from `/dev/tty` (`[y/N]`); `die "no free vmid confirmed"` on decline. Never silent. - **default 9201 + `--force`** → destructive over-provision, no prompt. - `--vmid` help + `usage()` range updated (and usage now reaches `-h, --help`). ## Green gates - Hub: `go build ./... && go vet ./... && go test ./...` — all `ok` (incl. the updated `TestTemplates_FloorRender`). - Script: `bash -n` clean. `shellcheck` (koalaman container on the build server): my additions clean; the only output is **2 pre-existing** SC2015 *info* notices (lines 833/843, unrelated verify functions — present in HEAD, not touched). Local unit test of `next_free_vmid` (3 cases) passes. ## Deploy hub image `:0.28.0` built+pushed on 180; `manifests/hub.yaml` bumped; ArgoCD `felhom` synced → **Synced/Healthy**; `deploy/hub` on `:0.28.0`. `felhom-webpage` rollout-restarted so it re-clones with the new sparse-checkout (`/scripts/`) + new nginx config. ## Live proofs - **Part A (claude-in-chrome, operator-authed):** Configuration tab shows both cards with current values (global floor v0.87.0; agent 0.43.0 / golden 0.85.1); "Save global floor" → `/configuration?flash=floor_set` with the green "Controller-version floor saved." banner (persist + flash on Configuration). The Customers page shows only the customer list + "Add Customer" (cards gone; footer `0.28.0`). - **Part B (curl + browser):** `curl https://felhom.eu/scripts/felhom-host-install.sh` → **HTTP 200**, serves `SCRIPT_VERSION="1.2.0"`. The customer page (`demo-felhom`) shows Option 1 online install with the id filled + copy button, Option 2 local, Option 3 debug — passphrase not in Option 1. - **Part C (SSH, felhom-pve):** the deployed script's own vmid functions against real `pct`+`qm`: used-set `{9000,9001,9100,9201,9999}`, 9201 in use → `next_free_vmid`=**9202** (verified genuinely free). The actual guard block (extracted from the served script): explicit+no-force → deterministic die; default+no-force → logs "next free vmid is 9202" + prompts on `/dev/tty` → clean `die "no free vmid confirmed"` on no-confirm; default+`--force` → destructive-over-provision warning, no prompt. (A fully interactive real run isn't automatable now that both prompts read `/dev/tty`; the guard was proven with real host state + the real code path.) ## Observations - The `/dev/tty` reads make the script prompts unattended-proof by design — so an automated run past the prompts isn't possible; Part C was proven via the real functions/guard against live `pct`+`qm`. - shellcheck shows 2 pre-existing SC2015 *info* notices unrelated to this change (left untouched — fixing them would alter unrelated verify logic). - The customer page still renders the retrieval passphrase in the debug Option 3 + the credential box (pre-existing); the new Option 1 deliberately does not. ## Remaining audit follow-ups (unchanged) Controller-side geo intent sync; a read-only reported-vs-desired "Show Diff"; the cosmetic `controllerURL` cleanup in `configs.go`.