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,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