#!/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 ""