chore: bootstrap lean sysadmin-chronicles repo
Import the runnable game code, content, docs, scripts, and repo guidance while leaving local agent state, dependency installs, build output, and backup copies out of the published tree.
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
#!/usr/bin/env bash
|
||||
# seed-vms.sh — Build all game VMs and create baseline snapshots.
|
||||
#
|
||||
# This script orchestrates the full VM provisioning pipeline:
|
||||
# 1. Build base VM images (cloud-init or manual install)
|
||||
# 2. Install guest helper binaries
|
||||
# 3. Run quest-prep scripts for each Tier 1 quest
|
||||
# 4. Take named baseline snapshots
|
||||
#
|
||||
# Prerequisites: Run first-run-setup.sh first (creates networks + pool).
|
||||
#
|
||||
# Usage:
|
||||
# bash tools/setup/seed-vms.sh # Build all VMs
|
||||
# bash tools/setup/seed-vms.sh --dry-run # Preview only
|
||||
# bash tools/setup/seed-vms.sh --vm workstation # One VM only
|
||||
# bash tools/setup/seed-vms.sh --skip-build # Prep scripts + snapshots only
|
||||
#
|
||||
# AGENT RULES:
|
||||
# - Never run quest-prep scripts against live player VMs.
|
||||
# - All prep scripts must be idempotent (safe to run twice).
|
||||
# - Snapshots are only taken after prep scripts complete successfully.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
OWNER_USER="${SUDO_USER:-$USER}"
|
||||
OWNER_HOME="$(getent passwd "$OWNER_USER" | cut -d: -f6)"
|
||||
OWNER_HOME="${OWNER_HOME:-$HOME}"
|
||||
export LIBVIRT_DEFAULT_URI="${LIBVIRT_DEFAULT_URI:-qemu:///system}"
|
||||
export SC_OWNER_USER="$OWNER_USER"
|
||||
export SC_OWNER_HOME="$OWNER_HOME"
|
||||
export SC_SSH_KEY="${SC_SSH_KEY:-$OWNER_HOME/.ssh/sc_host_key}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
VM_TOOLS="$PROJECT_ROOT/tools/vm"
|
||||
QUEST_PREP="$VM_TOOLS/quest-prep"
|
||||
|
||||
source "$PROJECT_ROOT/tools/lib/config.sh"
|
||||
config_read || true
|
||||
|
||||
normalize_dir_path() {
|
||||
local path="${1:-}"
|
||||
while [[ "$path" == *//* ]]; do
|
||||
path="${path//\/\//\/}"
|
||||
done
|
||||
while [ "$path" != "/" ] && [ "${path%/}" != "$path" ]; do
|
||||
path="${path%/}"
|
||||
done
|
||||
printf '%s\n' "$path"
|
||||
}
|
||||
|
||||
if [ -n "${SC_IMAGES_DIR:-}" ]; then
|
||||
SC_IMAGES_DIR="$(normalize_dir_path "$SC_IMAGES_DIR")"
|
||||
export SC_IMAGE_ROOT="$SC_IMAGES_DIR"
|
||||
fi
|
||||
export SC_POOL_NAME="${SC_POOL_NAME:-sc-images}"
|
||||
export SC_NETWORK_NAME="${SC_NETWORK_NAME:-sc-internal}"
|
||||
|
||||
DRY_RUN=false
|
||||
SKIP_BUILD=false
|
||||
SINGLE_VM=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
--skip-build) SKIP_BUILD=true; shift ;;
|
||||
--vm) SINGLE_VM="$2"; shift 2 ;;
|
||||
*) echo "Unknown argument: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
run() {
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo " [DRY-RUN] $*"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
step() { echo ""; echo "── $* ───────────────────────────────────────"; }
|
||||
ok() { echo " ✓ $*"; }
|
||||
info() { echo " → $*"; }
|
||||
fail() { echo " ✗ $*"; exit 1; }
|
||||
vm_selected() {
|
||||
local key="$1"
|
||||
[ -z "$SINGLE_VM" ] || [ "$SINGLE_VM" = "$key" ]
|
||||
}
|
||||
domain_selected() {
|
||||
local domain="$1"
|
||||
case "$domain" in
|
||||
sc-workstation) vm_selected "workstation" ;;
|
||||
sc-web-server) vm_selected "web_server" ;;
|
||||
sc-build-machine) vm_selected "build_machine" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
require_file() {
|
||||
local path="$1"
|
||||
local label="$2"
|
||||
if [ ! -f "$path" ]; then
|
||||
fail "$label is missing: $path"
|
||||
fi
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "══════════════════════════════════════════════════"
|
||||
echo " Sysadmin Chronicles — VM Seed Pipeline"
|
||||
echo "══════════════════════════════════════════════════"
|
||||
[ "$DRY_RUN" = "true" ] && echo " [DRY-RUN mode]"
|
||||
echo ""
|
||||
|
||||
step "Validating provisioning toolchain"
|
||||
require_file "$QUEST_PREP/Q001-prep.sh" "Q001 prep script"
|
||||
require_file "$QUEST_PREP/Q002-prep.sh" "Q002 prep script"
|
||||
require_file "$QUEST_PREP/Q003-prep.sh" "Q003 prep script"
|
||||
require_file "$QUEST_PREP/Q004-prep.sh" "Q004 prep script"
|
||||
require_file "$QUEST_PREP/Q006-prep.sh" "Q006 prep script"
|
||||
require_file "$QUEST_PREP/Q006-post-clean.sh" "Q006 post-clean script"
|
||||
|
||||
if [ "$SKIP_BUILD" = "false" ]; then
|
||||
missing_scripts=()
|
||||
for script in \
|
||||
"$VM_TOOLS/build-workstation.sh" \
|
||||
"$VM_TOOLS/build-web-server.sh" \
|
||||
"$VM_TOOLS/build-build-machine.sh" \
|
||||
"$VM_TOOLS/install-guest-helper.sh" \
|
||||
"$VM_TOOLS/suppress-maintenance-noise.sh"
|
||||
do
|
||||
if [ ! -f "$script" ]; then
|
||||
missing_scripts+=("$script")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#missing_scripts[@]}" -gt 0 ]; then
|
||||
echo " ✗ VM provisioning pipeline is incomplete in this repo checkout."
|
||||
echo " Missing files:"
|
||||
for script in "${missing_scripts[@]}"; do
|
||||
echo " - $script"
|
||||
done
|
||||
echo ""
|
||||
echo " Current state:"
|
||||
echo " - The Godot game and authored content are present."
|
||||
echo " - The VM image build/provision helper scripts are not."
|
||||
echo ""
|
||||
echo " Real VM seeding cannot complete until those scripts are added."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# STEP 1 — Build base images
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if [ "$SKIP_BUILD" = "false" ]; then
|
||||
step "Building VM base images"
|
||||
info "NOTE: VM image builds require cloud-init ISO or manual install."
|
||||
info " See docs/ARCHITECTURE.md §5.3.1 for workstation profile guidance."
|
||||
info " See tools/vm/build-*.sh scripts for per-VM build details."
|
||||
echo ""
|
||||
|
||||
if vm_selected "workstation"; then
|
||||
info "Building workstation (ares) — Debian XFCE desktop..."
|
||||
run bash "$VM_TOOLS/build-workstation.sh" $([ "$DRY_RUN" = "true" ] && echo "--dry-run")
|
||||
fi
|
||||
|
||||
if vm_selected "web_server"; then
|
||||
info "Building web_server (hermes) — headless Debian..."
|
||||
run bash "$VM_TOOLS/build-web-server.sh" $([ "$DRY_RUN" = "true" ] && echo "--dry-run")
|
||||
fi
|
||||
|
||||
if vm_selected "build_machine"; then
|
||||
info "Building build_machine (vulcan) — headless Arch..."
|
||||
run bash "$VM_TOOLS/build-build-machine.sh" $([ "$DRY_RUN" = "true" ] && echo "--dry-run")
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# STEP 1b — Verify baseline connectivity
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if [ "$SKIP_BUILD" = "false" ] && [ "$DRY_RUN" = "false" ]; then
|
||||
step "Verifying baseline connectivity"
|
||||
for dom_host in "sc-web-server:hermes" "sc-build-machine:vulcan"; do
|
||||
dom="${dom_host%%:*}"
|
||||
host="${dom_host##*:}"
|
||||
addr="$(virsh domifaddr "$dom" --source agent 2>/dev/null | awk '/ipv4/ {print $4}' | cut -d/ -f1 | head -n1 || true)"
|
||||
if [ -z "$addr" ]; then
|
||||
info "$dom: no IP yet — skipping connectivity check"
|
||||
continue
|
||||
fi
|
||||
result="$(ssh -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=10 -i "$SC_SSH_KEY" "player@$addr" hostname 2>/dev/null || echo FAIL)"
|
||||
if [ "$result" = "FAIL" ] || [ -z "$result" ]; then
|
||||
fail "$dom ($host): 'hostname' failed — check inetutils and shell PATH provisioning"
|
||||
fi
|
||||
ok "$dom ($host): hostname=$result"
|
||||
done
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# STEP 2 — Suppress guest maintenance noise
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
step "Suppressing guest maintenance noise"
|
||||
info "Tuning base images to suppress package manager notices..."
|
||||
for dom in sc-workstation sc-web-server sc-build-machine; do
|
||||
domain_selected "$dom" || continue
|
||||
if virsh dominfo "$dom" &>/dev/null 2>&1 || [ "$DRY_RUN" = "true" ]; then
|
||||
run bash "$VM_TOOLS/suppress-maintenance-noise.sh" "$dom" \
|
||||
$([ "$DRY_RUN" = "true" ] && echo "--dry-run")
|
||||
ok "$dom: maintenance noise suppressed"
|
||||
fi
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# STEP 3 — Install guest helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
step "Installing guest helpers"
|
||||
info "Guest helpers are non-authoritative — advisory signals only."
|
||||
for dom in sc-workstation sc-web-server sc-build-machine; do
|
||||
domain_selected "$dom" || continue
|
||||
if virsh dominfo "$dom" &>/dev/null 2>&1 || [ "$DRY_RUN" = "true" ]; then
|
||||
run bash "$VM_TOOLS/install-guest-helper.sh" "$dom" \
|
||||
$([ "$DRY_RUN" = "true" ] && echo "--dry-run")
|
||||
ok "$dom: guest helper installed"
|
||||
fi
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# STEP 4 — Run quest-prep scripts and snapshot
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
step "Running quest-prep scripts and snapshotting"
|
||||
|
||||
run_prep_and_snapshot() {
|
||||
local quest_id="$1"
|
||||
local domain="$2"
|
||||
local snapshot_name="$3"
|
||||
local prep_script="$QUEST_PREP/${quest_id}-prep.sh"
|
||||
|
||||
if [ ! -f "$prep_script" ]; then
|
||||
echo " ⚠ No prep script found for $quest_id — skipping"
|
||||
return
|
||||
fi
|
||||
|
||||
info "Running $quest_id prep on $domain..."
|
||||
run bash "$prep_script" "$domain" $([ "$DRY_RUN" = "true" ] && echo "--dry-run")
|
||||
|
||||
info "Taking snapshot '$snapshot_name' on $domain..."
|
||||
run virsh snapshot-delete "$domain" "$snapshot_name" >/dev/null 2>&1 || true
|
||||
run virsh snapshot-create-as "$domain" "$snapshot_name" \
|
||||
--description "${quest_id} baseline — created by seed-vms.sh" \
|
||||
--atomic
|
||||
ok "$domain → $snapshot_name"
|
||||
}
|
||||
|
||||
run_post_clean_and_snapshot() {
|
||||
local quest_id="$1"
|
||||
local domain="$2"
|
||||
local snapshot_name="$3"
|
||||
local clean_script="$QUEST_PREP/${quest_id}-post-clean.sh"
|
||||
|
||||
if [ ! -f "$clean_script" ]; then
|
||||
echo " ⚠ No post-clean script found for $quest_id — skipping"
|
||||
return
|
||||
fi
|
||||
|
||||
info "Applying ${quest_id} clean branch state on $domain..."
|
||||
run bash "$clean_script" "$domain" $([ "$DRY_RUN" = "true" ] && echo "--dry-run")
|
||||
|
||||
info "Taking snapshot '$snapshot_name' on $domain..."
|
||||
run virsh snapshot-delete "$domain" "$snapshot_name" >/dev/null 2>&1 || true
|
||||
run virsh snapshot-create-as "$domain" "$snapshot_name" \
|
||||
--description "${quest_id} clean branch baseline — created by seed-vms.sh" \
|
||||
--atomic
|
||||
ok "$domain → $snapshot_name"
|
||||
}
|
||||
|
||||
# Q001: workstation day-one state
|
||||
if vm_selected "workstation"; then
|
||||
run_prep_and_snapshot "Q001" "sc-workstation" "baseline.day-one"
|
||||
fi
|
||||
|
||||
# Q002–Q004 share hermes clean baseline; prep scripts layer on top
|
||||
if vm_selected "web_server"; then
|
||||
run_prep_and_snapshot "Q002" "sc-web-server" "baseline.clean"
|
||||
run_prep_and_snapshot "Q003" "sc-web-server" "baseline.post-q002"
|
||||
run_prep_and_snapshot "Q004" "sc-web-server" "baseline.post-q003"
|
||||
fi
|
||||
|
||||
# Q005 and Q007 use post-q004 baseline
|
||||
if vm_selected "web_server"; then
|
||||
info "Creating baseline.post-q004 snapshot (used by Q005, Q007)..."
|
||||
run virsh snapshot-delete "sc-web-server" "baseline.post-q004" >/dev/null 2>&1 || true
|
||||
run virsh snapshot-create-as "sc-web-server" "baseline.post-q004" \
|
||||
--description "Post-Q004 baseline" --atomic || true
|
||||
fi
|
||||
|
||||
# Q006: build machine broken baseline, then authored clean handoff for later quests
|
||||
if vm_selected "build_machine"; then
|
||||
run_prep_and_snapshot "Q006" "sc-build-machine" "baseline.clean"
|
||||
run_post_clean_and_snapshot "Q006" "sc-build-machine" "baseline.post-q006"
|
||||
fi
|
||||
|
||||
info "Q008 remains a multi-VM authored-state gap and is not provisioned by seed-vms.sh yet."
|
||||
|
||||
# Take recovery snapshots (always available as fallback)
|
||||
step "Creating recovery snapshots"
|
||||
for dom in sc-workstation sc-web-server sc-build-machine; do
|
||||
domain_selected "$dom" || continue
|
||||
info "Creating baseline.recovery on $dom..."
|
||||
run virsh snapshot-delete "$dom" "baseline.recovery" >/dev/null 2>&1 || true
|
||||
run virsh snapshot-create-as "$dom" "baseline.recovery" \
|
||||
--description "Recovery fallback — created by seed-vms.sh" \
|
||||
--atomic || true
|
||||
ok "$dom → baseline.recovery"
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo ""
|
||||
echo "══════════════════════════════════════════════════"
|
||||
echo " Seed pipeline complete."
|
||||
echo " Verify with: bash tools/setup/check-host.sh"
|
||||
echo " Run content validation: bash tools/dev/test-content.sh"
|
||||
echo "══════════════════════════════════════════════════"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user