#!/usr/bin/env bash # Sysadmin Chronicles — Installer # Usage: bash install.sh set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$SCRIPT_DIR" source "$PROJECT_ROOT/tools/lib/ui.sh" source "$PROJECT_ROOT/tools/lib/config.sh" source "$PROJECT_ROOT/tools/lib/deps.sh" source "$PROJECT_ROOT/tools/lib/libvirt.sh" source "$PROJECT_ROOT/tools/lib/vm.sh" OWNER_USER="${SUDO_USER:-$USER}" OWNER_HOME="$(getent passwd "$OWNER_USER" | cut -d: -f6)" OWNER_HOME="${OWNER_HOME:-$HOME}" SC_LOG_DIR="$OWNER_HOME/.local/share/sysadmin-chronicles" export SC_INSTALL_LOG="$SC_LOG_DIR/install.log" export LIBVIRT_DEFAULT_URI="${LIBVIRT_DEFAULT_URI:-qemu:///system}" # --------------------------------------------------------------------------- # Phase 1 — Welcome # --------------------------------------------------------------------------- sc_header "SYSADMIN CHRONICLES — SETUP" cat << 'WELCOME' Welcome! This installer will: • Install a few system tools (KVM, QEMU, libvirt) • Set up a private virtual network for the game • Build three virtual machines (~30 minutes, once only) WELCOME DEFAULT_GAME_DIR="$OWNER_HOME/Games/sysadmin-chronicles" [ "$PROJECT_ROOT" = "$DEFAULT_GAME_DIR" ] || DEFAULT_GAME_DIR="$PROJECT_ROOT" SC_GAME_DIR="$(sc_prompt "Where would you like to install the game?" "$DEFAULT_GAME_DIR")" while [[ "$SC_GAME_DIR" == *//* ]]; do SC_GAME_DIR="${SC_GAME_DIR//\/\//\/}" done while [ "$SC_GAME_DIR" != "/" ] && [ "${SC_GAME_DIR%/}" != "$SC_GAME_DIR" ]; do SC_GAME_DIR="${SC_GAME_DIR%/}" done echo "" SC_IMAGES_DIR="$SC_GAME_DIR/images" # Build scripts normally default to /var/lib/libvirt when using qemu:///system. # The installer asks for a custom game directory, so force the VM tooling to use # the libvirt storage pool path selected above instead of silently falling back # to /var/lib/libvirt/images/sysadmin-chronicles. export SC_HOME="$SC_LOG_DIR" export SC_IMAGE_ROOT="$SC_IMAGES_DIR" export SC_POOL_NAME="sc-images" export SC_NETWORK_NAME="sc-internal" export SC_OWNER_USER="$OWNER_USER" export SC_OWNER_HOME="$OWNER_HOME" sc_log_append() { mkdir -p "$SC_LOG_DIR" { printf '\n[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" } >> "$SC_INSTALL_LOG" 2>/dev/null || true } sc_log_cmd() { mkdir -p "$SC_LOG_DIR" { printf '\n[%s] $' "$(date '+%Y-%m-%d %H:%M:%S')" printf ' %q' "$@" printf '\n' "$@" } >> "$SC_INSTALL_LOG" 2>&1 } # --------------------------------------------------------------------------- # Phase 2 — Silent system check # --------------------------------------------------------------------------- detect_distro if [ "$SC_DISTRO" = "unknown" ]; then sc_warn "Could not detect your Linux distribution." sc_warn "Dependency auto-install may not work — you may need to install packages manually." echo "" fi mapfile -t _missing_deps < <(check_deps 2>/dev/null || true) # --------------------------------------------------------------------------- # Phase 3 — Dependency install (only if needed) # --------------------------------------------------------------------------- if [ "${#_missing_deps[@]}" -gt 0 ]; then sc_section "System check" echo "" echo " Your system is missing the following tools:" echo "" _shown_labels="" for _dep in "${_missing_deps[@]}"; do [ -z "$_dep" ] && continue _label="$(dep_label "$_dep")" [ -z "$_label" ] && continue case "|${_shown_labels}|" in *"|${_label}|"*) ;; *) echo " • $_label"; _shown_labels="${_shown_labels}|${_label}" ;; esac done echo "" if sc_confirm "Install them now? You'll be asked for your password." "Y"; then echo "" mkdir -p "$SC_LOG_DIR" # Write log header cat > "$SC_INSTALL_LOG" << EOF # Sysadmin Chronicles — Install Log # Created: $(date '+%Y-%m-%d %H:%M:%S') # Distro: ${SC_DISTRO} ($(uname -r)) # Game dir: $SC_GAME_DIR # Images: $SC_IMAGES_DIR EOF install_deps "${_missing_deps[@]}" log_present_deps echo "" # Append manual removal hint { echo "" echo "# To remove manually:" printf '# sudo %s\n' "$(case "$SC_DISTRO" in arch) echo 'pacman -Rns libvirt qemu-system-x86 virt-install virt-viewer' ;; debian|ubuntu) echo 'apt-get remove libvirt-daemon-system qemu-kvm virt-manager' ;; fedora) echo 'dnf remove libvirt qemu-kvm virt-install' ;; *) echo 'remove the packages listed above' ;; esac)" } >> "$SC_INSTALL_LOG" sc_ok "Dependencies installed." echo "" echo " Install log: $SC_INSTALL_LOG" echo "" else echo "" sc_warn "Skipping dependency install. Some features may not work." echo "" fi else mkdir -p "$SC_LOG_DIR" if [ ! -f "$SC_INSTALL_LOG" ]; then cat > "$SC_INSTALL_LOG" << EOF # Sysadmin Chronicles — Install Log # Created: $(date '+%Y-%m-%d %H:%M:%S') # Distro: ${SC_DISTRO} ($(uname -r)) # Game dir: $SC_GAME_DIR # Images: $SC_IMAGES_DIR EOF log_present_deps fi fi # --------------------------------------------------------------------------- # Phase 4 — Network, storage, and SSH key setup # --------------------------------------------------------------------------- # Ensure libvirtd is running before touching networks or pools. # Pacman installs the socket unit but does not start/enable it. _libvirtd_ready=false _need_relogin=false # Ensure user is in the libvirt group (needed for virsh access without sudo) if ! groups "$OWNER_USER" 2>/dev/null | grep -qw libvirt; then sc_info "Adding $OWNER_USER to the libvirt group..." sudo usermod -aG libvirt "$OWNER_USER" 2>/dev/null || true _need_relogin=true fi # Clear any failed unit state from a previous attempt sudo systemctl reset-failed libvirtd.service libvirtd.socket 2>/dev/null || true if ! systemctl is-active --quiet libvirtd.service 2>/dev/null; then sc_info "Starting virtual machine daemon..." # Enable the socket for future boots, start the service directly now sudo systemctl enable libvirtd.socket >/dev/null 2>&1 || true if ! sudo systemctl start libvirtd.service >/dev/null 2>&1; then echo "" echo " libvirtd failed to start. Details:" sudo systemctl status libvirtd.service --no-pager -l 2>&1 | tail -25 | sed 's/^/ /' echo "" sc_fail "Fix the error above, then run install.sh again." fi fi # Wait up to 30s for libvirtd.service to reach active state (pure systemd # check — no virsh connection needed, works regardless of group membership # in the current session). for _i in $(seq 1 60); do if systemctl is-active --quiet libvirtd.service 2>/dev/null; then _libvirtd_ready=true break fi sleep 0.5 done if [ "$_libvirtd_ready" != true ]; then echo "" echo " libvirtd did not reach active state. Current status:" sudo systemctl status libvirtd.service --no-pager -l 2>&1 | tail -25 | sed 's/^/ /' echo "" sc_fail "Fix the error above, then run install.sh again." fi # If the group was just added, the current shell does not have it yet. The # install can still proceed by using sudo for virsh operations. if virsh -c "$LIBVIRT_DEFAULT_URI" list >/dev/null 2>&1; then export SC_VIRSH_SUDO=false else export SC_VIRSH_SUDO=true sc_log_append "virsh is not usable by $OWNER_USER in this session; using sudo for installer libvirt operations." fi sc_log_append "Installer paths: SC_GAME_DIR=$SC_GAME_DIR SC_IMAGES_DIR=$SC_IMAGES_DIR SC_IMAGE_ROOT=$SC_IMAGE_ROOT LIBVIRT_DEFAULT_URI=$LIBVIRT_DEFAULT_URI SC_VIRSH_SUDO=${SC_VIRSH_SUDO:-false}" sc_log_cmd id || true sc_log_cmd groups "$OWNER_USER" || true sc_log_cmd systemctl is-active libvirtd.service || true sc_section "Setting up game network" echo "" NETWORK_XML="$PROJECT_ROOT/tools/vm/network-sc-internal.xml" sc_spinner "Creating private game network" if ! ensure_network "sc-internal" "$NETWORK_XML"; then sc_spinner_stop echo "" sc_fail "Could not create the game network. Make sure the virtual machine tools are installed and running: sudo systemctl enable --now libvirtd.socket Then run install.sh again." fi sc_spinner_stop sc_ok "Private game network created" sc_spinner "Configuring VM image storage" if ! ensure_pool "sc-images" "$SC_IMAGES_DIR"; then sc_spinner_stop sc_fail "Could not configure image storage at $SC_IMAGES_DIR" fi sc_spinner_stop sc_ok "VM image storage configured at $SC_IMAGES_DIR" # Pre-create subdirectories used by tools/vm/lib/common.sh. Since these live # under the user-selected install path, they should be user-writable. This also # fails early with a useful message instead of producing a one-line build log. if ! mkdir -p "$SC_IMAGES_DIR/base" "$SC_IMAGES_DIR/seed"; then sc_fail "Could not create VM image directories under $SC_IMAGES_DIR" fi chown -R "$OWNER_USER:$OWNER_USER" "$SC_IMAGES_DIR" 2>/dev/null || true sc_log_cmd ls -lah "$SC_IMAGES_DIR" || true SC_SSH_KEY="$OWNER_HOME/.ssh/sc_host_key" if [ ! -f "$SC_SSH_KEY" ]; then sc_spinner "Generating game access keys" mkdir -p "$(dirname "$SC_SSH_KEY")" ssh-keygen -t ed25519 -N "" -C "sysadmin-chronicles-host" -f "$SC_SSH_KEY" >/dev/null 2>&1 chmod 600 "$SC_SSH_KEY" chmod 644 "${SC_SSH_KEY}.pub" chown "$OWNER_USER:$OWNER_USER" "$SC_SSH_KEY" "${SC_SSH_KEY}.pub" 2>/dev/null || true sc_spinner_stop sc_ok "Game access keys generated" else sc_ok "Game access keys already present" fi # Write config (survives game dir moves) config_write SC_GAME_DIR "$SC_GAME_DIR" config_write SC_IMAGES_DIR "$SC_IMAGES_DIR" config_write SC_LIBVIRT_URI "$LIBVIRT_DEFAULT_URI" config_write SC_INSTALL_DATE "$(date '+%Y-%m-%d')" echo "" # --------------------------------------------------------------------------- # Phase 5 — VM build # --------------------------------------------------------------------------- sc_section "Building your game world" echo "" echo " This happens once and takes about 30 minutes." echo " You can leave this running in the background." echo "" _build_vm() { local label="$1" local n="$2" local total="$3" local profile="$4" local logfile="$SC_LOG_DIR/build-${profile}.log" local start_ts elapsed mins secs start_ts="$(date +%s)" printf " Building %-20s (%d/%d) " "$label" "$n" "$total" { echo "# Sysadmin Chronicles VM build log" echo "# Created: $(date '+%Y-%m-%d %H:%M:%S')" echo "# Profile: $profile" echo "# Game dir: $SC_GAME_DIR" echo "# Images: $SC_IMAGES_DIR" echo "# SC_IMAGE_ROOT: $SC_IMAGE_ROOT" echo "# LIBVIRT_DEFAULT_URI: $LIBVIRT_DEFAULT_URI" echo "# SC_VIRSH_SUDO: ${SC_VIRSH_SUDO:-false}" echo "" } > "$logfile" if vm_build "$profile" >> "$logfile" 2>&1; then elapsed=$(( $(date +%s) - start_ts )) mins=$(( elapsed / 60 )) secs=$(( elapsed % 60 )) printf "✓ %dm %02ds\n" "$mins" "$secs" else printf "✗\n" sc_warn "Build failed — see $logfile" return 1 fi } echo "Generating TLS certificates..." bash "$PROJECT_ROOT/tools/setup/generate-certs.sh" echo "" _build_vm "workstation" 1 3 "workstation" _build_vm "web server" 2 3 "web-server" _build_vm "build server" 3 3 "build-machine" printf " Setting up quest scenarios " bash "$PROJECT_ROOT/tools/setup/seed-vms.sh" --skip-build \ > "$SC_LOG_DIR/seed-vms.log" 2>&1 && printf "✓\n" || { printf "✗\n"; sc_warn "Quest setup had errors — see $SC_LOG_DIR/seed-vms.log"; } echo "" # --------------------------------------------------------------------------- # Phase 6 — Application menu entry # --------------------------------------------------------------------------- sc_section "Application menu launcher" echo "" if sc_confirm "Create an application-menu launcher? This does not add a desktop icon." "Y"; then DESKTOP_FILE="$OWNER_HOME/.local/share/applications/sysadmin-chronicles.desktop" mkdir -p "$(dirname "$DESKTOP_FILE")" cat > "$DESKTOP_FILE" << EOF [Desktop Entry] Name=Sysadmin Chronicles Comment=A sysadmin simulation game Exec=bash $SC_GAME_DIR/start-game.sh Terminal=true Type=Application Categories=Game; EOF sc_ok "Application-menu launcher created" fi echo "" # --------------------------------------------------------------------------- # Phase 7 — Done # --------------------------------------------------------------------------- sc_done_banner cat << EOF Start the game: bash $SC_GAME_DIR/start-game.sh (or from your system application menu if you created the launcher; no desktop icon is installed) Rebuild the virtual machines at any time: bash $SC_GAME_DIR/tools/vm/rebuild-vms.sh Install log: $SC_INSTALL_LOG EOF if [ "$_need_relogin" = true ]; then sc_warn "You were added to the 'libvirt' group during install." sc_warn "Log out and back in before running the game, or run:" echo " newgrp libvirt" echo "" fi