chore: bootstrap lean sysadmin-chronicles repo

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.
This commit is contained in:
2026-05-02 11:49:07 -04:00
commit 0265afa054
252 changed files with 37574 additions and 0 deletions
+238
View File
@@ -0,0 +1,238 @@
#!/usr/bin/env bash
# Save slot and VM snapshot management for Sysadmin Chronicles.
#
# Usage:
# manage-saves.sh Interactive menu
# manage-saves.sh --list List save slots (non-interactive)
# manage-saves.sh --reset Reset current save
# manage-saves.sh --reset slot-1 Reset a specific slot
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/save.sh"
source "$PROJECT_ROOT/tools/lib/libvirt.sh"
source "$PROJECT_ROOT/tools/lib/vm.sh"
config_read || true
export LIBVIRT_DEFAULT_URI="${SC_LIBVIRT_URI:-${LIBVIRT_DEFAULT_URI:-qemu:///system}}"
# Non-interactive flags
for arg in "$@"; do
case "$arg" in
--list)
save_list
exit 0
;;
esac
done
if [[ "${1:-}" == "--reset" ]]; then
slot="${2:-}"
if [ -n "$slot" ]; then
save_reset "$slot"
else
save_reset
fi
exit 0
fi
# ---------------------------------------------------------------------------
# Interactive menu
# ---------------------------------------------------------------------------
declare -A VM_LABEL=(
[sc-workstation]="workstation (ares)"
[sc-web-server]="web server (hermes)"
[sc-build-machine]="build server (vulcan)"
)
ALL_VMS=(sc-workstation sc-web-server sc-build-machine)
sc_header "SYSADMIN CHRONICLES — SAVE MANAGEMENT"
_main_menu() {
while true; do
echo ""
save_list
echo ""
echo " ── Save Actions ─────────────────────────────"
echo " s) Switch active save slot"
echo " n) New save slot"
echo " r) Reset a save slot"
echo " e) Export save to file"
echo " i) Import save from file"
echo ""
echo " ── VM Snapshots ─────────────────────────────"
echo " v) View and manage VM snapshots"
echo ""
echo " q) Quit"
echo ""
printf " > " >/dev/tty
read -r choice </dev/tty
echo ""
case "$choice" in
s|S) _switch_slot ;;
n|N) _new_slot ;;
r|R) _reset_slot ;;
e|E) _export_slot ;;
i|I) _import_slot ;;
v|V) _vm_snapshots ;;
q|Q) break ;;
*) sc_warn "Unknown choice." ;;
esac
done
}
_switch_slot() {
echo " Switch to which slot? (autosave / slot-1 / slot-2 / slot-3)"
printf " > " >/dev/tty
read -r slot </dev/tty
echo ""
save_switch "$slot" || true
# Warn if VM state may not match
local active_day
active_day="$(grep -o '"day":[0-9]*' "$SC_SAVE_DIR/${slot}.json" 2>/dev/null | head -1 | cut -d: -f2 || echo '')"
if [ -n "$active_day" ] && [ "$active_day" -gt 1 ] 2>/dev/null; then
echo ""
sc_warn "This save is on Day $active_day. Your VMs may not match this slot's expected state."
sc_info "If things look wrong, use 'tools/vm/rebuild-vms.sh' to revert VMs."
fi
}
_new_slot() {
echo " Create in which slot? (slot-1 / slot-2 / slot-3)"
printf " > " >/dev/tty
read -r slot </dev/tty
echo ""
save_new "$slot" || true
}
_reset_slot() {
echo " Reset which slot? (autosave / slot-1 / slot-2 / slot-3)"
printf " > " >/dev/tty
read -r slot </dev/tty
echo ""
if sc_confirm " Reset $slot to new game state? This cannot be undone." "N"; then
save_reset "$slot"
fi
}
_export_slot() {
echo " Export which slot? (autosave / slot-1 / slot-2 / slot-3)"
printf " > " >/dev/tty
read -r slot </dev/tty
echo ""
echo " Save to (full path, e.g. ~/Desktop/my-save.json):"
printf " > " >/dev/tty
read -r dest </dev/tty
dest="${dest/#\~/$HOME}"
echo ""
save_export "$slot" "$dest" || true
}
_import_slot() {
echo " Path to save file to import:"
printf " > " >/dev/tty
read -r src </dev/tty
src="${src/#\~/$HOME}"
echo ""
echo " Import into which slot? (autosave / slot-1 / slot-2 / slot-3)"
printf " > " >/dev/tty
read -r slot </dev/tty
echo ""
save_import "$src" "$slot" || true
}
_vm_snapshots() {
while true; do
echo ""
echo " VM Snapshots"
echo ""
local vm label snaps snap ts prot
for vm in "${ALL_VMS[@]}"; do
label="${VM_LABEL[$vm]:-$vm}"
echo " $label"
if domain_exists "$vm" 2>/dev/null; then
snaps="$(virsh snapshot-list "$vm" --name 2>/dev/null | grep -v '^$' || true)"
if [ -n "$snaps" ]; then
while IFS= read -r snap; do
ts="$(virsh snapshot-info "$vm" "$snap" 2>/dev/null | grep 'Creation Time' | awk '{print $3}' || true)"
if [[ "$snap" == baseline.* ]] || [[ "$snap" == checkpoint.* ]]; then
prot=" [protected]"
else
prot=""
fi
printf " %-36s %-12s%s\n" "$snap" "$ts" "$prot"
done <<< "$snaps"
else
echo " (no snapshots)"
fi
else
echo " (VM not found)"
fi
echo ""
done
echo " Actions: [t]ake snapshot [r]evert [d]elete [q]uit"
echo ""
printf " > " >/dev/tty
local snap_action
read -r snap_action </dev/tty
echo ""
case "$snap_action" in
t|T)
_pick_vm vm_id " Snapshot which VM? (workstation / web-server / build-machine)"
echo " Snapshot name (letters, numbers, hyphens):"
printf " > " >/dev/tty
read -r snap_name </dev/tty
echo ""
vm_snapshot_create "sc-${vm_id}" "$snap_name" \
&& sc_ok "Snapshot taken: $snap_name on $vm_id" \
|| sc_warn "Snapshot failed."
;;
r|R)
_pick_vm vm_id " Revert which VM?"
echo " Snapshot name:"
printf " > " >/dev/tty
read -r snap_name </dev/tty
echo ""
if sc_confirm " Revert $vm_id to '$snap_name'?" "N"; then
vm_snapshot_revert "sc-${vm_id}" "$snap_name" \
&& sc_ok "Reverted to $snap_name" \
|| sc_warn "Revert failed."
fi
;;
d|D)
_pick_vm vm_id " Delete snapshot from which VM?"
echo " Snapshot name:"
printf " > " >/dev/tty
read -r snap_name </dev/tty
echo ""
vm_snapshot_delete "sc-${vm_id}" "$snap_name" \
&& sc_ok "Deleted $snap_name from $vm_id" \
|| true
;;
q|Q) break ;;
*) sc_warn "Unknown action." ;;
esac
done
}
_pick_vm() {
local -n _result_ref="$1"
local _prompt="$2"
echo " $_prompt (workstation / web-server / build-machine)"
printf " > " >/dev/tty
read -r _result_ref </dev/tty
echo ""
}
_main_menu