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:
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user