#!/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 : # 3. event dir — create /opt/homelab/events/, 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"