diff --git a/README.md b/README.md index 3f22210..cb2e1f3 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,11 @@ After a host reboot (VMs already exist, just need to come back up): ./start-lab.sh # re-arms libvirt/network/NAT state and starts the 3 VMs ``` +To pause the lab without destroying it (e.g. to free RAM/CPU): +```bash +./stop-lab.sh # gracefully shuts down all 3 VMs; ./start-lab.sh brings them back +``` + ## Access - Web UI: and - Login: `root` / value of `ROOT_PASSWORD` in `lab.env` diff --git a/stop-lab.sh b/stop-lab.sh new file mode 100755 index 0000000..6115942 --- /dev/null +++ b/stop-lab.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Gracefully shut down the lab's VMs without destroying anything — libvirt +# domains, the network, and storage pools are left defined so ./start-lab.sh +# can bring the lab back up later. Use ./teardown.sh instead to remove it all. +# +# Shuts down the PVE nodes first, then the storage VM last, so nothing is +# still relying on its NFS/iSCSI when it disappears (mirrors the storage-first +# order start-lab.sh uses on the way up). +set -uo pipefail +source "$(dirname "$0")/lab.env" + +sudo -v # prompt for the sudo password once, up front + +request_shutdown() { + local name="$1" state + state=$(sudo virsh domstate "$name" 2>/dev/null || echo "missing") + case "$state" in + "shut off") echo " $name already shut off" ;; + missing) echo " $name is not defined in libvirt; skipping" ;; + *) echo " sending ACPI shutdown to $name (state: $state)"; sudo virsh shutdown "$name" >/dev/null 2>&1 ;; + esac +} + +wait_shutoff() { + local name="$1" tries=0 + while true; do + local state + state=$(sudo virsh domstate "$name" 2>/dev/null || echo "missing") + [[ "$state" == "shut off" || "$state" == "missing" ]] && { echo " $name is down"; return 0; } + sleep 5 + tries=$((tries+1)) + if (( tries % 6 == 0 )); then echo " still waiting for $name to power off... (${tries}0s)"; fi + if (( tries > 36 )); then return 1; fi # ~3 minutes + done +} + +force_off_if_needed() { + local name="$1" + if ! wait_shutoff "$name"; then + echo " WARN: $name didn't shut down cleanly after 3m; forcing off" + sudo virsh destroy "$name" >/dev/null 2>&1 || true + fi +} + +echo "==> Shutting down PVE nodes" +request_shutdown "$PVE1_NAME" +request_shutdown "$PVE2_NAME" +echo "==> Waiting for PVE nodes to power off" +force_off_if_needed "$PVE1_NAME" +force_off_if_needed "$PVE2_NAME" + +echo "==> Shutting down storage VM (after the PVE nodes, so nothing still depends on its NFS/iSCSI)" +request_shutdown "$STORE_NAME" +echo "==> Waiting for $STORE_NAME to power off" +force_off_if_needed "$STORE_NAME" + +echo +echo "Lab stopped. VMs, network, and storage pools are still defined (nothing was destroyed)." +echo "Run ./start-lab.sh to bring it back up."