Files
44r0n7 0265afa054 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.
2026-05-02 11:49:07 -04:00

141 lines
4.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# build-vm.sh — Modular VM builder. Sources a profile file that declares VM
# variables and a generate_user_data() function, then runs the common build
# pipeline against it.
#
# Usage:
# ./build-vm.sh <profile> [--dry-run] [--force]
#
# Example:
# ./build-vm.sh profiles/web-server.sh --force
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <profile> [--dry-run] [--force]"
exit 1
fi
PROFILE_ARG="$1"; shift
DRY_RUN=false
FORCE=false
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=true; shift ;;
--force) FORCE=true; shift ;;
*) echo "Unknown argument: $1"; exit 1 ;;
esac
done
source "$PROJECT_ROOT/tools/lib/config.sh"
config_read || true
if [ -n "${SC_IMAGES_DIR:-}" ]; then
SC_IMAGE_ROOT="${SC_IMAGE_ROOT:-$SC_IMAGES_DIR}"
export SC_IMAGE_ROOT
fi
if [ -n "${SC_LIBVIRT_URI:-}" ]; then
LIBVIRT_DEFAULT_URI="${LIBVIRT_DEFAULT_URI:-$SC_LIBVIRT_URI}"
export LIBVIRT_DEFAULT_URI
fi
source "$SCRIPT_DIR/lib/common.sh"
# Resolve profile path: bare name (e.g. "web-server") or explicit path.
if [[ -f "$PROFILE_ARG" ]]; then
PROFILE="$PROFILE_ARG"
elif [[ -f "$SCRIPT_DIR/profiles/${PROFILE_ARG}.sh" ]]; then
PROFILE="$SCRIPT_DIR/profiles/${PROFILE_ARG}.sh"
elif [[ -f "$SCRIPT_DIR/profiles/${PROFILE_ARG}" ]]; then
PROFILE="$SCRIPT_DIR/profiles/${PROFILE_ARG}"
else
echo "Profile not found: $PROFILE_ARG"
echo "Available profiles:"
ls "$SCRIPT_DIR/profiles/"
exit 1
fi
source "$PROFILE"
# Validate required profile variables.
for var in DOMAIN HOSTNAME RAM_MB VCPUS DISK_SIZE GRAPHICS BASE_URL BASE_IMAGE; do
[[ -n "${!var:-}" ]] || { echo "Profile must set $var"; exit 1; }
done
declare -f generate_user_data >/dev/null || { echo "Profile must define generate_user_data()"; exit 1; }
GAME_HOST_IP="${SC_GAME_HOST_IP:-10.42.0.1}"
POOL_DIR="$(pool_path)"
DISK_PATH="$POOL_DIR/${DOMAIN}.qcow2"
SEED_ISO="$SC_SEED_DIR/${DOMAIN}-seed.iso"
PUBKEY="$(<"${SC_SSH_KEY}.pub")"
export DOMAIN HOSTNAME RAM_MB VCPUS DISK_SIZE GRAPHICS BASE_URL BASE_IMAGE
export GAME_HOST_IP POOL_DIR DISK_PATH SEED_ISO PUBKEY
ensure_vm_tooling
echo ""
echo "══════════════════════════════════════════════════"
echo " Building VM: $DOMAIN ($HOSTNAME)"
echo " Profile: $(basename "$PROFILE")"
echo " RAM: ${RAM_MB} MB vCPUs: ${VCPUS} Disk: ${DISK_SIZE}"
echo "══════════════════════════════════════════════════"
if domain_exists "$DOMAIN" && [ "$FORCE" = "false" ]; then
ok "$DOMAIN already exists. Use --force to rebuild it."
exit 0
fi
step "Preparing base image"
download_if_missing "$BASE_URL" "$BASE_IMAGE"
step "Preparing cloud-init seed"
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
generate_user_data > "$tmpdir/user-data"
cat > "$tmpdir/meta-data" <<EOF
instance-id: ${DOMAIN}
local-hostname: ${HOSTNAME}
EOF
create_seed_iso "$tmpdir/user-data" "$tmpdir/meta-data" "$SEED_ISO"
step "Building domain"
destroy_domain "$DOMAIN"
create_backing_disk "$BASE_IMAGE" "$DISK_PATH" "$DISK_SIZE"
build_import_domain "$DOMAIN" "$DISK_PATH" "$SEED_ISO" "$RAM_MB" "$VCPUS" "$GRAPHICS"
step "Waiting for guest networking"
guest_addr=""
if [ "$DRY_RUN" = "false" ]; then
if guest_addr="$(wait_for_agent_ip "$DOMAIN" 300)"; then
ok "$DOMAIN is reachable at $guest_addr"
else
info "Guest IP not available yet. First boot may still be running cloud-init."
fi
fi
if [ "$DRY_RUN" = "false" ] && [ -n "${READY_COMMAND:-}" ]; then
step "Waiting for guest readiness"
if [ -n "$guest_addr" ]; then
if [ -n "${READY_WATCH_TEMPLATE:-}" ]; then
watch_command="${READY_WATCH_TEMPLATE//\{ADDR\}/$guest_addr}"
info "Watch live progress in another terminal:"
info "$watch_command"
fi
fi
if wait_for_guest_command "$DOMAIN" "${READY_TIMEOUT:-600}" "$READY_COMMAND" "${READY_PROGRESS_COMMAND:-}" "${READY_PROGRESS_EVERY_SEC:-30}"; then
ok "$DOMAIN passed readiness check"
detach_seed_iso "$DOMAIN" "$SEED_ISO"
else
fail "$DOMAIN did not pass readiness check within ${READY_TIMEOUT:-600}s"
fi
fi
ok "$DOMAIN build complete"