GL-2: --mode appliance|byo install profile (host-install v1.10.0) + harness

Mandatory install profile (no default): byo = a host the operator does not
own. Break-glass gated OFF at its call site in byo (root@pam never touched),
mandatory --cores/--memory, argv-time refusals (--enable-oob/--rotate-recovery,
non-9.x PVE, missing --acl-storages), host-mutation disclosure + typed-hostname
ack, byo config asserts (lan_resolver/wg_tunnel/oob off; byo flips the
lan_resolver write default to off), pool+ACL verify asserts in BOTH modes (R2),
--preflight-only (no state, PASS/FAIL verdict), resume mode-mismatch refusal,
FELHOM_INSTALL_STATE_DIR harness override.

NEW scripts/hostinstall-mode-harness.sh: static refusal matrix C1-C4 + grep
invariants + PVE tier (C5 + A/B dry transcripts). 16/16 PASS on felhom-pve
(C5 live); red-proofs RP-1..RP-3 run->fail->revert. shellcheck clean at
severity=warning. Docs: day0-install SC.5 byo section + trust model; REUSE row;
CONTEXT + REPORT. Live drill = GL-6 (supervised); STOP honored (no non-dry run).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-07 20:26:32 +02:00
parent 1a1e42addb
commit 02d63ed070
7 changed files with 687 additions and 60 deletions
+65 -4
View File
@@ -14,6 +14,12 @@
>
> Scope: a **shared** Proxmox box (the colleague's-box model) — a box that already runs, or will run,
> non-Felhom guests. A dedicated box is the same procedure minus the sizing caution.
>
> **v1.10.0 (GL-2): every install now REQUIRES `--mode appliance|byo`** — there is no default. Use
> `appliance` for a box Felhom owns/manages end-to-end (this guide's drilled path, unchanged apart
> from the flag); use `byo` for a host the operator does NOT own (the pilot's own PVE) — see §C.5.
> The byo profile is implemented + statically validated but **not yet drill-validated** (that is the
> supervised GL-6 run).
## Who does what
@@ -135,7 +141,7 @@ Notes:
```bash
curl -fsSO https://felhom.eu/scripts/felhom-host-install.sh
chmod +x felhom-host-install.sh
./felhom-host-install.sh -h | head -3 # sanity: must print v1.9.1 (or newer) — the version this guide was drilled against
./felhom-host-install.sh -h | head -3 # sanity: must print v1.10.0 (or newer) — older scripts don't know --mode
```
### C.2 Preview (recommended)
@@ -144,7 +150,7 @@ chmod +x felhom-host-install.sh
read-only and asks for the passphrase):
```bash
./felhom-host-install.sh --customer-id <CUSTOMER-ID> --vmid <VMID> \
./felhom-host-install.sh --customer-id <CUSTOMER-ID> --mode appliance --vmid <VMID> \
--cores 2 --memory 4096 \
--force-gitea-golden --acl-storages "local local-lvm" \
--dry-run
@@ -153,7 +159,7 @@ read-only and asks for the passphrase):
### C.3 The canonical shared-box install command
```bash
./felhom-host-install.sh --customer-id <CUSTOMER-ID> --vmid <VMID> \
./felhom-host-install.sh --customer-id <CUSTOMER-ID> --mode appliance --vmid <VMID> \
--cores 2 --memory 4096 \
--force-gitea-golden --acl-storages "local local-lvm"
```
@@ -163,6 +169,7 @@ Placeholders and flags — what and why:
| Flag | Value | Why |
|---|---|---|
| `--customer-id` | the Part A customer ID | keys every hub call |
| `--mode` | `appliance` or `byo` | REQUIRED, no default. `appliance` = a Felhom-owned/managed box (this section). `byo` = a host the operator does not own — use §C.5's command instead |
| `--vmid` | the free vmid from Part B | ALWAYS pass it explicitly — deterministic, and recorded in the install state for a later `--uninstall` |
| `--cores 2 --memory 4096` | the appliance cap | protects the other guests on a shared box; size to roughly half the host if the box is small (cap ≤ host resources) |
| `--force-gitea-golden` | — | a fresh box has no local golden archive; fetch it from Gitea and verify its sha256 against the hub manifest (this is the normal customer path) |
@@ -196,7 +203,57 @@ Expected duration: minutes; dominated by the two Gitea downloads (agent ~20 MB,
the customer's uplink.
If a step fails: read the error (they are specific), fix the cause, re-run the SAME command with
`--resume` appended.
`--resume` appended (with the SAME `--mode` — the script refuses a mode flip on a half-done install).
### C.5 BYO hosts (`--mode byo`) — installing on a Proxmox server the operator does NOT own
**Trust model.** On a BYO host the roles invert: the box owner is root and stays root; Felhom is the
guest. The installer therefore (a) never touches the owner's credentials — the break-glass step
(4b/8, root@pam reset + hub vault) is skipped entirely, nothing is vaulted; (b) treats the owner's
workloads as the thing to protect — the CPU/RAM caps are mandatory, they are the only
noisy-neighbor containment there; (c) refuses anything host-invasive that the appliance profile
allows (`--enable-oob`, `--rotate-recovery`, unvalidated PVE majors, ACL grants on storages the box
doesn't have); and (d) asserts the written agent config keeps `lan_resolver`/`wg_tunnel`/`oob` OFF
— the agent must not take over the owner's DNS or open tunnels. Recovery on a BYO box = the owner's
own console access, by design.
**Recommended first run — preflight only** (all step-1 checks incl. the byo gates, an explicit
PASS/FAIL verdict, exit 0/1, writes nothing — re-runnable any number of times):
```bash
./felhom-host-install.sh --customer-id <CUSTOMER-ID> --mode byo --vmid <VMID> \
--cores <N> --memory <MiB> \
--force-gitea-golden --acl-storages "local local-lvm" \
--preflight-only
```
**The install command** (same flags, minus `--preflight-only`):
```bash
./felhom-host-install.sh --customer-id <CUSTOMER-ID> --mode byo --vmid <VMID> \
--cores <N> --memory <MiB> \
--force-gitea-golden --acl-storages "local local-lvm"
```
What byo does differently (everything else matches C.4's eight steps):
- `--cores` AND `--memory` are **required** — size them WITH the owner (the guest must never starve
the owner's guests). `--acl-storages` must name storages that actually exist on the box, or the
preflight dies naming the missing ones.
- Preflight additionally prints what already listens on the host's `:53` (informational — Felhom
never configures DNS on a byo box) and the existing-guest count.
- At the end of preflight the script prints the **complete host-mutation list** (pveum
roles/user/token/ACL/pool, agent user/binary/unit/sudoers/config/state, the watchdog + guarded
wrappers, the guest + golden) and requires typing the host's **short hostname** to proceed —
show this list to the box owner; it is the consent artifact.
- Step 4b/8 (break-glass) is skipped; the verify step asserts pool membership + the scoped ACL
grants landed (both modes do this from v1.10.0).
### C.6 Post-hoc mode note for pre-v1.10.0 installs
Boxes installed by ≤ v1.9.1 have no recorded mode; their state file simply predates it. `--resume`
on such a box accepts whichever `--mode` you pass — pass the mode that matches how the box is
actually operated (existing Felhom-owned boxes: `appliance`).
---
@@ -306,6 +363,10 @@ roles/ACL/token/user, the pool (if empty), the install state file.
| Symptom | Cause | Fix |
|---|---|---|
| dies immediately: "--mode is required" | v1.10.0+ has no default profile | add `--mode appliance` (Felhom-owned box) or `--mode byo` (owner's box, §C.5) |
| dies immediately: "byo mode requires explicit --cores and --memory" | byo caps are mandatory | size the caps with the box owner and pass both |
| dies: "install started as X; resume with --mode X" | `--resume` with the other mode | resume with the recorded mode, or `--uninstall` and start over |
| byo dies: "acl storage(s) not found on this box" | `--acl-storages` names a storage the box lacks | pass the box's real storages (check `pvesm status`) |
| step 1 dies: "this is a N-node cluster" | multi-node cluster | re-run with `--node <name>` |
| step 5 dies: "hub artifact manifest has no agent version" | Day-0 manifest unset/incomplete | Part A.3 — set it in the operator UI |
| step 5 dies: "no git token in controller.yaml" | customer created without git credentials | Part A.2 — add `git.username`/`git.token`, regenerate config |