#!/usr/bin/env bash set -euo pipefail URI="qemu:///system" IMGROOT="/var/lib/libvirt/images" BASEDIR="$IMGROOT/base" LABDIR="$IMGROOT/lab" TEMPLATE_DOMAIN="debian13-template" TEMPLATE_BASE="$BASEDIR/debian13-template.qcow2" usage() { echo "Usage: kvm-lab-create [--base template|work01|/abs/path.qcow2] [--start]" echo "Examples:" echo " kvm-lab-create lab10 --base template --start" echo " kvm-lab-create lab11 --base work01 --start" echo " kvm-lab-create lab12 --base /var/lib/libvirt/images/base/custom.qcow2 --start" } # --- Parse args --- NAME="${1:-}" shift || true BASESEL="template" START="no" while [[ $# -gt 0 ]]; do case "$1" in --base) BASESEL="${2:-}" shift 2 ;; --start) START="yes" shift ;; -h|--help) usage exit 0 ;; *) echo "ERROR: Unknown argument: $1" usage exit 1 ;; esac done if [[ -z "$NAME" ]]; then usage exit 1 fi DISK="${LABDIR}/${NAME}.qcow2" XML_TMP="$(mktemp "/tmp/${NAME}.xml.XXXXXX")" cleanup() { rm -f "$XML_TMP"; } trap cleanup EXIT # --- Resolve base selection --- resolve_base() { local sel="$1" if [[ "$sel" == "template" ]]; then echo "$TEMPLATE_BASE" return 0 fi if [[ "$sel" == "work01" ]]; then # Pick newest promoted work01 base (must exist first) local latest latest="$(ls -1t "$BASEDIR"/work01-base-*.qcow2 2>/dev/null | head -n 1 || true)" if [[ -z "$latest" ]]; then echo "ERROR: No work01 base found in $BASEDIR (expected work01-base-*.qcow2)." echo "Action: run your promote script to create one." return 1 fi echo "$latest" return 0 fi # Absolute path base if [[ "$sel" == /* ]]; then echo "$sel" return 0 fi echo "ERROR: Invalid --base '$sel' (use template|work01|/abs/path.qcow2)" return 1 } BASE="$(resolve_base "$BASESEL")" # --- Preconditions (read-only checks) --- if [[ ! -d "$LABDIR" ]]; then echo "ERROR: Lab directory missing: $LABDIR" exit 1 fi if ! sudo test -r "$BASE"; then echo "ERROR: Base image not readable (via sudo): $BASE" exit 1 fi if [[ -e "$DISK" ]]; then echo "ERROR: Disk already exists: $DISK" exit 1 fi if virsh -c "$URI" dominfo "$NAME" &>/dev/null; then echo "ERROR: Domain already exists: $NAME" exit 1 fi if ! virsh -c "$URI" dominfo "$TEMPLATE_DOMAIN" &>/dev/null; then echo "ERROR: Template domain not found in $URI: $TEMPLATE_DOMAIN" exit 1 fi # --- Create overlay disk --- echo "Using base: $BASE" echo "Creating overlay disk: $DISK" sudo qemu-img create -f qcow2 -F qcow2 -b "$BASE" "$DISK" >/dev/null sudo chown libvirt-qemu:libvirt "$DISK" sudo chmod 0660 "$DISK" echo "Validating backing file..." sudo qemu-img info "$DISK" | grep -E "file format:|virtual size:|backing file:" || true # --- Clone domain XML --- echo "Cloning domain XML from: $TEMPLATE_DOMAIN" virsh -c "$URI" dumpxml "$TEMPLATE_DOMAIN" > "$XML_TMP" # Set name, disk path, and force NAT-only network "default" sed -i \ -e "s|${TEMPLATE_DOMAIN}|${NAME}|" \ -e "s|${TEMPLATE_BASE}|${DISK}|g" \ -e "s|[^<]*<\/uuid>/d' "$XML_TMP" # Remove any host filesystem passthrough (if present) perl -0777 -i -pe 's/\n?//g' "$XML_TMP" # --- Define domain --- echo "Defining domain: $NAME" virsh -c "$URI" define "$XML_TMP" >/dev/null # --- Optional start --- if [[ "$START" == "yes" ]]; then echo "Starting domain: $NAME" virsh -c "$URI" start "$NAME" >/dev/null fi echo "OK: Lab '$NAME' created (disk: $DISK)." if [[ "$START" != "yes" ]]; then echo "To start: virsh -c $URI start $NAME" fi