# Felhom scripts — Changelog ## felhom-host-install.sh v1.4.0 — appliance CPU/RAM cap passthrough (`--cores` / `--memory`) (2026-07-01) Colleague-safety batch #3 (host-install half; the mechanism is agent v0.52.0). Lets an operator cap the provisioned guest so a trial appliance on a SHARED production Proxmox doesn't pressure the colleague's existing guests. - **`--cores N` / `--memory M` (MiB)** — optional; passed through to the agent's `--selftest=provision` as `-cores`/`-memory`. `0`/unset = keep the golden's baked sizes (unchanged behaviour). New vars `CPU_CORES`/`MEM_MIB`; `usage()` header gains an "Appliance cap (optional)" group. - **Conditional passthrough** — `step_provision` builds a `cap_args` array and appends the flags to BOTH the dry-run log and the real agent call **only when set**. An agent < v0.52.0 would reject an unknown flag, so the flags are never sent unless the operator opts in (see the deploy dependency below). - **Pre-flight sanity WARN (soft, provision only)** — if `--cores` > host `nproc` or `--memory` > host `MemTotal`, `log_warn` "the cap won't protect other guests"; never `die` (the operator may know better). - **Deploy dependency:** a fresh install using `--cores`/`--memory` needs the hub artifact manifest to serve **agent ≥ v0.52.0**. - **Validated dry-run on felhom-pve:** `--cores 2 --memory 4096 --dry-run` → provision command shows `-cores 2 -memory 4096`; without the flags → neither present; `--cores 64 --memory 65536` → both WARN lines (host 4 cores / ~15771 MiB). `bash -n` + `shellcheck` clean (0 new warnings; the 2 pre-existing SC2015 in `step_verify` unchanged). ## felhom-host-install.sh v1.3.0 — `--uninstall` (clean revert) + pre-flight guards (2026-07-01) Colleague-safety batch #1+#2. Adds a first-class, guarded **`--uninstall`** teardown so an operator can cleanly back out of a trial install, plus three provision pre-flight guards that stop common footguns. Script-only; no agent/hub/controller change. - **`--uninstall` (local host teardown — no hub contact, no passphrase).** Reverses an install in the install-order's reverse: **guest → agent(unit/sudoers/binary/state/user) → pveum(ACL,token,user,role) → golden(opt-in) → state file.** Every mutation goes through `run()` so `--dry-run` prints the full plan and executes nothing. Safety: - **Ours-check:** refuses to destroy a guest that lacks the `/etc/felhom-bootstrap` bind mount (matched by the constant guest *path*, not a hardcoded `mpN` slot — on the demo host it's `mp9`), unless `--force`. - **Typed confirmation:** must type the vmid to confirm PERMANENT destruction (read from `/dev/tty`; skipped only under `--dry-run`, where nothing is destroyed). - **Other-guests guard:** if any OTHER Felhom guest remains, destroys only the target and **leaves the agent + PVE token + state in place** (re-run with `--force` to remove host-level anyway — orphans the others). - **Never removes the `sudo` package**; never contacts the hub (the host record intentionally persists). - Presence-checked + idempotent: an already-absent guest/unit/sudoers/binary/user/ACL/token/role is a tolerated skip, not an error. The `pveum role delete` runs only after its ACL grants are gone (PVE refuses to delete a referenced role). Confirmed PVE 9 ACL-delete form: `pveum acl delete / --users|--tokens --roles FelhomAgent`. - Target vmid resolves from `--vmid`, else the recorded `provisioned_vmid` (else dies). A `--vmid` that disagrees with the recorded one needs `--force`. - **`--remove-golden`:** with `--uninstall`, also delete the golden vzdump from the archive storage (`pvesm free`); otherwise it is left in place. - **Install state now records `customer_id` + `provisioned_vmid`** (new `_state_put`/`_state_get` helpers, dry-run-guarded like `_state_mark`; the `completed[]` shape is untouched) so a later `--uninstall` resolves its target automatically and safely. - **Pre-flight guards (provision mode):** - **Multi-node guard** — on a 2+-node cluster, `die` (naming the nodes) unless `--node` is explicit (new `NODE_EXPLICIT`); single-node keeps the current auto-pick. No-op under `--skip-provision`. - **Archive-storage-exists guard** — verify `--archive-storage` appears in `pvesm status` (else `die`); no-op under `--skip-provision`. - **RAM floor (WARN, never fatal)** — warn when `MemAvailable < 2048 MiB`. All three run inside `step_preflight` (before any mutation) so they also fire under `--dry-run`. - **Validated dry-run-only on felhom-pve** (single-node, live guest 9201): T-A full uninstall plan, T-C not-ours refusal (red-proof), archive-missing `die`, RAM line, other-guests detector, state round-trip; confirmed 9201 + agent + pveum + state untouched after all dry-runs. `bash -n` + `shellcheck` clean (0 new warnings vs. baseline; the 2 pre-existing SC2015 in `step_verify` are unchanged). **NOT yet live-validated (awaiting a supervised run):** a real live `--uninstall` (guest destroy + pveum removal) and the multi-node guard on an actual cluster. ## felhom-host-install.sh v1.2.0 — /dev/tty passphrase read + vmid auto-detect (2026-07-01) Two operator-experience fixes so a colleague can install online (via the hub's new "Option 1: Online install" one-liner) and onto a host that already runs a guest at 9201. - **Passphrase prompt reads from `/dev/tty`, not stdin** (`read_passphrase`). `read -rsp … < /dev/tty` makes the no-echo prompt work regardless of how stdin is wired — both download-then-run **and** `curl … | sudo bash` (where stdin is the pipe). Strictly more correct; the `--passphrase-file` path is unchanged. The passphrase is still never on argv / in logs / in the state file. - **VMID auto-detect (`--vmid` now optional-smart).** New `VMID_EXPLICIT` flag (set by `--vmid`). The pre-flight vmid guard now determines "in use" against the **`pct list` + `qm list`** id-set (LXC and VMs share the id space — more complete than the old `pct status`, which only knew LXC): - **explicit `--vmid`** → unchanged deterministic behavior: die if the id is in use unless `--force` (destructive over-provision). - **default 9201, in use, no `--force`** → **auto-pick the next free id** (scan upward from 9201 over the used-set) and **ask to confirm** from the terminal (`read … < /dev/tty`, `[y/N]`); proceed on yes, `die "no free vmid confirmed"` otherwise. Never a silent auto-pick. - **default 9201 + `--force`** → over-provision 9201 (destructive) without prompting, as before. - New helpers `used_vmids` / `_vmid_in_use` / `next_free_vmid`. `--vmid` help text + `usage()` updated. ## felhom-host-install.sh v1.1.0 — self-install the agent + fetch the golden from Gitea (2026-06-28) The script now **installs the agent itself** (the last big manual Day-0 prerequisite is gone). It fetches the agent binary + golden from Gitea generic packages and **verifies each against the hub-vouched artifact manifest** before installing/using it. BUNDLE slice; pairs with hub v0.16.0 (artifact manifest endpoint + operator UI) and felhom-agent v0.43.0 (canonical unit + publish). - **New step `5/8 agent install`** (before agent-config): resolves the manifest (`GET /api/v1/artifacts/{id}`, passphrase) + the git fetch token (from the customer's `controller.yaml` via config-retrieve — **NO new credential**); fetches `/api/packages/admin/generic/felhom-agent//felhom-agent`, **verifies sha256 vs the hub manifest** (aborts on mismatch — verify-before-use), backs up any existing binary, installs `0755 /usr/local/bin/felhom-agent`; ensures the non-root `felhom-agent` system user; installs the canonical sudoers (`0440`, `visudo -cf`-validated) + systemd unit; `daemon-reload` + enable. Idempotent: same version already installed + service active → skip. - **`--skip-provision`:** install + configure + verify the agent (incl. golden fetch+verify) but do NOT provision a guest — the agent-only path for re-installing/upgrading the agent on a host that already has live guests. Adds an agent-only `step_verify_agent` (binary + non-root service active + a `--selftest=hub` collect-report). - **New step `7/8 golden`:** local auto-discovery stays the default/fallback; otherwise fetches `/api/packages/admin/generic/felhom-golden//golden.tar.zst`, **verifies sha256**, and imports it into the archive storage's dump dir for the restore. `--force-gitea-golden` forces the Gitea path. - **Non-root agent model:** the agent now runs as `felhom-agent` with `privileged.mode: "sudo"` (was the dev/CI `direct`+root shortcut). The config is `chown`ed to the service user (0600) so the daemon can read it; `systemctl is-active` after restart is the real proof the non-root user can read the config. - **Pre-flight relaxed:** a missing agent binary is no longer fatal (step 5 installs it); the local golden requirement is deferred to step 7. - **Trust model:** checksum **trust root = the hub** (manifest), not Gitea; the fetch credential is the existing config-retrieve git token; artifacts are pinned to a version (never `:latest`). - **Secrets:** the git token is a never-logged runtime carrier (cleared on EXIT alongside the passphrase / pve-token / hub api_key); the sudoers is `0440` and `visudo -cf`-validated before install. - `bash -n` + `shellcheck` clean. ## felhom-host-install.sh v1.0.0 — Day-0 host bootstrap (provision mode) (2026-06-26) First release. A single operator-run script that automates Day-0 on a freshly-PVE-installed host: Proxmox API token → hub host enrollment (option C, single secret) → agent config → guest provision → verify. Composes proven mechanisms (the `pveum` role/token sequence, hub `POST /host-enroll`, `felhom-agent --selftest=provision`); grounded by `documentation/audits/SPIKE-day0-firstboot-handshake-2026-06-26.md`. - **7 steps, idempotent + resumable** via `/var/lib/felhom-install/state.json`: pre-flight → Proxmox token → compute grows → host-enroll → agent config → provision → verify. - **Single-secret** (the retrieval passphrase): read no-echo or from a 0600 file, never on argv/logs/state. The global operator key never touches the box. - **pveum automation:** 16-priv `FelhomAgent` role (create-or-modify), `felhom-agent@pve` user, privsep token (reuse-if-working else rotate), and **both** ACL grants applied **after** the token exists (token-remove purges the token ACL). - **Auto-discovery:** golden archive (newest `vzdump-lxc-`), PVE node name, vmbr0 bridge IP for the local-api, and the served-leaf TLS fingerprint pin. - **Safety:** pre-flight fails fast (root, PVE 9.x, local-lvm headroom, hub reachable, customer+passphrase valid via read-only `GET /config/{id}`, golden resolvable); refuses to clobber an existing `--vmid` without `--force`; `--dry-run` previews every mutation; `--preserve-from` keeps operator infra (PBS/local_api/privileged/authz) on re-deploys. - **`--mode dr`:** documented 10D stub (restore customer PBS snapshot instead of golden) — not implemented. - **Live-validated** end-to-end on `felhom-pve`: authorized wipe of demo guest 9201 → re-provision from the golden → controller config-pull + public tunnel `HTTP 200` → host-report of guest 9201 → idempotent `--resume` no-op. (One ordering bug — token ACL applied before rotation — was found and fixed during the live run.)