#!/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'
sc-internal
EOF
}
network_has_nat() {
virsh net-dumpxml sc-internal 2>/dev/null | grep -q "/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:.*\(.*\).*:\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 ""