host-install v1.5.0: felhom pool by default + --adopt-pool + uninstall teardown
Every managed guest joins a dedicated `felhom` pool (fleet uniformity; foundation for the later pool-scoped ACL). All pool ops run as root@pam from the installer — NO agent/token/ACL change (PVE_PRIVS untouched). step_provision creates the pool + adds the guest; new --adopt-pool retrofits existing guests non-destructively (ours-checked, membership-only); --uninstall deletes the pool only if empty. Confirmed pveum pool / /pools syntax live; validated dry-run + SAFE live adopt of 9201 on felhom-pve. bash -n + shellcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,34 @@
|
||||
# Felhom scripts — Changelog
|
||||
|
||||
## 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 `felhom` pool default.** `step_provision` calls `ensure_felhom_pool` (create if absent,
|
||||
idempotent) and, after a successful provision, adds the guest via `pveum pool modify felhom -vms
|
||||
<vmid>` (skip-if-already-member). New helpers `pool_exists` / `pool_members` / `ensure_felhom_pool` /
|
||||
`pool_add_guest`; const `PVE_POOL="felhom"`. PVE 9 syntax + `/pools` JSON shape confirmed live before
|
||||
wiring (`pveum pool add|delete|modify`; `pvesh get /pools` → `[{poolid,comment}]`, `/pools/<id>` →
|
||||
`{members:[{vmid,…}]}`).
|
||||
- **`--adopt-pool` retrofit mode.** Non-destructive: adds an EXISTING Felhom guest to the pool (creating
|
||||
it if needed), resolving the guest from `--vmid` else the recorded `provisioned_vmid`. Reuses the
|
||||
ours-check (`/etc/felhom-bootstrap` mount) — refuses a non-Felhom guest unless `--force`. Touches ONLY
|
||||
pool membership: never reconfigures/restarts the guest, never contacts the hub. Idempotent
|
||||
(skip-if-member).
|
||||
- **`--uninstall` pool teardown (step 5b).** After the pveum removal, deletes the `felhom` pool **only
|
||||
if empty** (a destroyed guest is auto-removed from its pool); a pool that still has members is left
|
||||
with a `log_skip` naming 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/felhom` lists 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` +
|
||||
`shellcheck` clean (0 new warnings; the 2 pre-existing SC2015 in `step_verify` unchanged).
|
||||
- **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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# felhom-host-install.sh v1.4.0
|
||||
# felhom-host-install.sh v1.5.0
|
||||
# Day-0 host-bootstrap for a Felhom Proxmox host (operator-deploy model).
|
||||
#
|
||||
# Run by the operator on a FRESHLY-PVE-INSTALLED box (after a manual PVE install
|
||||
@@ -79,6 +79,13 @@
|
||||
# --archive-storage, --golden-vmid, --dry-run.
|
||||
# --remove-golden with --uninstall, also delete the golden vzdump from the archive storage
|
||||
#
|
||||
# Retrofit (local, non-destructive — no hub contact, no passphrase):
|
||||
# --adopt-pool add an EXISTING Felhom guest to the `felhom` pool (creates the pool if
|
||||
# needed). Resolves the guest from --vmid else the recorded provisioned_vmid;
|
||||
# refuses a non-Felhom guest unless --force. Touches ONLY pool membership —
|
||||
# never reconfigures/restarts the guest. (A fresh provision joins the pool
|
||||
# automatically; this retrofits already-installed boxes.)
|
||||
#
|
||||
# State (idempotent/resumable): /var/lib/felhom-install/state.json
|
||||
# Agent config written 0600 to the systemd unit's -config path
|
||||
# (auto-detected; else /etc/felhom-agent/agent.json).
|
||||
@@ -90,7 +97,7 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_VERSION="1.4.0"
|
||||
SCRIPT_VERSION="1.5.0"
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Logging (mirrors felhom-controller/scripts/docker-setup.sh)
|
||||
@@ -136,6 +143,7 @@ DRY_RUN=false
|
||||
RESUME=false
|
||||
UNINSTALL=false # --uninstall: local host teardown (destroy guest + remove agent/pveum/state)
|
||||
REMOVE_GOLDEN=false # --remove-golden: also delete the golden vzdump during --uninstall
|
||||
ADOPT_POOL=false # --adopt-pool: retrofit an EXISTING Felhom guest into the felhom pool (non-destructive)
|
||||
|
||||
# --- Gitea (artifact source) + agent install model (BUNDLE slice) ---
|
||||
GITEA_BASE="https://gitea.dooplex.hu"
|
||||
@@ -150,6 +158,7 @@ AGENT_STATE_DIR="/var/lib/felhom-agent"
|
||||
PVE_USER="felhom-agent@pve"
|
||||
PVE_TOKENID="agent"
|
||||
PVE_ROLE="FelhomAgent"
|
||||
PVE_POOL="felhom" # dedicated pool every Felhom-managed guest joins (fleet uniformity; 3b will scope the ACL to it)
|
||||
# The authoritative 16 privileges (agent README; VM.Config.CPUMemory is NOT real, SDN.Use IS required).
|
||||
PVE_PRIVS="VM.Allocate VM.Audit VM.Config.Disk VM.Config.CPU VM.Config.Memory VM.Config.Network VM.Config.Options VM.PowerMgmt VM.Snapshot VM.Snapshot.Rollback VM.Backup Datastore.Allocate Datastore.AllocateSpace Datastore.Audit Sys.Audit SDN.Use"
|
||||
|
||||
@@ -173,7 +182,7 @@ ART_GOLDEN_SHA=""
|
||||
#-------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
#-------------------------------------------------------------------------------
|
||||
usage() { sed -n '2,80p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
|
||||
usage() { sed -n '2,87p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
|
||||
|
||||
run() { # simple (no pipes/redirects) mutating command
|
||||
if $DRY_RUN; then log_dry "$*"; else "$@"; fi
|
||||
@@ -303,6 +312,49 @@ felhom_guests() {
|
||||
done
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# felhom pool (fleet uniformity) — every managed guest joins the `felhom` pool. All pool ops run as
|
||||
# root@pam from the installer, so NO agent/token/ACL change is involved (that is the separate 3b spike).
|
||||
# API shapes confirmed on PVE 9: `pvesh get /pools` → [{poolid,comment}]; `pvesh get /pools/<id>` →
|
||||
# {poolid,comment,members:[{vmid,...}]}. Pool ops: `pveum pool add|delete <id>`, `pveum pool modify
|
||||
# <id> --vms <ids>` (additive).
|
||||
#-------------------------------------------------------------------------------
|
||||
# pool_exists — true if the felhom pool is present.
|
||||
pool_exists() {
|
||||
pvesh get /pools --output-format json 2>/dev/null \
|
||||
| python3 -c "import json,sys;sys.exit(0 if any(p.get('poolid')=='$PVE_POOL' for p in json.load(sys.stdin)) else 1)" 2>/dev/null
|
||||
}
|
||||
|
||||
# pool_members — space-separated vmids currently in the felhom pool (empty if none / pool absent).
|
||||
pool_members() {
|
||||
pvesh get "/pools/$PVE_POOL" --output-format json 2>/dev/null \
|
||||
| python3 -c "import json,sys
|
||||
try: d=json.load(sys.stdin)
|
||||
except Exception: sys.exit(0)
|
||||
print(' '.join(str(m.get('vmid')) for m in d.get('members',[]) if m.get('vmid') is not None))" 2>/dev/null
|
||||
}
|
||||
|
||||
# ensure_felhom_pool — create the pool if absent (idempotent no-op otherwise). Via run() (dry-run-aware).
|
||||
ensure_felhom_pool() {
|
||||
if pool_exists; then
|
||||
log_skip " pool $PVE_POOL already exists"
|
||||
else
|
||||
run pveum pool add "$PVE_POOL" --comment "Felhom-managed guests"
|
||||
fi
|
||||
}
|
||||
|
||||
# pool_add_guest VMID — add a guest to the felhom pool unless it is already a member (idempotent).
|
||||
pool_add_guest() {
|
||||
local vmid="$1" members
|
||||
members=" $(pool_members) "
|
||||
if [[ "$members" == *" $vmid "* ]]; then
|
||||
log_skip " guest $vmid already in pool $PVE_POOL"
|
||||
else
|
||||
run pveum pool modify "$PVE_POOL" -vms "$vmid"
|
||||
log_success " guest $vmid added to pool $PVE_POOL"
|
||||
fi
|
||||
}
|
||||
|
||||
# run_uninstall — the full guarded teardown. Every mutation goes through run() so --dry-run prints it
|
||||
# and executes nothing. Ordering is the reverse of install: guest -> agent -> pveum(ACL,token,user,
|
||||
# role) -> golden(opt-in) -> state file. See the TASK spec §7/§8.
|
||||
@@ -310,7 +362,7 @@ run_uninstall() {
|
||||
log_step "UNINSTALL — local host teardown"
|
||||
|
||||
# 1. Resolve the target vmid: --vmid, else the recorded provisioned_vmid, else die.
|
||||
local state_vmid vmid
|
||||
local state_vmid vmid pool_removed=false
|
||||
state_vmid=$(_state_get provisioned_vmid)
|
||||
if $VMID_EXPLICIT; then
|
||||
vmid="$VMID"
|
||||
@@ -438,6 +490,21 @@ run_uninstall() {
|
||||
log_skip " role $PVE_ROLE already absent"
|
||||
fi
|
||||
|
||||
# 5b. felhom pool — delete ONLY if empty (a destroyed guest is auto-removed from its pool). Never
|
||||
# delete a pool that still holds members (someone else's guests, or another Felhom guest kept
|
||||
# under --force).
|
||||
if pool_exists; then
|
||||
local pool_left; pool_left=$(pool_members)
|
||||
if [[ -z "$pool_left" ]]; then
|
||||
run pveum pool delete "$PVE_POOL"
|
||||
pool_removed=true
|
||||
else
|
||||
log_skip " pool $PVE_POOL not empty (members: $pool_left) — leaving it"
|
||||
fi
|
||||
else
|
||||
log_skip " pool $PVE_POOL already absent"
|
||||
fi
|
||||
|
||||
# 6. Golden vzdump (opt-in via --remove-golden; else left in place).
|
||||
if $REMOVE_GOLDEN; then
|
||||
local gvols gv
|
||||
@@ -461,13 +528,51 @@ run_uninstall() {
|
||||
|
||||
# 8. Summary.
|
||||
echo ""
|
||||
log_success "UNINSTALL complete — removed: guest $vmid, the felhom-agent (unit/sudoers/binary/state/user), the pveum role/user/token/ACL, and $STATE_FILE."
|
||||
log_success "UNINSTALL complete — removed: guest $vmid, the felhom-agent (unit/sudoers/binary/state/user), the pveum role/user/token/ACL,$( $pool_removed && printf ' the %s pool,' "$PVE_POOL") and $STATE_FILE."
|
||||
if $REMOVE_GOLDEN; then log_info " golden vzdump: removed."; else log_info " golden vzdump: left in place (--remove-golden to remove)."; fi
|
||||
log_info " NOTE: the 'sudo' package was left installed (system package); the host record still exists in the hub — remove it there if desired."
|
||||
$DRY_RUN && log_warn " DRY-RUN: nothing above was actually executed."
|
||||
return 0
|
||||
}
|
||||
|
||||
# run_adopt_pool — retrofit an EXISTING Felhom guest into the felhom pool. Non-destructive: creates the
|
||||
# pool if absent + adds the guest; never reconfigures/restarts the guest, never contacts the hub. Guest
|
||||
# resolves from --vmid else the recorded provisioned_vmid (mirrors run_uninstall).
|
||||
run_adopt_pool() {
|
||||
log_step "ADOPT-POOL — add an existing Felhom guest to the $PVE_POOL pool"
|
||||
|
||||
local state_vmid vmid
|
||||
state_vmid=$(_state_get provisioned_vmid)
|
||||
if $VMID_EXPLICIT; then
|
||||
vmid="$VMID"
|
||||
elif [[ -n "$state_vmid" ]]; then
|
||||
vmid="$state_vmid"
|
||||
log_info " no --vmid given; using recorded provisioned_vmid=$vmid from $STATE_FILE"
|
||||
else
|
||||
die "pass --vmid N (state has no recorded vmid)"
|
||||
fi
|
||||
|
||||
_vmid_in_use "$vmid" || die "guest $vmid not found on this host (nothing to adopt)"
|
||||
|
||||
# ours-check: only adopt a Felhom guest (has the /etc/felhom-bootstrap mount) unless --force.
|
||||
if pct config "$vmid" 2>/dev/null | grep -q 'mp=/etc/felhom-bootstrap'; then
|
||||
log_info " vmid $vmid looks like a Felhom guest (has the /etc/felhom-bootstrap mount)"
|
||||
elif $FORCE; then
|
||||
log_warn " vmid $vmid has NO /etc/felhom-bootstrap mount — --force given, adopting anyway"
|
||||
else
|
||||
die "vmid $vmid does not look like a Felhom-provisioned guest (no /etc/felhom-bootstrap mount).
|
||||
Refusing to adopt. Pass --force to override."
|
||||
fi
|
||||
|
||||
ensure_felhom_pool
|
||||
pool_add_guest "$vmid"
|
||||
|
||||
echo ""
|
||||
log_success "ADOPT-POOL complete — guest $vmid is in pool $PVE_POOL (guest not otherwise modified)."
|
||||
$DRY_RUN && log_warn " DRY-RUN: nothing above was actually executed."
|
||||
return 0
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Arg parse
|
||||
#-------------------------------------------------------------------------------
|
||||
@@ -496,6 +601,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--skip-provision) SKIP_PROVISION=true; shift ;;
|
||||
--uninstall) UNINSTALL=true; shift ;;
|
||||
--remove-golden) REMOVE_GOLDEN=true; shift ;;
|
||||
--adopt-pool) ADOPT_POOL=true; shift ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
--resume) RESUME=true; shift ;;
|
||||
-h|--help) usage ;;
|
||||
@@ -517,6 +623,20 @@ if $UNINSTALL; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
#===============================================================================
|
||||
# ADOPT-POOL MODE — retrofit an EXISTING Felhom guest into the felhom pool (non-destructive; no hub
|
||||
# contact, no passphrase, no guest reconfigure beyond pool membership). Dispatched early.
|
||||
#===============================================================================
|
||||
if $ADOPT_POOL; then
|
||||
[[ $EUID -eq 0 ]] || die "must run as root"
|
||||
echo ""
|
||||
log_info "felhom-host-install v${SCRIPT_VERSION} — mode=adopt-pool"
|
||||
$DRY_RUN && log_warn "DRY-RUN: no mutations will be performed"
|
||||
echo ""
|
||||
run_adopt_pool
|
||||
exit 0
|
||||
fi
|
||||
|
||||
#===============================================================================
|
||||
# DR MODE — documented seam only (10D). NOT implemented.
|
||||
#===============================================================================
|
||||
@@ -1131,8 +1251,12 @@ step_provision() {
|
||||
local -a cap_args=()
|
||||
[[ -n "$CPU_CORES" ]] && cap_args+=(-cores "$CPU_CORES")
|
||||
[[ -n "$MEM_MIB" ]] && cap_args+=(-memory "$MEM_MIB")
|
||||
# felhom pool: ensure it exists (root@pam op — no agent/token/ACL change), then add the guest after
|
||||
# a successful provision. Fleet uniformity + the environment 3b will scope its ACL against.
|
||||
ensure_felhom_pool
|
||||
if $DRY_RUN; then
|
||||
log_dry "felhom-agent --config $AGENT_CONFIG --selftest=provision -archive $GOLDEN_VOLID -vmid $VMID -customer-id $CUSTOMER_ID -hub-password <pass> -rootfs-grow $ROOTFS_GROW -datavol-grow $DATAVOL_GROW -sysdata-grow $SYSDATA_GROW ${cap_args[*]}"
|
||||
log_dry "add guest $VMID to pool $PVE_POOL"
|
||||
log_dry "record provisioned_vmid=$VMID in $STATE_FILE (for a later automatic --uninstall)"
|
||||
_state_mark provision; return 0
|
||||
fi
|
||||
@@ -1147,6 +1271,8 @@ step_provision() {
|
||||
_state_mark provision
|
||||
# Record the provisioned vmid so a later --uninstall resolves the target automatically + safely.
|
||||
_state_put provisioned_vmid "$VMID"
|
||||
# Join the felhom pool (idempotent; skip-if-member).
|
||||
pool_add_guest "$VMID"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user