diff --git a/scripts/CHANGELOG.md b/scripts/CHANGELOG.md index aeddeb5..eac40ea 100644 --- a/scripts/CHANGELOG.md +++ b/scripts/CHANGELOG.md @@ -1,5 +1,20 @@ # Felhom scripts — Changelog +## felhom-host-install.sh v1.17.0 — appliance guest auto-sizing (F5) + doc-drift fix (2026-07-17) + +Closes `VALIDATION-n100-baremetal-2026-07-16.md` **F5 (MEDIUM):** appliance mode provisioned the +golden default (2 GB RAM) on a 16 GB host and never surfaced the `--memory`/`--cores` caps. Now, in +**appliance** mode with no explicit cap, the guest is auto-sized from the host (LXC limits are cheap → +err generous): RAM = `clamp(host-4096, min 4096, max host-2048)` then a hard ceiling `host-1024` +(never over-commit); cores = `host-1, min 2`. An explicit `--memory`/`--cores` ALWAYS wins untouched; +byo mode still requires explicit caps (never auto-sized). Host reads (`MemTotal`/`nproc`) are +overridable via `FELHOM_FAKE_MEMTOTAL_MIB`/`FELHOM_FAKE_NPROC` for the mode harness. Sizing table: +8 GB→4096, 16 GB→12288, 32 GB→28672 MiB; a 4 GB host→3072 (min capped at host-1024). +Red-proof: `hostinstall-mode-harness.sh` new F5 section (8/16/32 GB + small-host edge + explicit-flag +precedence) — pre-fix (auto-size disabled) FAILs, post-fix PASSes. **Same commit (R-16 doc-drift +leftover):** the operator-signing-keys "EMPTY by default" comment was stale (the keys are PINNED to +the real `felhom-op-1`/`felhom-rec-1` ceremony keypairs) — corrected, comment-only, no behavior. + ## felhom-poke.sh v1.0.0 — NEW (agent-plane immediate-sync, Direction-2a, 2026-07-16) The offsite endpoint's THIRD hub forced-command surface (`documentation/runbooks/offsite-endpoint.md` diff --git a/scripts/felhom-host-install.sh b/scripts/felhom-host-install.sh index 31427c2..267db87 100644 --- a/scripts/felhom-host-install.sh +++ b/scripts/felhom-host-install.sh @@ -182,7 +182,7 @@ set -euo pipefail -SCRIPT_VERSION="1.16.0" # the SINGLE version source (F-1): -h, the run banners, and the hub +SCRIPT_VERSION="1.17.0" # the SINGLE version source (F-1): -h, the run banners, and the hub # Setup-tab copy (hub internal/web/configs.go hostInstallVersion — # scripts/hostinstall_gates.py asserts the two stay equal) all follow it. # 1.16.0: the FELHOM_ESCROW sudoers alias (controller-driven escrow @@ -190,11 +190,11 @@ SCRIPT_VERSION="1.16.0" # the SINGLE version source (F-1): -h, the run banners # CANONICAL sudoers fetch below (configs/felhom-agent.sudoers from the # agent repo, visudo-gated), no separate installer step. -# Operator signing keys pinned at day-0 (GL-4; doc 04 §3 two-key model). EMPTY by default — the pin -# CEREMONY is an operator step: generate the real keypairs OFFLINE, then fill these four constants -# in one commit (or pass --operator-pubkey-file at install time, which overrides them). Empty = -# no authz.signers written = agent self-update stays DORMANT (the safe default; the verify step -# warns). PUBLIC keys only — this script never generates, reads, or references private key material. +# Operator signing keys pinned at day-0 (GL-4; doc 04 §3 two-key model). PINNED below to the real +# ceremony keypairs (the pin ceremony is DONE — felhom-op-1 / felhom-rec-1); --operator-pubkey-file +# at install time overrides them. If these were ever cleared to empty, no authz.signers is written +# and agent self-update stays DORMANT (the safe fallback; the verify step warns). PUBLIC keys only — +# this script never generates, reads, or references private key material. OPERATOR_KEY_OPERATIONAL_ID="felhom-op-1" OPERATOR_KEY_OPERATIONAL_LINE="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL8z0qCNgA3x2xxAB0Qj5ro8waFjGZ8Ta/sWB63tlLw+ felhom-op-1" OPERATOR_KEY_RECOVERY_ID="felhom-rec-1" @@ -1152,6 +1152,49 @@ fi # --resume variant), so the profile is decided HERE, before the passphrase prompt and any step. The # refusals are deliberately argv-time (the harness relies on them firing on a non-PVE machine too). #=============================================================================== + +# --- F5 (VALIDATION-n100): appliance guest auto-sizing -------------------------- +# The golden default (2 GB RAM) was too small on a 16 GB host. In APPLIANCE mode, when the operator +# passes no explicit cap, size the guest generously from the host — LXC limits are cheap, so err +# generous. An explicit --cores/--memory ALWAYS wins (never overwritten). byo mode requires explicit +# caps (never auto-sized). The host reads are overridable for the mode harness (no real /proc dep). +host_total_mib() { + if [[ -n "${FELHOM_FAKE_MEMTOTAL_MIB:-}" ]]; then echo "$FELHOM_FAKE_MEMTOTAL_MIB"; return; fi + awk '/^MemTotal:/{print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0 +} +host_core_count() { + if [[ -n "${FELHOM_FAKE_NPROC:-}" ]]; then echo "$FELHOM_FAKE_NPROC"; return; fi + nproc 2>/dev/null || echo 0 +} +autosize_guest_caps() { + [[ "$MODE" == "appliance" ]] || return 0 + if [[ -z "$MEM_MIB" ]]; then + local total; total=$(host_total_mib) + if [[ "${total:-0}" -gt 0 ]]; then + # clamp(host-4096, min 4096, max host-2048), then the hard ceiling host-1024 (never + # over-commit). Order matters: the min floor is applied AFTER the generous max so that on + # a host too small for both the MIN wins (edge rule), bounded only by the host-1024 ceiling. + local mem=$(( total - 4096 )) + local max2=$(( total - 2048 )) + (( mem > max2 )) && mem=$max2 + (( mem < 4096 )) && mem=4096 + local ceil=$(( total - 1024 )) + (( mem > ceil )) && mem=$ceil + MEM_MIB=$mem + log_info " auto-sized guest RAM: ${MEM_MIB} MiB (host ${total} MiB; clamp(host-4096, min 4096, max host-2048), ceiling host-1024)" + fi + fi + if [[ -z "$CPU_CORES" ]]; then + local cores; cores=$(host_core_count) + if [[ "${cores:-0}" -gt 0 ]]; then + local c=$(( cores - 1 )) + (( c < 2 )) && c=2 + CPU_CORES=$c + log_info " auto-sized guest cores: ${CPU_CORES} (host ${cores} cores; host-1, min 2)" + fi + fi +} + case "$MODE" in appliance|byo) ;; "") @@ -1165,6 +1208,9 @@ case "$MODE" in *) die "Unknown --mode: $MODE (appliance|byo)" ;; esac +# F5: fill the appliance guest caps from the host when the operator gave none (explicit flags win). +autosize_guest_caps + # BYO argument refusals (C1/C2) — before the passphrase prompt, before any step. if [[ "$MODE" == "byo" ]]; then if [[ -z "$CPU_CORES" || -z "$MEM_MIB" ]]; then diff --git a/scripts/hostinstall-mode-harness.sh b/scripts/hostinstall-mode-harness.sh index f86b25a..81ad3b9 100644 --- a/scripts/hostinstall-mode-harness.sh +++ b/scripts/hostinstall-mode-harness.sh @@ -347,6 +347,47 @@ else verdict FAIL "GL8-F1b glob removal clears agent.json + every .bak* + the empty dir" "residue: $(ls -A "$f1dir" 2>/dev/null | tr '\n' ' ')" fi +echo "" +echo "--- F5 appliance guest auto-sizing (VALIDATION-n100) ---" +# The auto-size log line is emitted right after mode validation (BEFORE any PVE/hub contact), so it +# is asserted portably on ANY host — the later pipeline dies for lack of PVE, but the sizing already +# happened. RED-PROOF: pre-fix, appliance passes NO cap → the golden 2 GB default reaches the guest +# (no auto-size line at all); post-fix, the formula value is logged (and flows into the provision +# args via cap_args). Host reads are faked via FELHOM_FAKE_MEMTOTAL_MIB / FELHOM_FAKE_NPROC. +f5_run() { # [extra install args...] + local mt="$1" np="$2"; shift 2 + FELHOM_INSTALL_STATE_DIR="$STATE_OVERRIDE_ENV" FELHOM_FAKE_MEMTOTAL_MIB="$mt" FELHOM_FAKE_NPROC="$np" \ + bash "$SCRIPT" --customer-id t --mode appliance --dry-run "$@" &1 || true +} +while read -r f5_mt f5_np f5_mem f5_cores; do + [[ -z "$f5_mt" ]] && continue + o=$(f5_run "$f5_mt" "$f5_np") + if echo "$o" | grep -q "auto-sized guest RAM: ${f5_mem} MiB" && echo "$o" | grep -q "auto-sized guest cores: ${f5_cores} "; then + verdict PASS "F5 auto-size host ${f5_mt}MiB/${f5_np}c -> guest ${f5_mem}MiB/${f5_cores} cores" + else + verdict FAIL "F5 auto-size host ${f5_mt}MiB/${f5_np}c -> guest ${f5_mem}MiB/${f5_cores} cores" \ + "got: $(echo "$o" | grep -i auto-sized | tr '\n' '; ')" + fi +done <<'F5TABLE' +8192 8 4096 7 +16384 8 12288 7 +32768 16 28672 15 +F5TABLE +# Edge: a host below the min clamp — the min (4096) wins but is capped at host-1024. +o=$(f5_run 4096 2) +if echo "$o" | grep -q "auto-sized guest RAM: 3072 MiB"; then + verdict PASS "F5 auto-size small host 4096MiB -> 3072MiB (min capped at host-1024)" +else + verdict FAIL "F5 auto-size small host 4096MiB -> 3072MiB" "got: $(echo "$o" | grep -i 'auto-sized guest RAM' | tr '\n' '; ')" +fi +# Explicit flags ALWAYS win: NO auto-size line (byte-identical to the pre-fix no-autosize behavior). +o=$(f5_run 16384 8 --memory 8192 --cores 4) +if ! echo "$o" | grep -qi "auto-sized"; then + verdict PASS "F5 explicit --memory/--cores suppress auto-sizing (explicit wins untouched)" +else + verdict FAIL "F5 explicit --memory/--cores suppress auto-sizing" "auto-size line present despite explicit flags" +fi + echo "" echo "--- PVE tier ---" if ! command -v pveum >/dev/null 2>&1 || [[ "$(id -u)" != 0 ]]; then