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:
2026-05-02 11:49:07 -04:00
commit 0265afa054
252 changed files with 37574 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
#!/usr/bin/env bash
# check-host.sh — Sysadmin Chronicles host prerequisite checker
#
# Run this before first-run-setup.sh to see what's missing.
# Safe to run any number of times — read-only checks only.
#
# Exit code 0 = all required dependencies present.
# Exit code 1 = one or more required dependencies missing.
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}"
PASS="✓"
FAIL="✗"
WARN="⚠"
errors=0
warnings=0
run_virsh_quick() {
timeout 5 virsh "$@" >/dev/null 2>&1
}
check_cmd() {
local cmd="$1"
local label="${2:-$cmd}"
if command -v "$cmd" &>/dev/null; then
echo " $PASS $label"
else
echo " $FAIL $label — NOT FOUND"
((errors+=1))
fi
}
check_file() {
local path="$1"
local label="$2"
local required="${3:-true}"
if [ -e "$path" ]; then
echo " $PASS $label ($path)"
else
if [ "$required" = "true" ]; then
echo " $FAIL $label — NOT FOUND ($path)"
((errors+=1))
else
echo " $WARN $label — not found ($path) [optional]"
((warnings+=1))
fi
fi
}
check_group() {
local group="$1"
if id -nG "$OWNER_USER" | grep -qw "$group"; then
echo " $PASS User is in group: $group"
else
echo " $WARN Not in group '$group' — libvirt access may require sudo or group add"
((warnings+=1))
fi
}
check_kvm_access() {
if id -nG "$OWNER_USER" | grep -qw kvm; then
echo " $PASS User is in group: kvm"
elif [ -r /dev/kvm ] && [ -w /dev/kvm ]; then
echo " $PASS /dev/kvm is accessible to this session"
else
echo " $WARN Not in group 'kvm' — KVM acceleration may require sudo or group add"
((warnings+=1))
fi
}
libvirt_ready=false
socket_ready=false
if run_virsh_quick -q list --all; then
libvirt_ready=true
fi
if systemctl is-active --quiet libvirtd.socket 2>/dev/null || \
systemctl is-active --quiet virtqemud.socket 2>/dev/null; then
socket_ready=true
fi
# ---------------------------------------------------------------------------
echo ""
echo "══════════════════════════════════════════════════"
echo " Sysadmin Chronicles — Host Prerequisite Check"
echo "══════════════════════════════════════════════════"
echo ""
echo "── Virtualization ─────────────────────────────────"
check_file "/dev/kvm" "KVM device node"
check_cmd "virsh" "virsh (libvirt CLI)"
check_cmd "qemu-system-x86_64" "QEMU (x86_64)"
# libvirt runtime
if [ "$libvirt_ready" = "true" ]; then
echo " $PASS libvirt responds to virsh"
elif [ "$socket_ready" = "true" ]; then
echo " $WARN libvirt socket activation is available, but this user cannot reach $LIBVIRT_DEFAULT_URI"
echo " Add yourself to the libvirt group or use sudo for setup."
((warnings+=1))
else
echo " $FAIL libvirt is not reachable — start socket activation or the daemon"
echo " Example: sudo systemctl enable --now libvirtd.socket"
((errors+=1))
fi
check_group "libvirt"
check_kvm_access
echo ""
echo "── Storage ────────────────────────────────────────"
check_cmd "qemu-img" "qemu-img (disk image tool)"
# Storage pool exists check
if run_virsh_quick pool-info sc-images; then
echo " $PASS sc-images storage pool exists"
else
echo " $WARN sc-images storage pool not yet created (run first-run-setup.sh)"
((warnings+=1))
fi
echo ""
echo "── Networking ─────────────────────────────────────"
if run_virsh_quick net-info sc-internal; then
echo " $PASS sc-internal network exists"
if timeout 5 virsh net-dumpxml sc-internal 2>/dev/null | grep -q "<forward mode=['\"]nat['\"]"; then
echo " $PASS sc-internal has NAT egress for VM provisioning"
else
echo " $WARN sc-internal is missing NAT egress — rebuilds may fail package installation"
echo " Run: sudo bash tools/setup/first-run-setup.sh"
((warnings+=1))
fi
else
echo " $WARN sc-internal network not yet created (run first-run-setup.sh)"
((warnings+=1))
fi
echo ""
echo "── SSH Keys ───────────────────────────────────────"
check_file "$OWNER_HOME/.ssh/sc_host_key" "Host→Guest SSH key" "false"
check_file "$OWNER_HOME/.ssh/sc_host_key.pub" "Host→Guest SSH public key" "false"
echo ""
echo "── Runtime Tools ──────────────────────────────────"
check_cmd "ssh" "ssh"
check_cmd "node" "node (Node.js, for content validation)"
check_cmd "godot" "godot (Godot 4 engine binary)" || true # warn only
echo ""
echo "── VM Images ──────────────────────────────────────"
for vm in sc-workstation sc-web-server sc-build-machine; do
if run_virsh_quick dominfo "$vm"; then
echo " $PASS Domain exists: $vm"
else
echo " $WARN Domain not yet created: $vm (run seed-vms.sh)"
((warnings+=1))
fi
done
echo ""
echo "══════════════════════════════════════════════════"
if [ "$errors" -eq 0 ] && [ "$warnings" -eq 0 ]; then
echo " ✅ All checks passed. Ready to play."
elif [ "$errors" -eq 0 ]; then
echo "$warnings warning(s). Run first-run-setup.sh if this is a fresh install."
else
echo "$errors error(s), $warnings warning(s). Run first-run-setup.sh to resolve."
fi
echo "══════════════════════════════════════════════════"
echo ""
exit $([ "$errors" -eq 0 ] && echo 0 || echo 1)
+206
View File
@@ -0,0 +1,206 @@
#!/usr/bin/env bash
# first-run-setup.sh — Sysadmin Chronicles one-time host setup
#
# This script creates the libvirt resources (networks, storage pool) and SSH
# keys required for the game. It is safe to re-run — all actions are idempotent.
#
# What this does:
# 1. Creates sc-internal libvirt network (private NAT, game-only)
# 2. Creates sc-images storage pool for qcow2 VM images
# 3. Generates the host→guest SSH key pair (sc_host_key)
# 4. Checks libvirtd is running and user has access
#
# What this does NOT do:
# - Build VM images (that's seed-vms.sh)
# - Modify /etc/libvirt or system-wide config beyond the named libvirt resources
# - Require broad sudo during normal gameplay
#
# AGENT RULES: Never run provisioning scripts against VMs from here.
# This script only creates host infrastructure.
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}"
DRY_RUN=false
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=true
echo "[DRY-RUN] No changes will be made."
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
run() {
if [ "$DRY_RUN" = "true" ]; then
echo " [would run] $*"
else
"$@"
fi
}
step() { echo ""; echo "── $* ───────────────────────"; }
ok() { echo "$*"; }
info() { echo "$*"; }
libvirt_reachable() {
virsh -q list --all >/dev/null 2>&1
}
libvirt_socket_available() {
systemctl is-active --quiet libvirtd.socket 2>/dev/null || \
systemctl is-active --quiet virtqemud.socket 2>/dev/null
}
echo ""
echo "══════════════════════════════════════════════════"
echo " Sysadmin Chronicles — First-Run Setup"
echo "══════════════════════════════════════════════════"
# Sanity: check libvirt access
step "Checking libvirt access"
if libvirt_reachable; then
ok "libvirt responds to virsh"
elif libvirt_socket_available; then
ok "libvirt socket activation is available"
else
echo " ERROR: libvirt is not reachable."
echo " Run: sudo systemctl enable --now libvirtd.socket"
exit 1
fi
# ---------------------------------------------------------------------------
step "Creating sc-internal libvirt network"
# ---------------------------------------------------------------------------
NETWORK_XML="$SCRIPT_DIR/../vm/network-sc-internal.xml"
write_network_xml() {
local target="$1"
cat > "$target" << 'EOF'
<network>
<name>sc-internal</name>
<forward mode='nat'/>
<bridge name='sc-br0' stp='on' delay='0'/>
<ip address='10.42.0.1' netmask='255.255.255.0'>
<dhcp>
<range start='10.42.0.10' end='10.42.0.50'/>
</dhcp>
</ip>
</network>
EOF
}
network_has_nat() {
virsh net-dumpxml sc-internal 2>/dev/null | grep -q "<forward mode=['\"]nat['\"]"
}
define_network() {
local label="$1"
local tmpxml
tmpxml=$(mktemp /tmp/sc-internal-XXXXXX.xml)
if [ -f "$NETWORK_XML" ]; then
cp "$NETWORK_XML" "$tmpxml"
else
write_network_xml "$tmpxml"
fi
run virsh net-define "$tmpxml"
run virsh net-autostart sc-internal
run virsh net-start sc-internal
rm -f "$tmpxml"
ok "$label"
}
if virsh net-list --all | grep -q "sc-internal"; then
if network_has_nat; then
ok "sc-internal network already exists with NAT"
else
info "Recreating sc-internal with NAT egress for guest provisioning..."
run virsh net-destroy sc-internal >/dev/null 2>&1 || true
run virsh net-undefine sc-internal
define_network "sc-internal network recreated and started"
fi
else
info "Creating sc-internal (private NAT, game-scoped network)..."
define_network "sc-internal network created and started"
fi
# ---------------------------------------------------------------------------
step "Creating sc-images storage pool"
# ---------------------------------------------------------------------------
if [ "${LIBVIRT_DEFAULT_URI}" = "qemu:///system" ]; then
IMAGES_DIR="/var/lib/libvirt/images/sysadmin-chronicles"
else
IMAGES_DIR="$OWNER_HOME/.local/share/sysadmin-chronicles/images"
fi
if virsh pool-list --all | grep -q "sc-images"; then
CURRENT_POOL_PATH="$(virsh pool-dumpxml sc-images 2>/dev/null | sed -n 's:.*<path>\(.*\)</path>.*:\1:p' | head -n1)"
if [ "$CURRENT_POOL_PATH" = "$IMAGES_DIR" ]; then
ok "sc-images pool already exists"
else
info "Recreating sc-images pool at $IMAGES_DIR (was $CURRENT_POOL_PATH)..."
run mkdir -p "$IMAGES_DIR"
run virsh pool-destroy sc-images >/dev/null 2>&1 || true
run virsh pool-undefine sc-images
run virsh pool-define-as sc-images dir --target "$IMAGES_DIR"
run virsh pool-autostart sc-images
run virsh pool-start sc-images
ok "sc-images pool recreated"
fi
else
info "Creating sc-images pool at $IMAGES_DIR..."
run mkdir -p "$IMAGES_DIR"
run virsh pool-define-as sc-images dir --target "$IMAGES_DIR"
run virsh pool-autostart sc-images
run virsh pool-start sc-images
ok "sc-images pool created"
fi
# ---------------------------------------------------------------------------
step "Generating SSH key pair"
# ---------------------------------------------------------------------------
KEY_PATH="$OWNER_HOME/.ssh/sc_host_key"
if [ -f "$KEY_PATH" ]; then
run chown "$OWNER_USER:$OWNER_USER" "$KEY_PATH" "${KEY_PATH}.pub" >/dev/null 2>&1 || true
run chmod 600 "$KEY_PATH" >/dev/null 2>&1 || true
run chmod 644 "${KEY_PATH}.pub" >/dev/null 2>&1 || true
ok "SSH key already exists: $KEY_PATH"
else
info "Generating ed25519 key: $KEY_PATH"
run mkdir -p "$(dirname "$KEY_PATH")"
run ssh-keygen -t ed25519 -N "" -C "sysadmin-chronicles-host" -f "$KEY_PATH"
run chmod 600 "$KEY_PATH"
run chmod 644 "${KEY_PATH}.pub"
run chown "$OWNER_USER:$OWNER_USER" "$KEY_PATH" "${KEY_PATH}.pub"
ok "SSH key generated"
info "Public key (add to VM authorized_keys during build):"
if [ "$DRY_RUN" = "false" ]; then
cat "${KEY_PATH}.pub"
fi
fi
# ---------------------------------------------------------------------------
step "Verifying group membership"
# ---------------------------------------------------------------------------
if id -nG | grep -qw "libvirt"; then
ok "User is in libvirt group"
else
info "Consider adding yourself to the libvirt group for passwordless VM control:"
info " sudo usermod -aG libvirt \$USER && newgrp libvirt"
fi
# ---------------------------------------------------------------------------
echo ""
echo "══════════════════════════════════════════════════"
echo " Setup complete."
echo " Next step: bash tools/setup/seed-vms.sh"
echo "══════════════════════════════════════════════════"
echo ""
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Generates a self-signed CA and server certificate for Sysadmin Chronicles TLS.
# Idempotent — skips if certs already exist.
# Run this before building VMs. Called by install.sh automatically.
set -euo pipefail
SC_CERT_DIR="${SC_CERT_DIR:-$HOME/.local/share/sysadmin-chronicles/certs}"
mkdir -p "$SC_CERT_DIR"
chmod 700 "$SC_CERT_DIR"
if [[ -f "$SC_CERT_DIR/server.crt" && -f "$SC_CERT_DIR/server.key" && -f "$SC_CERT_DIR/ca.crt" ]]; then
echo "TLS certs already exist at $SC_CERT_DIR — skipping."
exit 0
fi
echo "Generating Axiom Works internal CA..."
openssl genrsa -out "$SC_CERT_DIR/ca.key" 4096 2>/dev/null
openssl req -new -x509 -days 3650 \
-key "$SC_CERT_DIR/ca.key" \
-out "$SC_CERT_DIR/ca.crt" \
-subj "/CN=Axiom Works Internal CA/O=Axiom Works" 2>/dev/null
echo "Generating server certificate..."
openssl genrsa -out "$SC_CERT_DIR/server.key" 4096 2>/dev/null
openssl req -new \
-key "$SC_CERT_DIR/server.key" \
-out "$SC_CERT_DIR/server.csr" \
-subj "/CN=portal.axiomworks.internal/O=Axiom Works" 2>/dev/null
cat > "$SC_CERT_DIR/server.ext" <<'EXTEOF'
subjectAltName=DNS:portal.axiomworks.internal,DNS:sage.axiomworks.internal,DNS:axiomworks.corp,DNS:www.axiomworks.corp,DNS:*.axiomworks.internal,DNS:*.axiomworks.corp
EXTEOF
openssl x509 -req -days 3650 \
-in "$SC_CERT_DIR/server.csr" \
-CA "$SC_CERT_DIR/ca.crt" \
-CAkey "$SC_CERT_DIR/ca.key" \
-CAcreateserial \
-out "$SC_CERT_DIR/server.crt" \
-extfile "$SC_CERT_DIR/server.ext" 2>/dev/null
chmod 600 "$SC_CERT_DIR/ca.key" "$SC_CERT_DIR/server.key"
rm -f "$SC_CERT_DIR/server.csr" "$SC_CERT_DIR/server.ext"
echo "TLS certs generated at $SC_CERT_DIR"
echo " CA cert: $SC_CERT_DIR/ca.crt"
echo " Server cert: $SC_CERT_DIR/server.crt"
echo " Server key: $SC_CERT_DIR/server.key"
+327
View File
@@ -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
# Q002Q004 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 ""
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
# uninstall.sh — Remove all Sysadmin Chronicles game-owned host resources.
#
# Removes:
# - sc- prefixed libvirt VM domains (after confirmation)
# - sc- prefixed libvirt networks
# - sc-images storage pool and its directory
# - SSH key pair (~/.ssh/sc_host_key)
#
# Does NOT remove:
# - The game directory itself (remove manually if desired)
# - Any system-wide libvirt config
# - Any resources not prefixed with sc-
#
# Usage:
# bash tools/setup/uninstall.sh
# bash tools/setup/uninstall.sh --dry-run
# bash tools/setup/uninstall.sh --yes (skip confirmation)
set -euo pipefail
DRY_RUN=false
ASSUME_YES=false
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
--yes) ASSUME_YES=true ;;
esac
done
run() {
if [ "$DRY_RUN" = "true" ]; then echo " [DRY-RUN] $*"; else "$@"; fi
}
echo ""
echo "══════════════════════════════════════════════════"
echo " Sysadmin Chronicles — Uninstall"
echo "══════════════════════════════════════════════════"
[ "$DRY_RUN" = "true" ] && echo " [DRY-RUN mode — no changes]"
echo ""
echo " This will PERMANENTLY remove all sc- prefixed VMs,"
echo " networks, storage, and SSH keys."
echo ""
if [ "$ASSUME_YES" = "false" ] && [ "$DRY_RUN" = "false" ]; then
read -rp " Type YES to confirm uninstall: " confirm
if [ "$confirm" != "YES" ]; then
echo " Aborted."
exit 0
fi
fi
# ---------------------------------------------------------------------------
# Remove VMs
# ---------------------------------------------------------------------------
echo ""
echo "── Removing VM domains ──────────────────────────"
for domain in $(virsh list --all --name 2>/dev/null | grep "^sc-" || true); do
echo " Removing $domain..."
# Stop if running
if virsh domstate "$domain" 2>/dev/null | grep -q "running"; then
run virsh destroy "$domain"
fi
# Remove all snapshots
for snap in $(virsh snapshot-list "$domain" --name 2>/dev/null || true); do
run virsh snapshot-delete "$domain" "$snap"
done
run virsh undefine "$domain" --remove-all-storage
echo "$domain removed"
done
# ---------------------------------------------------------------------------
# Remove networks
# ---------------------------------------------------------------------------
echo ""
echo "── Removing sc- networks ─────────────────────────"
for net in $(virsh net-list --all --name 2>/dev/null | grep "^sc-" || true); do
echo " Removing network $net..."
if virsh net-info "$net" 2>/dev/null | grep -q "Active:.*yes"; then
run virsh net-destroy "$net"
fi
run virsh net-undefine "$net"
echo "$net removed"
done
# ---------------------------------------------------------------------------
# Remove storage pool
# ---------------------------------------------------------------------------
echo ""
echo "── Removing sc-images storage pool ──────────────"
if virsh pool-list --all | grep -q "sc-images"; then
POOL_PATH=$(virsh pool-dumpxml sc-images 2>/dev/null | grep -oP '(?<=<path>)[^<]+' || echo "")
if virsh pool-info sc-images 2>/dev/null | grep -q "State:.*running"; then
run virsh pool-destroy sc-images
fi
run virsh pool-undefine sc-images
if [ -n "$POOL_PATH" ] && [ -d "$POOL_PATH" ]; then
run rm -rf "$POOL_PATH"
fi
echo " ✓ sc-images pool removed"
else
echo " (sc-images pool not found — skipping)"
fi
# ---------------------------------------------------------------------------
# Remove SSH keys
# ---------------------------------------------------------------------------
echo ""
echo "── Removing SSH keys ─────────────────────────────"
KEY="$HOME/.ssh/sc_host_key"
if [ -f "$KEY" ]; then
run rm -f "$KEY" "${KEY}.pub"
echo " ✓ SSH keys removed"
else
echo " (No sc_host_key found — skipping)"
fi
echo ""
echo "══════════════════════════════════════════════════"
echo " Uninstall complete."
echo " Game files (this directory) were not removed."
echo "══════════════════════════════════════════════════"
echo ""