#!/usr/bin/env bash # Rebuild or revert game virtual machines. # # Usage: # rebuild-vms.sh Interactive menu # rebuild-vms.sh --vm workstation Rebuild a single VM (interactive) # rebuild-vms.sh --revert Revert all VMs to baseline snapshot # rebuild-vms.sh --revert --vm workstation # rebuild-vms.sh --snapshot --vm workstation --name before-risky-thing # rebuild-vms.sh --snapshot --all --name pre-shift-4 # rebuild-vms.sh --revert --name before-risky-thing --vm workstation # rebuild-vms.sh --dry-run [other flags] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" source "$PROJECT_ROOT/tools/lib/ui.sh" source "$PROJECT_ROOT/tools/lib/config.sh" source "$PROJECT_ROOT/tools/lib/libvirt.sh" source "$PROJECT_ROOT/tools/lib/vm.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_GAME_DIR:-}" ]; then normalized_game_dir="$(_normalize_dir_path "$SC_GAME_DIR")" if [ "$normalized_game_dir" != "$SC_GAME_DIR" ]; then SC_GAME_DIR="$normalized_game_dir" config_write SC_GAME_DIR "$SC_GAME_DIR" fi fi if [ -n "${SC_IMAGES_DIR:-}" ]; then normalized_images_dir="$(_normalize_dir_path "$SC_IMAGES_DIR")" elif [ -n "${SC_GAME_DIR:-}" ]; then normalized_images_dir="$SC_GAME_DIR/images" else normalized_images_dir="" fi if [ -n "$normalized_images_dir" ]; then if [ "${SC_IMAGES_DIR:-}" != "$normalized_images_dir" ]; then SC_IMAGES_DIR="$normalized_images_dir" config_write SC_IMAGES_DIR "$SC_IMAGES_DIR" fi export SC_IMAGE_ROOT="$SC_IMAGES_DIR" fi export LIBVIRT_DEFAULT_URI="${SC_LIBVIRT_URI:-${LIBVIRT_DEFAULT_URI:-qemu:///system}}" export SC_POOL_NAME="${SC_POOL_NAME:-sc-images}" export SC_NETWORK_NAME="${SC_NETWORK_NAME:-sc-internal}" # VM display names declare -A VM_LABEL=( [sc-workstation]="workstation" [sc-web-server]="web server" [sc-build-machine]="build server" ) declare -A VM_PROFILE=( [sc-workstation]=workstation [sc-web-server]=web-server [sc-build-machine]=build-machine ) ALL_VMS=(sc-workstation sc-web-server sc-build-machine) DRY_RUN=false MODE="" SINGLE_VM="" SNAP_NAME="" while [[ $# -gt 0 ]]; do case "$1" in --dry-run) DRY_RUN=true; shift ;; --revert) MODE=revert; shift ;; --snapshot) MODE=snapshot; shift ;; --vm) SINGLE_VM="sc-$2"; shift 2 ;; --name) SNAP_NAME="$2"; shift 2 ;; --all) SINGLE_VM=""; shift ;; *) echo "Unknown argument: $1"; exit 1 ;; esac done run() { if [ "$DRY_RUN" = true ]; then echo " [dry-run] $*" else "$@" fi } # Revert one VM to its newest baseline snapshot; prints result. _revert_to_baseline() { local vm="$1" local label="${VM_LABEL[$vm]:-$vm}" local candidate local_snap="" for candidate in "baseline.recovery" "baseline.day-one" "baseline.clean"; do if snapshot_exists "$vm" "$candidate" 2>/dev/null; then local_snap="$candidate" break fi done if [ -n "$local_snap" ]; then sc_info "Reverting $label to $local_snap..." run snapshot_revert "$vm" "$local_snap" sc_ok "$label reverted to $local_snap" else sc_warn "No baseline snapshot found for $vm — skipping" fi } _target_vms() { if [ -n "$SINGLE_VM" ]; then echo "$SINGLE_VM" else printf '%s\n' "${ALL_VMS[@]}" fi } # --------------------------------------------------------------------------- # Non-interactive flag modes # --------------------------------------------------------------------------- if [ "$MODE" = "revert" ] && [ -n "$SNAP_NAME" ]; then sc_header "REVERTING TO SNAPSHOT: $SNAP_NAME" while IFS= read -r vm; do label="${VM_LABEL[$vm]:-$vm}" sc_info "Reverting $label..." if snapshot_exists "$vm" "$SNAP_NAME"; then run snapshot_revert "$vm" "$SNAP_NAME" sc_ok "$label reverted to $SNAP_NAME" else sc_warn "Snapshot '$SNAP_NAME' not found on $vm — skipping" fi done < <(_target_vms) exit 0 fi if [ "$MODE" = "snapshot" ]; then [ -n "$SNAP_NAME" ] || { echo " --snapshot requires --name"; exit 1; } while IFS= read -r vm; do label="${VM_LABEL[$vm]:-$vm}" sc_info "Snapshotting $label as '$SNAP_NAME'..." run vm_snapshot_create "$vm" "$SNAP_NAME" sc_ok "$label → $SNAP_NAME" done < <(_target_vms) exit 0 fi if [ "$MODE" = "revert" ]; then sc_header "REVERTING TO BASELINE" while IFS= read -r vm; do _revert_to_baseline "$vm" done < <(_target_vms) exit 0 fi # --------------------------------------------------------------------------- # Interactive menu # --------------------------------------------------------------------------- sc_header "SYSADMIN CHRONICLES — VM TOOLS" while true; do echo " What would you like to do?" echo "" echo " 1) Revert all VMs to last known good (fast — ~30s)" echo " 2) Rebuild workstation (~8 min)" echo " 3) Rebuild web server (~4 min)" echo " 4) Rebuild build server (~5 min)" echo " 5) Rebuild everything (~20 min)" echo " 6) Take a snapshot" echo " 7) Revert to a named snapshot" echo "" echo " q) Cancel" echo "" printf " > " >/dev/tty read -r choice "$logfile" 2>&1; then elapsed=$(( $(date +%s) - start_ts )) printf "✓ %dm %02ds\n" $(( elapsed / 60 )) $(( elapsed % 60 )) else printf "✗\n" sc_warn "Rebuild failed — see $logfile" overall_status=1 continue fi # Re-run quest prep and re-snapshot sc_info "Re-running quest prep for $vm..." if run bash "$PROJECT_ROOT/tools/setup/seed-vms.sh" --skip-build --vm "${profile//-/_}" \ >> "$logfile" 2>&1; then sc_ok "$label rebuild complete" else sc_warn "Quest prep had errors — see $logfile" overall_status=1 fi done [ "$overall_status" -eq 0 ] || exit "$overall_status" break ;; 6) echo " Take a snapshot" echo "" echo " Which VM?" for i in "${!ALL_VMS[@]}"; do vm="${ALL_VMS[$i]}" printf " %d) %s\n" $(( i + 1 )) "${VM_LABEL[$vm]:-$vm}" done printf " > " >/dev/tty read -r vm_choice /dev/tty read -r snap_name " >/dev/tty read -r vm_choice /dev/null | grep -v '^$' | sed 's/^/ /' || true echo "" printf " Snapshot name to revert to: " >/dev/tty read -r snap_name /dev/null | grep 'Creation Time' | awk '{print $3, $4}' || echo "")" [ -n "$snap_date" ] && sc_info "Snapshot date: $snap_date" if sc_confirm "Revert ${VM_LABEL[$vm]:-$vm} to '$snap_name'?" "N"; then run vm_snapshot_revert "$vm" "$snap_name" \ && sc_ok "Reverted to $snap_name" \ || sc_warn "Revert failed." fi break ;; *) sc_warn "Invalid choice — enter 1–7 or q." ;; esac done echo ""