0265afa054
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.
312 lines
11 KiB
Bash
Executable File
312 lines
11 KiB
Bash
Executable File
#!/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 </dev/tty
|
||
echo ""
|
||
|
||
case "$choice" in
|
||
q|Q)
|
||
echo " Cancelled."
|
||
exit 0
|
||
;;
|
||
1)
|
||
sc_section "Reverting all VMs to baseline"
|
||
for vm in "${ALL_VMS[@]}"; do
|
||
_revert_to_baseline "$vm"
|
||
done
|
||
break
|
||
;;
|
||
2|3|4|5)
|
||
case "$choice" in
|
||
2) targets=(sc-workstation) ;;
|
||
3) targets=(sc-web-server) ;;
|
||
4) targets=(sc-build-machine) ;;
|
||
5) targets=("${ALL_VMS[@]}") ;;
|
||
esac
|
||
overall_status=0
|
||
for vm in "${targets[@]}"; do
|
||
label="${VM_LABEL[$vm]:-$vm}"
|
||
profile="${VM_PROFILE[$vm]}"
|
||
echo ""
|
||
sc_warn "This will permanently rebuild $label."
|
||
sc_warn "Quest progress on this VM will be lost."
|
||
echo ""
|
||
if sc_confirm "Back up save data first?" "Y"; then
|
||
BACKUP="$HOME/.local/share/sysadmin-chronicles/saves/pre-rebuild-$(date +%Y%m%d-%H%M%S).json"
|
||
[ -f "$HOME/.local/share/sysadmin-chronicles/saves/autosave.json" ] \
|
||
&& cp "$HOME/.local/share/sysadmin-chronicles/saves/autosave.json" "$BACKUP" \
|
||
&& sc_ok "Save backed up to $BACKUP" || sc_info "(no autosave found)"
|
||
fi
|
||
if ! sc_confirm "Rebuild $label now?" "N"; then
|
||
sc_info "Skipping $label."
|
||
continue
|
||
fi
|
||
sc_section "Rebuilding $label"
|
||
logfile="$HOME/.local/share/sysadmin-chronicles/rebuild-${profile}.log"
|
||
printf " Rebuilding %-18s " "$label"
|
||
start_ts="$(date +%s)"
|
||
if run vm_rebuild "$profile" $( [ "$DRY_RUN" = true ] && echo "--dry-run" ) \
|
||
> "$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
|
||
echo ""
|
||
vm="${ALL_VMS[$(( vm_choice - 1 ))]:-}"
|
||
[ -n "$vm" ] || { sc_warn "Invalid choice"; continue; }
|
||
printf " Snapshot name (letters, numbers, hyphens): " >/dev/tty
|
||
read -r snap_name </dev/tty
|
||
echo ""
|
||
run vm_snapshot_create "$vm" "$snap_name" \
|
||
&& sc_ok "Snapshot created: $snap_name on ${VM_LABEL[$vm]:-$vm}" \
|
||
|| sc_warn "Snapshot failed."
|
||
break
|
||
;;
|
||
7)
|
||
echo " Revert to a named 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
|
||
echo ""
|
||
vm="${ALL_VMS[$(( vm_choice - 1 ))]:-}"
|
||
[ -n "$vm" ] || { sc_warn "Invalid choice"; continue; }
|
||
echo " Available snapshots on ${VM_LABEL[$vm]:-$vm}:"
|
||
virsh snapshot-list "$vm" --name 2>/dev/null | grep -v '^$' | sed 's/^/ /' || true
|
||
echo ""
|
||
printf " Snapshot name to revert to: " >/dev/tty
|
||
read -r snap_name </dev/tty
|
||
echo ""
|
||
snap_date="$(virsh snapshot-info "$vm" "$snap_name" 2>/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 ""
|