Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
21 KiB
Felhom scripts — Changelog
install_skills.py — new: Claude Code skills installer (2026-07-03)
Installs skills/*/SKILL.md (felhom-build-deploy, felhom-ui-design, felhom-testing) into
~/.claude/skills/ as Windows junctions (mklink /J) so repo edits are live immediately; falls
back to a full copy if junction creation fails or isn't followed (copy mode prints a re-run
reminder). Idempotent — re-runs detect a correct junction and leave it. Verified: junctions ARE
followed by Claude Code skill discovery (fresh-session probe found all three).
reuse_refs_check.py — new gate: REUSE.md citation checker (2026-07-03)
Staleness defense for the new per-repo REUSE.md reuse maps. Takes repo roots as argv, extracts
every cited *.go/*.py/*.html/*.css/*.yml/*.yaml/*.sh path (slash-containing tokens only — bare
filenames are conventions, not citations), verifies each exists; prints offenders, non-zero exit on
any missing path. Symbols are spot-verified by the reviewer, not this script.
Usage: python scripts/reuse_refs_check.py <repo-root> [...].
felhom-host-install.sh v1.8.0 — install the guarded-mkfs wrapper (Impl-1 Part B) (2026-07-01)
Companion to felhom-agent v0.54.0 (format-safety foundation). During agent install, fetch + install the guarded-mkfs wrapper so the agent's format path is safe on any box.
- New step in
step_agent_install: fetchconfigs/felhom-mkfs-guarded.shfrom Gitea,bash -nvalidate,install -m0755 -o root -g root→/usr/local/sbin/felhom-mkfs-guarded. Installed BEFORE the sudoers (which now allowlists ONLY the wrapper, not rawmkfs.*), so the ordering is gap-free. - The agent v0.54.0 sudoers (fetched by the same step) drops the raw
mkfs.ext4 -F /dev/* / mkfs.xfs -f /dev/*allowlist and permits onlyfelhom-mkfs-guarded /dev/* *, plus read-onlypvs/zpoolfor the agent's unclaimed-disk guard. No other host-install change. bash -n+shellcheckclean (0 new warnings). Live-validated on felhom-pve (agent v0.54.0 deploy): wrapper refuses the OS disk + an LVM-PV partition, raw mkfs is sudo-denied, an unclaimed throwaway disk formats; the agent guard's sudo reads (pvs/lsblk/zpool) all work as the felhom-agent user.
felhom-host-install.sh v1.7.0 — 3b-fix: Datastore.Audit box-wide (restore drive visibility) (2026-07-01)
Fixes a regression the v1.6.0 pool-scoped ACL introduced: Datastore.Audit was placed in the
per-storage Store role (granted only on local/local-lvm/felhom-pbs), which excluded the
enrolled removable drives felhom-usb/felhom-flash. The agent enumerates storage via
ListStorage/NodeStorage (both gated by Datastore.Audit — internal/storage/observe.go), so it
could no longer SEE the drives → false "Meghajtó leválasztva" (drive detached) alerts + drives absent
from the agent-view. (The v1.6.0 swap's "felhom-usb → 403" was mis-read as blast-radius success;
felhom-usb is Felhom's OWN customer drive, not an out-of-scope object.)
Datastore.Auditmoved from Store → Base (PVE_PRIVS_BASEnow"Sys.Audit SDN.Use Datastore.Audit";PVE_PRIVS_STOREnow"Datastore.Allocate Datastore.AllocateSpace"). Audit is read-only metadata, so box-wide Audit restores visibility of ALL storages (incl. dynamically-enrolled drives — no per-drive grant ever needed) while the write privs (Allocate/AllocateSpace) stay per-storage → write/allocate blast-radius containment is UNCHANGED. Confirmed at source: the agent creates no PVE storage (noPOST /storage/pvesm add); drives are dir-storages it observes + mounts via host ops, so they need only Audit, never Allocate.apply_scoped_aclreordered Base-before-Store (role + grant) so a RE-APPLY on a live box addsAudit@/before Store drops its per-storage Audit → gap-free (the agent never loses enumeration).remove_scoped_acl/--uninstall/--rescope-acloperate by role NAME and inherit the corrected privs automatically (no other change).- Live-repaired felhom-pve (two
pveum role modify, Base first — no agent stop/restart): drives reappeared (agent-view 3→5 storages), detach alerts cleared. Re-tested under the scoped token: drives readable (was 403), write-containment intact (vzdump→felhom-usb still 403; out-of-pool guest 403), PBS Store grant unchanged.bash -n+shellcheckclean (0 new warnings). - NOT physically run (source-confirmed, no
Datastore.Allocatein the path): a brand-new-drive UI enrollment (needs a spare USB) — the host-ops/Audit path is unchanged from pre-3b.
felhom-host-install.sh v1.6.0 — pool-scoped token ACL (3-role) + --rescope-acl retrofit (2026-07-01)
Colleague-safety batch #4 phase b (script half; agent half = v0.53.0). Moves the agent token's dangerous
privileges off / (which spanned every guest + storage) to /pool/felhom + /storage/<targets>, so on
a shared box the token can only touch Felhom's own guests + storages. Validated by
documentation/audits/SPIKE-pool-scoped-acl-2026-07-01.md (PASS) — implemented here.
- 3-role scoped ACL (
step_tokenrewrite). Replaces the singleFelhomAgentrole granted at/with three roles, each granted to BOTH the user AND the token (privsep intersection):FelhomAgentGuest(VM.*+Pool.Allocate) @/pool/felhom;FelhomAgentStore(Datastore.*) @ each ofPVE_STORAGES(defaultlocal local-lvm felhom-pbs— the offsite PBS MUST be included, SPIKE residual #1;--acl-storagesoverrides);FelhomAgentBase(Sys.Audit SDN.Use) @/. Helpersapply_scoped_acl/remove_scoped_acl/_grant/_ensure_role. - Pool before token.
ensure_felhom_poolruns at the top ofstep_token(always, incl.--skip-provision) so/pool/felhomexists before it's granted on. - Re-install safety.
step_tokenalso removes the pre-3b broad/grant +FelhomAgentrole if present (remove_old_broad_acl, tolerate-absent), so a re-install can't leave the old grant unioned with the scoped one. The post-provisionpool_add_guestis gone (the agent'srestore --poolmakes the guest a member atomically — v0.53.0). --rescope-aclretrofit (new mode, mirrors--adopt-pool): migrate an existing install — ensure the pool + guest membership, apply the scoped grants, THEN remove the old broad grant (add-before- remove: the token is never grant-less mid-migration). Prints the "now deploy agent ≥ v0.53.0" ordering reminder. Idempotent + dry-run-aware. SUPERVISED (run with the agent stopped — the scoped ACL and the pool-param agent are mutually dependent; §13 of the task).--uninstallnow removes the scoped grants + 3 roles AND the pre-3b broad grant/role (both tolerate-absent → works on either shape), keeping the pool delete-if-empty (v1.5.0).- Validated on felhom-pve (dry-run): T-A fresh install (pool-before-token, 3 roles once, scoped
grants incl.
/storage/felhom-pbs),--rescope-acl(add scoped → remove oldFelhomAgent), T-F uninstall (old-shape cleanup + pool not-empty skip).bash -n+shellcheckclean (0 new warnings). The live rescope + agent swap is the supervised STOP — not run here.
felhom-host-install.sh v1.5.0 — felhom pool by default + --adopt-pool retrofit + uninstall teardown (2026-07-01)
Colleague-safety batch #4 phase a. Every Felhom-managed guest now joins a dedicated felhom pool
for fleet uniformity (and as the environment the later pool-scoped ACL — 3b — will spike against). All
pool ops run as root@pam from the installer, so there is NO agent/token/ACL change and zero
permission-model risk (PVE_PRIVS untouched; the FelhomAgent token stays scoped at /).
- New
felhompool default.step_provisioncallsensure_felhom_pool(create if absent, idempotent) and, after a successful provision, adds the guest viapveum pool modify felhom -vms <vmid>(skip-if-already-member). New helperspool_exists/pool_members/ensure_felhom_pool/pool_add_guest; constPVE_POOL="felhom". PVE 9 syntax +/poolsJSON shape confirmed live before wiring (pveum pool add|delete|modify;pvesh get /pools→[{poolid,comment}],/pools/<id>→{members:[{vmid,…}]}). --adopt-poolretrofit mode. Non-destructive: adds an EXISTING Felhom guest to the pool (creating it if needed), resolving the guest from--vmidelse the recordedprovisioned_vmid. Reuses the ours-check (/etc/felhom-bootstrapmount) — refuses a non-Felhom guest unless--force. Touches ONLY pool membership: never reconfigures/restarts the guest, never contacts the hub. Idempotent (skip-if-member).--uninstallpool teardown (step 5b). After the pveum removal, deletes thefelhompool only if empty (a destroyed guest is auto-removed from its pool); a pool that still has members is left with alog_skipnaming them. Not reached on the Spec-1 safe-skip path (other Felhom guests remain).- Validated on felhom-pve (dry-run + SAFE live): T-A fresh-install dry-run shows the pool create +
membership lines; T-B live adopt of guest 9201 →
pvesh get /pools/felhomlists 9201, guest still running, config unchanged (the demo node is now pool-uniform); re-run = no-op; T-B' non-Felhom vmid → refusal; T-C uninstall dry-run → "pool felhom not empty (members: 9201) — leaving it".bash -n+shellcheckclean (0 new warnings; the 2 pre-existing SC2015 instep_verifyunchanged). - NOT changed:
PVE_PRIVS, the ACL grants, the agent, the provision-call args. 3b (pool-scoped ACL- agent restore-into-pool under a scoped token) is the separate spike-gated task.
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=provisionas-cores/-memory.0/unset = keep the golden's baked sizes (unchanged behaviour). New varsCPU_CORES/MEM_MIB;usage()header gains an "Appliance cap (optional)" group.- Conditional passthrough —
step_provisionbuilds acap_argsarray 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> hostnprocor--memory> hostMemTotal,log_warn"the cap won't protect other guests"; neverdie(the operator may know better). - Deploy dependency: a fresh install using
--cores/--memoryneeds 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+shellcheckclean (0 new warnings; the 2 pre-existing SC2015 instep_verifyunchanged).
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 throughrun()so--dry-runprints the full plan and executes nothing. Safety:- Ours-check: refuses to destroy a guest that lacks the
/etc/felhom-bootstrapbind mount (matched by the constant guest path, not a hardcodedmpNslot — on the demo host it'smp9), 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
--forceto remove host-level anyway — orphans the others). - Never removes the
sudopackage; 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 deleteruns 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 <x> --roles FelhomAgent. - Target vmid resolves from
--vmid, else the recordedprovisioned_vmid(else dies). A--vmidthat 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.
- Ours-check: refuses to destroy a guest that lacks the
- Install state now records
customer_id+provisioned_vmid(new_state_put/_state_gethelpers, dry-run-guarded like_state_mark; thecompleted[]shape is untouched) so a later--uninstallresolves its target automatically and safely. - Pre-flight guards (provision mode):
- Multi-node guard — on a 2+-node cluster,
die(naming the nodes) unless--nodeis explicit (newNODE_EXPLICIT); single-node keeps the current auto-pick. No-op under--skip-provision. - Archive-storage-exists guard — verify
--archive-storageappears inpvesm status(elsedie); no-op under--skip-provision. - RAM floor (WARN, never fatal) — warn when
MemAvailable < 2048 MiB. All three run insidestep_preflight(before any mutation) so they also fire under--dry-run.
- Multi-node guard — on a 2+-node cluster,
- 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+shellcheckclean (0 new warnings vs. baseline; the 2 pre-existing SC2015 instep_verifyare 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/ttymakes the no-echo prompt work regardless of how stdin is wired — both download-then-run andcurl … | sudo bash(where stdin is the pipe). Strictly more correct; the--passphrase-filepath is unchanged. The passphrase is still never on argv / in logs / in the state file. - VMID auto-detect (
--vmidnow optional-smart). NewVMID_EXPLICITflag (set by--vmid). The pre-flight vmid guard now determines "in use" against thepct list+qm listid-set (LXC and VMs share the id space — more complete than the oldpct 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.--vmidhelp text +usage()updated.
- explicit
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'scontroller.yamlvia config-retrieve — NO new credential); fetches/api/packages/admin/generic/felhom-agent/<ver>/felhom-agent, verifies sha256 vs the hub manifest (aborts on mismatch — verify-before-use), backs up any existing binary, installs0755 /usr/local/bin/felhom-agent; ensures the non-rootfelhom-agentsystem 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-onlystep_verify_agent(binary + non-root service active + a--selftest=hubcollect-report).- New step
7/8 golden: local auto-discovery stays the default/fallback; otherwise fetches/api/packages/admin/generic/felhom-golden/<ver>/golden.tar.zst, verifies sha256, and imports it into the archive storage's dump dir for the restore.--force-gitea-goldenforces the Gitea path. - Non-root agent model: the agent now runs as
felhom-agentwithprivileged.mode: "sudo"(was the dev/CIdirect+root shortcut). The config ischowned to the service user (0600) so the daemon can read it;systemctl is-activeafter 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
0440andvisudo -cf-validated before install. bash -n+shellcheckclean.
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
FelhomAgentrole (create-or-modify),felhom-agent@pveuser, 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-<golden-vmid>), 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--vmidwithout--force;--dry-runpreviews every mutation;--preserve-fromkeeps 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 tunnelHTTP 200→ host-report of guest 9201 → idempotent--resumeno-op. (One ordering bug — token ACL applied before rotation — was found and fixed during the live run.)