#!/usr/bin/env bash # snapshot-all.sh — Snapshot or revert all game VMs at once. # # Usage: # bash tools/vm/snapshot-all.sh --snapshot Create named snapshot on all VMs # bash tools/vm/snapshot-all.sh --revert-to Revert all VMs to named snapshot # bash tools/vm/snapshot-all.sh --list List all snapshots per VM # bash tools/vm/snapshot-all.sh --dry-run --revert-to ... Dry run (no state changes) # # SAFETY: # - Only operates on sc- prefixed domains. # - Always prints a summary before modifying state. # - --revert-to requires explicit confirmation (skipped with --yes flag). # - This script is for developer use only. It is NOT available in-game. # # AGENT RULES: # - Never run --revert-to without explicit user instruction. # - Never run against domains that don't start with sc-. set -euo pipefail VMS=("sc-workstation" "sc-web-server" "sc-build-machine") DRY_RUN=false ASSUME_YES=false SNAPSHOT_NAME="" REVERT_NAME="" LIST_MODE=false # Parse arguments while [[ $# -gt 0 ]]; do case "$1" in --snapshot) SNAPSHOT_NAME="$2"; shift 2 ;; --revert-to) REVERT_NAME="$2"; shift 2 ;; --list) LIST_MODE=true; shift ;; --dry-run) DRY_RUN=true; shift ;; --yes) ASSUME_YES=true; shift ;; *) echo "Unknown argument: $1"; exit 1 ;; esac done run() { if [ "$DRY_RUN" = "true" ]; then echo " [DRY-RUN] $*" else "$@" fi } guard_prefix() { local dom="$1" if [[ "$dom" != sc-* ]]; then echo "SAFETY: refusing to operate on non-game domain: $dom" exit 1 fi } # --------------------------------------------------------------------------- if [ "$LIST_MODE" = "true" ]; then echo "" echo "── Snapshots per VM ─────────────────────────────" for dom in "${VMS[@]}"; do echo "" echo " $dom:" if virsh dominfo "$dom" &>/dev/null 2>&1; then virsh snapshot-list "$dom" --name 2>/dev/null | sed 's/^/ /' || echo " (none)" else echo " (domain does not exist)" fi done echo "" exit 0 fi # --------------------------------------------------------------------------- # SNAPSHOT # --------------------------------------------------------------------------- if [ -n "$SNAPSHOT_NAME" ]; then echo "" echo "Creating snapshot '$SNAPSHOT_NAME' on all game VMs..." [ "$DRY_RUN" = "true" ] && echo "[DRY-RUN mode]" echo "" for dom in "${VMS[@]}"; do guard_prefix "$dom" if virsh dominfo "$dom" &>/dev/null 2>&1; then echo " Snapshotting $dom..." run virsh snapshot-create-as "$dom" "$SNAPSHOT_NAME" \ --description "Created by snapshot-all.sh" \ --atomic echo " ✓ $dom → $SNAPSHOT_NAME" else echo " ⚠ $dom not found — skipping" fi done echo "" echo "Done." exit 0 fi # --------------------------------------------------------------------------- # REVERT # --------------------------------------------------------------------------- if [ -n "$REVERT_NAME" ]; then echo "" echo "══════════════════════════════════════════════════" echo " REVERT ALL VMs TO: $REVERT_NAME" echo "══════════════════════════════════════════════════" echo " VMs: ${VMS[*]}" echo " This will DISCARD all unsaved VM state." [ "$DRY_RUN" = "true" ] && echo " [DRY-RUN mode — no changes will be made]" echo "" if [ "$ASSUME_YES" = "false" ] && [ "$DRY_RUN" = "false" ]; then read -rp " Type YES to confirm revert: " confirm if [ "$confirm" != "YES" ]; then echo " Aborted." exit 0 fi fi for dom in "${VMS[@]}"; do guard_prefix "$dom" if virsh dominfo "$dom" &>/dev/null 2>&1; then echo " Reverting $dom..." # Stop VM first if running if virsh domstate "$dom" 2>/dev/null | grep -q "running"; then run virsh destroy "$dom" fi run virsh snapshot-revert "$dom" "$REVERT_NAME" --running echo " ✓ $dom → $REVERT_NAME" else echo " ⚠ $dom not found — skipping" fi done echo "" echo "Revert complete." exit 0 fi # No mode selected echo "Usage:" echo " bash snapshot-all.sh --snapshot " echo " bash snapshot-all.sh --revert-to " echo " bash snapshot-all.sh --list" echo " Add --dry-run to preview without changes." exit 1