feat(onboard): implement 20-base.sh for LUSTRO — swap→zram, /opt/homelab, event dir
Three idempotent stages with guards (probe-before-mutate), rrun() for all remote mutations, rprobe() for unconditional state queries. Reads hardware.swap.mb from node.yaml (default 2048 MB). Adds swap.mb: 2048 to hosts/lustro/node.yaml so the value is declarative. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9b2a1b4e9a
commit
d81ac27ebb
|
|
@ -21,6 +21,7 @@ hardware:
|
|||
ram_mb: 4096
|
||||
swap:
|
||||
kind: zram
|
||||
mb: 2048
|
||||
docker_present: true
|
||||
mm_runtime: systemd:magicmirror.service
|
||||
|
||||
|
|
|
|||
157
scripts/onboard/steps/20-base.sh
Normal file
157
scripts/onboard/steps/20-base.sh
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
#!/usr/bin/env bash
|
||||
# scripts/onboard/steps/20-base.sh — base system configuration for LUSTRO
|
||||
#
|
||||
# Stages:
|
||||
# 1. swap→zram — disable dphys-swapfile, install + configure zram-tools
|
||||
# 2. /opt/homelab — create base directory, chown <ssh_user>:<ssh_user>
|
||||
# 3. event dir — create /opt/homelab/events/<ts_hostname>, chown -R
|
||||
#
|
||||
# Dry-run convention:
|
||||
# - Probes (state queries) run unconditionally — dry-run reflects real state
|
||||
# - Mutations use rrun() which skips execution when DRY_RUN=1
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
STEP_NAME="20-base"
|
||||
|
||||
: "${REPO_ROOT:?REPO_ROOT is not set — run via onboard.sh}"
|
||||
: "${NODE_YAML:?NODE_YAML is not set — run via onboard.sh}"
|
||||
: "${DRY_RUN:=0}"
|
||||
|
||||
# Source common.sh when run standalone (orchestrator sources it before calling steps)
|
||||
if ! declare -f log >/dev/null 2>&1; then
|
||||
# shellcheck source=../lib/common.sh
|
||||
source "${REPO_ROOT}/scripts/onboard/lib/common.sh"
|
||||
fi
|
||||
|
||||
# ── parse node.yaml ───────────────────────────────────────────────────────────
|
||||
SSH_USER=$(yaml_get "$NODE_YAML" "ssh_user")
|
||||
TS_HOSTNAME=$(yaml_get "$NODE_YAML" "tailscale.hostname")
|
||||
_raw_mb=$(yaml_get "$NODE_YAML" "hardware.swap.mb" 2>/dev/null || true)
|
||||
SWAP_MB="${_raw_mb:-2048}"
|
||||
|
||||
[[ -z "$SSH_USER" ]] && die "ssh_user not set in $NODE_YAML"
|
||||
[[ -z "$TS_HOSTNAME" ]] && die "tailscale.hostname not set in $NODE_YAML"
|
||||
|
||||
export ONBOARD_SSH_USER="${ONBOARD_SSH_USER:-${SSH_USER}}"
|
||||
export ONBOARD_SSH_HOST="${ONBOARD_SSH_HOST:-${TS_HOSTNAME}}"
|
||||
|
||||
# shellcheck source=../lib/remote.sh
|
||||
source "${REPO_ROOT}/scripts/onboard/lib/remote.sh"
|
||||
|
||||
# ── rprobe: read-only remote probe — always runs, even in dry-run ─────────────
|
||||
rprobe() {
|
||||
ssh "${_SSH_OPTS[@]}" "${ONBOARD_SSH_USER}@${ONBOARD_SSH_HOST}" -- "$@"
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Stage 1 — swap→zram
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
step "[$STEP_NAME] 1/3 swap→zram (target: zram ${SWAP_MB} MB, algo=zstd)"
|
||||
|
||||
# Guard: is dphys-swapfile still active?
|
||||
if rprobe 'systemctl is-active dphys-swapfile' >/dev/null 2>&1; then
|
||||
log "dphys-swapfile active — disabling"
|
||||
rrun sudo dphys-swapfile swapoff
|
||||
rrun sudo systemctl disable --now dphys-swapfile
|
||||
if rprobe '[ -f /var/swap ]' 2>/dev/null; then
|
||||
rrun sudo rm -f /var/swap
|
||||
log "Removed /var/swap"
|
||||
fi
|
||||
else
|
||||
log "dphys-swapfile not active — skip disable"
|
||||
fi
|
||||
|
||||
# Guard: is zram-tools installed?
|
||||
if ! rprobe 'command -v zramswap' >/dev/null 2>&1; then
|
||||
log "zram-tools not found — installing"
|
||||
rrun sudo apt-get install -y zram-tools
|
||||
else
|
||||
log "zram-tools already installed"
|
||||
fi
|
||||
|
||||
# Guard: is zram already active with the correct SIZE?
|
||||
_zram_ok=0
|
||||
if rprobe 'swapon --show --noheadings 2>/dev/null | grep -q zram' 2>/dev/null; then
|
||||
_cfg_size=$(rprobe \
|
||||
"grep -E '^[[:space:]]*SIZE=' /etc/default/zramswap 2>/dev/null \
|
||||
| cut -d= -f2 | tr -d '\"[:space:]'" 2>/dev/null || echo "")
|
||||
if [[ "$_cfg_size" == "$SWAP_MB" ]]; then
|
||||
_zram_ok=1
|
||||
log "zram active, SIZE=${SWAP_MB} MB — skip configure"
|
||||
else
|
||||
log "zram active but SIZE='${_cfg_size:-unset}' ≠ ${SWAP_MB} — reconfigure"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$_zram_ok" -eq 0 ]]; then
|
||||
log "Writing /etc/default/zramswap (ALGO=zstd, SIZE=${SWAP_MB})"
|
||||
rrun sudo bash -c "printf 'ALGO=zstd\nSIZE=${SWAP_MB}\n' | tee /etc/default/zramswap > /dev/null"
|
||||
rrun sudo systemctl enable zramswap
|
||||
rrun sudo systemctl restart zramswap
|
||||
fi
|
||||
|
||||
# Verify (skipped in dry-run — mutations may not have run)
|
||||
if [ "${DRY_RUN:-0}" != 1 ]; then
|
||||
if rprobe 'swapon --show --noheadings 2>/dev/null | grep -q zram'; then
|
||||
log "Verify OK: zram swap active"
|
||||
rprobe 'swapon --show; echo "---"; zramctl' || true
|
||||
else
|
||||
die "zram swap not active after setup — check: systemctl status zramswap on ${TS_HOSTNAME}"
|
||||
fi
|
||||
if rprobe 'systemctl is-active dphys-swapfile' >/dev/null 2>&1; then
|
||||
warn "dphys-swapfile still reports active — manual inspection needed"
|
||||
else
|
||||
log "Verify OK: dphys-swapfile not active"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Stage 2 — /opt/homelab
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
step "[$STEP_NAME] 2/3 /opt/homelab (owner: ${SSH_USER}:${SSH_USER})"
|
||||
|
||||
# Guard: exists AND owned by SSH_USER?
|
||||
_dir_ok=0
|
||||
if rprobe '[ -d /opt/homelab ]' 2>/dev/null; then
|
||||
_owner=$(rprobe "stat -c '%U' /opt/homelab" 2>/dev/null || echo "")
|
||||
if [[ "$_owner" == "$SSH_USER" ]]; then
|
||||
_dir_ok=1
|
||||
log "/opt/homelab exists, owner=${SSH_USER} — skip"
|
||||
else
|
||||
log "/opt/homelab exists but owner='${_owner}' — fixing"
|
||||
fi
|
||||
else
|
||||
log "/opt/homelab missing — creating"
|
||||
fi
|
||||
|
||||
if [[ "$_dir_ok" -eq 0 ]]; then
|
||||
rrun sudo mkdir -p /opt/homelab
|
||||
rrun sudo chown "${SSH_USER}:${SSH_USER}" /opt/homelab
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Stage 3 — event dir
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
step "[$STEP_NAME] 3/3 event dir (/opt/homelab/events/${TS_HOSTNAME})"
|
||||
|
||||
# Guard: event subdir exists AND /opt/homelab/events owned by SSH_USER?
|
||||
_evdir_ok=0
|
||||
if rprobe "[ -d /opt/homelab/events/${TS_HOSTNAME} ]" 2>/dev/null; then
|
||||
_ev_owner=$(rprobe "stat -c '%U' /opt/homelab/events" 2>/dev/null || echo "")
|
||||
if [[ "$_ev_owner" == "$SSH_USER" ]]; then
|
||||
_evdir_ok=1
|
||||
log "/opt/homelab/events/${TS_HOSTNAME} exists, owner=${SSH_USER} — skip"
|
||||
else
|
||||
log "/opt/homelab/events exists but owner='${_ev_owner}' — fixing"
|
||||
fi
|
||||
else
|
||||
log "/opt/homelab/events/${TS_HOSTNAME} missing — creating"
|
||||
fi
|
||||
|
||||
if [[ "$_evdir_ok" -eq 0 ]]; then
|
||||
rrun sudo mkdir -p "/opt/homelab/events/${TS_HOSTNAME}"
|
||||
rrun sudo chown -R "${SSH_USER}:${SSH_USER}" /opt/homelab/events
|
||||
fi
|
||||
|
||||
log "[$STEP_NAME] done"
|
||||
Loading…
Reference in a new issue