# 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); the scripts history lives in [scripts/CHANGELOG.md](scripts/CHANGELOG.md). ## TASK GL-2 — BYO-host install profile (`--mode appliance|byo`) — host-install v1.10.0 (2026-07-07) **Baseline confirmed:** felhom.eu @ `1a1e42ad`, `SCRIPT_VERSION="1.9.1"` → **`1.10.0`**. Implementation + static/dry validation only per spec §0 — **the live provision/uninstall runs are GL-6 (operator-supervised)**; the §13 STOP was honored (zero non-dry installer invocations anywhere). ### Files created / modified (single repo, `main`) - `scripts/felhom-host-install.sh` — v1.10.0 (Parts 1–3; see scripts/CHANGELOG.md for the feature list) - `scripts/hostinstall-mode-harness.sh` — NEW (Part 4) - `documentation/runbooks/day0-install.md` — `--mode` in every command, new §C.5 (byo command + trust-model paragraph + preflight-only), §C.6 (pre-v1.10.0 resume note), 4 new troubleshooting rows - `scripts/CHANGELOG.md`, `REUSE.md` (install-profile-gate pattern row), `CONTEXT.md`, this file ### Spec deviations / judgment calls (each verified against the real code) 1. **`--mode` collision:** v1.9.1 already used `--mode provision|dr`. Resolved: `appliance|byo` are the only install values, `provision` retired with a specific die, the `dr` stub dispatch kept verbatim (the documented 10D seam). 2. **Spec §2 was wrong about the lan-resolver**: the config write did `setdefault('lan_resolver', {"enable": True})` — default-ON, so the spec's byo assert would have died on every byo install. Fixed by flipping the byo DEFAULT to off (appliance untouched); the assert still dies if a `--preserve-from` config carries true, which is the intended refusal. 3. The byo skip line reads `break-glass (step 4b) SKIPPED (byo: …)` — NOT the spec's literal `"4b/8 …"` text, because Scenario B / harness H-B forbid the string `4b/8` in the byo transcript (an internal spec contradiction; the greppable invariant won). 4. `--preflight-only` skips the byo typed ack (prints the disclosure, logs the skip) — nothing will be mutated, and a mandatory prompt would break its re-runnable/scripted purpose. 5. `documentation/pilot/GO-LIVE-PACKAGE.md` is not in the repo and no operator copy arrived with the task — the G2/G4/G5 → "implemented, awaiting GL-6" flip is recorded in CONTEXT.md; add the doc when supplied. ### Harness results Static tier (Windows Git Bash AND felhom-pve, identical): **bash -n, C1/C1b/C1c, C2a/C2b, C3/C3b/C3c, C4, C6-static, INV-1 (one appliance-gated break-glass call site), INV-2 (chpasswd unreachable outside step_break_glass), INV-3 (--mode in usage) — all PASS.** PVE tier (felhom-pve, read-only, temp `FELHOM_INSTALL_STATE_DIR`): **C5 PASS live** (bogus `--acl-storages` entry → die naming it, pre-hub, pre-passphrase). H-A/H-B dry transcripts ran with the out-of-band demo passphrase (file→file, never printed) but **die at step 5 with rc=1 — a customer-data blocker, not a code bug**: demo-felhom's controller.yaml serves EMPTY `git.username`/`git.token`. The transcript span through the break-glass gate was validated manually instead: appliance transcript contains `4b/8`; byo transcript contains **no** `4b/8`/`chpasswd`/`recovery-credential` and **does** contain the disclosure block, the dry ack line, `acl storages all present`, and the host-DNS `:53` line (it correctly showed the live dnsmasq). `--preflight-only` proven live both ways: PASS verdict + rc 0 + **zero state entries**; bogus-storage run → specific die + `PRE-FLIGHT FAIL (exit 1)` verdict. **Red-proofs (run → fail → revert, against mutated scratch copies — the repo file was never mutated):** - RP-1 un-gated the break-glass call site → INV-1 FAIL (13/16). Reverted (scratch deleted). - RP-2 dropped the byo caps requirement → C1/C1b/C1c FAIL (11/16). Reverted. - RP-3 dropped the resume mode-mismatch check → C4 FAIL (13/16). Reverted. ### §9 rule 9 — the `_state_mark` dry-run finding Verified in code: **both** `_state_mark` and `_state_put` begin with `$DRY_RUN && return 0` — dry runs write nothing. Belt on top: the harness points `FELHOM_INSTALL_STATE_DIR` (new, harness-only env override — none existed) at a throwaway temp dir for EVERY invocation, so even non-dry refusal cases can never touch a live `state.json`. Confirmed live: felhom-pve's real state.json was byte-untouched (it only ever saw the temp dir) and the preflight-only runs left 0 entries. ### Shellcheck v0.10.0 (build server 180 + felhom-pve; not installed locally). **Clean at `--severity=warning`** on both scripts. Full-severity: 2 pre-existing SC2015 *info* notes on untouched v1.9.1 lines (A&&B||C in step_verify) — triaged, not refactored (spec rule 2); 1 new SC2154 false positive on the EXIT trap (assignment inside the trap string) — targeted disable comment. ### NOT yet live-validated — awaiting supervised GL-6 - The full appliance AND byo installs end-to-end (rc=0 transcripts blocked today by the demo-felhom git-creds gap below). - The uninstall → re-provision drill under both modes. - C7: the verify pool/ACL asserts firing on real drift (needs a deliberately-broken live box). - The byo typed-hostname ack answered interactively (only its dry/preflight-only branches ran). ### Observations (documented, not acted on) - **demo-felhom has EMPTY `git.username`/`git.token`** in its served controller.yaml — any step-5 (re)install for that customer dies until real Gitea read creds are set (operator; ties into the standing "scope down + rotate the package-WRITE git token" security follow-up). - felhom-pve's live install predates `_state_put` — its state.json has only `completed` (no `customer_id`/`provisioned_vmid`/`mode`); a future `--uninstall` there needs an explicit `--vmid`, and §C.6 of the runbook covers the missing-mode resume. - A `--resume` that skips preflight never records `mode` into an old state file (it only writes during a full preflight) — harmless (C4 only fires when a mode IS recorded), noted for GL-6. - The live host-DNS line on felhom-pve shows the agent's own dnsmasq (lan_resolver is ON there — correct for an appliance; it is exactly what byo now refuses).