host-install v1.3.0: --uninstall (clean revert) + pre-flight guards
Colleague-safety batch #1+#2 (script-only). Adds a guarded, dry-run-aware --uninstall local host teardown (guest -> agent -> pveum(ACL,token,user,role) -> golden(opt-in) -> state file), with ours-check on the /etc/felhom-bootstrap mount, typed vmid confirm, other-Felhom-guests safe-skip, and idempotent tolerate-absent. Records customer_id + provisioned_vmid into the install state (_state_put/_state_get) so uninstall resolves its target automatically. Adds three provision pre-flight guards: multi-node (--node required), archive-storage exists, and a RAM-floor WARN. Validated dry-run-only on felhom-pve. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,104 +1,99 @@
|
||||
# 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).
|
||||
> **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).
|
||||
|
||||
---
|
||||
|
||||
# REPORT — Configuration-tab move + online setup command + vmid auto-detect (hub v0.28.0 / host-install v1.2.0)
|
||||
# REPORT — `felhom-host-install.sh --uninstall` (clean revert) + pre-flight guards (host-install v1.3.0)
|
||||
|
||||
**Date:** 2026-07-01 · **Repo:** `felhom.eu` (`hub/` + `scripts/` + `manifests/`)
|
||||
**Date:** 2026-07-01 · **Repo:** `felhom.eu` (`scripts/` only)
|
||||
|
||||
Three operator-requested improvements.
|
||||
Colleague-safety batch **#1 (`--uninstall`) + #2 (pre-flight guards)**. Script-only. A first-class,
|
||||
guarded local host-teardown mode so an operator can cleanly back out of a trial install, plus three
|
||||
provision pre-flight guards. Specced, built, and validated **dry-run-only** (the live `--uninstall` is a
|
||||
supervised step and was deliberately NOT run — see the STOP list).
|
||||
|
||||
## Baselines
|
||||
## 1. Confirmed baseline
|
||||
|
||||
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).
|
||||
| Repo | `main` @ commit | Current | → Target |
|
||||
|------|-----------------|---------|----------|
|
||||
| felhom.eu (`scripts/felhom-host-install.sh`) | `83c4f7f7` | script **v1.2.0** | **v1.3.0** |
|
||||
|
||||
## Part A — global settings moved to the Configuration tab
|
||||
No agent/controller/hub change in this task.
|
||||
|
||||
The two **global** cards ("Managed updates — global floor", "Day-0 artifacts — agent & golden") were on
|
||||
the Customers page; they now live on Configuration.
|
||||
## 2. Files changed
|
||||
|
||||
- `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.
|
||||
- `scripts/felhom-host-install.sh` (v1.2.0 → **v1.3.0**) — the only code file.
|
||||
- `scripts/CHANGELOG.md` — v1.3.0 entry (newest on top; this is where the v1.2.0 entry lives).
|
||||
- `REPORT.md` — this file (overwritten).
|
||||
|
||||
## Part B — online setup command + serving fix + passphrase hardening
|
||||
## 3. Commit hash(es)
|
||||
|
||||
- **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`.
|
||||
- `<COMMIT_HASH>` on `main` (filled in after push; see §Served-copy check).
|
||||
|
||||
## Part C — vmid auto-detect (`scripts/…`)
|
||||
## 4. What was implemented
|
||||
|
||||
- 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`).
|
||||
**Part 1 — install state records vmid + customer.** New `_state_put KEY VALUE` / `_state_get KEY`
|
||||
helpers (python3, dry-run-guarded like `_state_mark`, `completed[]` shape untouched). `step_preflight`
|
||||
now records `customer_id`; `step_provision` records `provisioned_vmid` on success (dry-run logs only).
|
||||
|
||||
## Green gates
|
||||
**Part 2 — `--uninstall` mode** (early top-level dispatch, root-required, no `--customer-id`, no hub
|
||||
contact, no passphrase). `run_uninstall` reverses install order: **guest → agent(unit+.bak / sudoers /
|
||||
binary+.bak / state dir / user) → pveum(ACL user, ACL token, token, user, role) → golden(opt-in) →
|
||||
state file.** Every mutation via `run()` (dry-run prints, executes nothing). Guards: ours-check on the
|
||||
`/etc/felhom-bootstrap` bind mount (matched by the constant guest *path*, not a slot), typed vmid
|
||||
confirmation (`/dev/tty`, skipped only in dry-run), other-Felhom-guests safe-skip (keeps host-level in
|
||||
place unless `--force`), state-mismatch `--force` gate, and idempotent tolerate-absent on every object.
|
||||
`--remove-golden` opt-in deletes the golden vzdump (`pvesm free`). Never removes the `sudo` package;
|
||||
never contacts the hub. New helper `felhom_guests()` enumerates Felhom guests by the bootstrap mount.
|
||||
|
||||
- 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.
|
||||
**Part 3 — pre-flight guards** (all inside `step_preflight`, before any mutation → fire under dry-run):
|
||||
multi-node guard (`die` unless `--node` explicit via new `NODE_EXPLICIT`; no-op single-node / under
|
||||
`--skip-provision`), archive-storage-exists guard (`pvesm status --storage`; no-op under
|
||||
`--skip-provision`), RAM floor WARN (`MemAvailable < 2048 MiB`, never fatal).
|
||||
|
||||
## Deploy
|
||||
Confirmed the PVE 9 ACL-delete syntax live before wiring it:
|
||||
`pveum acl delete <path> --users|--tokens <x> --roles <role>`.
|
||||
|
||||
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.
|
||||
## 5. Green gate — `bash -n` + `shellcheck`
|
||||
|
||||
## Live proofs
|
||||
- `bash -n scripts/felhom-host-install.sh` → **OK** (locally + on felhom-pve).
|
||||
- `shellcheck v0.10.0` (fetched static binary on the build server; shellcheck isn't installed on any
|
||||
box): **HEAD v1.2.0 baseline = 2× SC2015 (info)**; **v1.3.0 = 2× SC2015 (info)** at lines 1128 & 1138
|
||||
— both in the *pre-existing* `step_verify` (`A && B || C`), untouched by this task. **0 new warnings.**
|
||||
|
||||
- **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.)
|
||||
## 6. Tests → §7 mapping (dry-run transcripts / read-only live checks on felhom-pve, single-node, live guest 9201)
|
||||
|
||||
## Observations
|
||||
| # | Scenario | Result |
|
||||
|---|----------|--------|
|
||||
| T-A | full clean-uninstall plan (`--uninstall --vmid 9201 --dry-run`) | **PASS** — ours-check passes, prints `pct config 9201`, would-prompt for typed confirm, then the full plan: `pct stop/destroy 9201` → agent removal (unit + all `.bak-*`, sudoers, binary + all `.bak-*`, `rm -rf /var/lib/felhom-agent`, `userdel`) → `pveum acl delete` (user, then token) → token remove → user delete → **role delete last** → golden skipped → state file rm. All `[DRY-RUN]`. `sudo` package NOT touched; hub note printed. |
|
||||
| T-B | other-guests detector | **PASS** — `felhom_guests()` enumeration prints only `9201` (only Felhom guest on the host). Skip-branch code-reviewed (2nd Felhom guest not available live). |
|
||||
| T-C | not-ours refusal (**red-proof**) | **PASS** — `--uninstall --vmid 9001 --dry-run` (9001 exists, no bootstrap mount) → `die "vmid 9001 does not look like a Felhom-provisioned guest … Refusing to destroy. Pass --force to override."`, exit 1, dies before any `run()`. Nonexistent id `987654` → safe refusal ("absent and is not the recorded provisioned_vmid … --force"). |
|
||||
| T-D | multi-node guard | **PASS (single-node)** — `node: demo-felhom (auto)`, guard is a no-op on the 1-node host; multi-node `die` code-reviewed. Live multi-node validation deferred (see STOP). |
|
||||
| T-E | storage / RAM guards | **PASS** — `--dry-run --archive-storage nonesuch` → `die "archive storage 'nonesuch' not found (pvesm status)…"` before the passphrase prompt; normal dry-run prints the RAM line (`free RAM: ~12251 MiB`; the `<2048` WARN branch is code-reviewed — the box has 12 GiB free). |
|
||||
| T-F | dry-run executes nothing | **PASS** — after all dry-runs: `pct status 9201` = running, `felhom-agent` active, binary present, `pveum` user + role present, `state.json` unchanged (no `provisioned_vmid` added — dry-run writes nothing). |
|
||||
| Part 1 | `_state_put`/`_state_get` round-trip | **PASS** — isolated temp-file test: writes top-level `customer_id`/`provisioned_vmid`, preserves `completed[]`, reads both back. |
|
||||
|
||||
- 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.
|
||||
## 7. Served-copy version check (after push)
|
||||
|
||||
## Remaining audit follow-ups (unchanged)
|
||||
```
|
||||
curl -fsSL https://felhom.eu/scripts/felhom-host-install.sh | grep SCRIPT_VERSION
|
||||
# expect: SCRIPT_VERSION="1.3.0"
|
||||
```
|
||||
→ `<RESULT after push>`
|
||||
|
||||
Controller-side geo intent sync; a read-only reported-vs-desired "Show Diff"; the cosmetic `controllerURL`
|
||||
cleanup in `configs.go`.
|
||||
## 8. NOT yet live-validated — awaiting a supervised run
|
||||
|
||||
- A **real live `--uninstall`** (actual guest destroy + `pveum` role/user/token/ACL removal + `userdel`
|
||||
+ state wipe). Deliberately not run per the TASK STOP — do it on the colleague's box or a throwaway
|
||||
scratch guest, never the demo guest 9201.
|
||||
- The **multi-node guard** on an actual 2+-node cluster (felhom-pve is single-node).
|
||||
- The other-Felhom-guests safe-skip branch with a second live Felhom guest present.
|
||||
|
||||
## 9. Observations (noticed, NOT acted on)
|
||||
|
||||
- In `--dry-run`, `run_uninstall` prints `[OK] guest <vmid> destroyed` after the `[DRY-RUN] pct destroy`
|
||||
lines; the trailing `[DRY-RUN]: nothing above was actually executed.` clarifies it, and this mirrors
|
||||
the existing dry-run success-logging pattern — left as-is (minimal change).
|
||||
- Colleague-safety batch items #3 (CPU/mem cap, agent-side) and #4 (pool-scoped ACL) are separate specs;
|
||||
not touched here.
|
||||
|
||||
Reference in New Issue
Block a user