Tests everything possible without the target hardware: create-vm.sh (runs on Proxmox host): - Creates Arch Linux VM (VMID 900, 8 cores, 16GB RAM, 100GB disk) - UEFI boot with OVMF (for nested QEMU testing) - Cloud-init auto-installs packages, clones repo, runs tests - Nested virtualization enabled for QEMU-in-QEMU boot tests run-in-vm.sh (runs inside the VM, 9 test suites): 1. Host environment validation 2. dpack build + unit tests + CLI smoke tests 3. Package definition validation (154 packages, dep resolution) 4. Script syntax checking (toolchain, init, installer, ISO) 5. Kernel config validation (critical options) 6. Package signing test (download zlib, compute SHA256) 7. Toolchain bootstrap (LFS Ch.5 cross-compiler build) 8. ISO generation 9. Nested QEMU boot test (UEFI boot, kernel + userspace check) Modes: --quick (30min, suites 1-5), --no-build (1hr), full (2-6hr) Generates report.json + report.txt for automated debugging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
210 lines
7.0 KiB
Bash
Executable File
210 lines
7.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# ============================================================================
|
|
# DarkForge — Proxmox VM Creation Script
|
|
# ============================================================================
|
|
# Run this on the Proxmox host to create an Arch Linux test VM.
|
|
#
|
|
# Requirements:
|
|
# - Proxmox VE 8.x or 9.x
|
|
# - ~100GB free on a storage pool
|
|
# - Internet access
|
|
#
|
|
# Usage:
|
|
# bash create-vm.sh # use defaults
|
|
# bash create-vm.sh --vmid 200 # custom VM ID
|
|
# bash create-vm.sh --storage local-lvm # custom storage
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
# --- Configuration (override via environment or flags) -----------------------
|
|
VMID="${VMID:-900}"
|
|
VM_NAME="${VM_NAME:-darkforge-test}"
|
|
STORAGE="${STORAGE:-local-lvm}"
|
|
DISK_SIZE="${DISK_SIZE:-100G}"
|
|
RAM="${RAM:-16384}" # 16GB
|
|
CORES="${CORES:-8}"
|
|
BRIDGE="${BRIDGE:-vmbr0}"
|
|
|
|
# Arch Linux cloud image
|
|
ARCH_IMG_URL="https://geo.mirror.pkgbuild.com/images/latest/Arch-Linux-x86_64-cloudimg.qcow2"
|
|
ARCH_IMG_FILE="/var/lib/vz/template/iso/arch-cloudimg.qcow2"
|
|
|
|
# Git repo to clone inside the VM
|
|
DARKFORGE_REPO="gitea@git.dannyhaslund.dk:danny8632/darkforge.git"
|
|
# Fallback if SSH key isn't available in the VM
|
|
DARKFORGE_REPO_HTTPS="https://git.dannyhaslund.dk/danny8632/darkforge.git"
|
|
|
|
# Parse args
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--vmid=*) VMID="${arg#*=}" ;;
|
|
--storage=*) STORAGE="${arg#*=}" ;;
|
|
--cores=*) CORES="${arg#*=}" ;;
|
|
--ram=*) RAM="${arg#*=}" ;;
|
|
--bridge=*) BRIDGE="${arg#*=}" ;;
|
|
esac
|
|
done
|
|
|
|
echo "═══════════════════════════════════════════════"
|
|
echo " DarkForge Test VM Creator"
|
|
echo " VMID: ${VMID} | Cores: ${CORES} | RAM: ${RAM}MB"
|
|
echo " Storage: ${STORAGE} | Disk: ${DISK_SIZE}"
|
|
echo "═══════════════════════════════════════════════"
|
|
echo ""
|
|
|
|
# --- Check if VM already exists ----------------------------------------------
|
|
if qm status "${VMID}" &>/dev/null; then
|
|
echo "VM ${VMID} already exists."
|
|
read -p "Destroy and recreate? [y/N] " confirm
|
|
if [[ "${confirm}" =~ ^[Yy]$ ]]; then
|
|
qm stop "${VMID}" 2>/dev/null || true
|
|
sleep 3
|
|
qm destroy "${VMID}" --purge
|
|
echo "Old VM destroyed."
|
|
else
|
|
echo "Aborted."
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# --- Download Arch cloud image if not cached ---------------------------------
|
|
if [ ! -f "${ARCH_IMG_FILE}" ]; then
|
|
echo ">>> Downloading Arch Linux cloud image..."
|
|
mkdir -p "$(dirname "${ARCH_IMG_FILE}")"
|
|
wget -q --show-progress -O "${ARCH_IMG_FILE}" "${ARCH_IMG_URL}"
|
|
echo ">>> Downloaded: ${ARCH_IMG_FILE}"
|
|
else
|
|
echo ">>> Using cached image: ${ARCH_IMG_FILE}"
|
|
fi
|
|
|
|
# --- Create the VM -----------------------------------------------------------
|
|
echo ">>> Creating VM ${VMID}..."
|
|
|
|
qm create "${VMID}" \
|
|
--name "${VM_NAME}" \
|
|
--ostype l26 \
|
|
--machine q35 \
|
|
--bios ovmf \
|
|
--cpu host \
|
|
--cores "${CORES}" \
|
|
--memory "${RAM}" \
|
|
--net0 "virtio,bridge=${BRIDGE}" \
|
|
--agent enabled=1 \
|
|
--serial0 socket \
|
|
--vga serial0
|
|
|
|
# Add EFI disk (required for OVMF BIOS)
|
|
qm set "${VMID}" --efidisk0 "${STORAGE}:1,efitype=4m,pre-enrolled-keys=0"
|
|
|
|
# Import the cloud image as the boot disk
|
|
echo ">>> Importing cloud image as boot disk..."
|
|
qm importdisk "${VMID}" "${ARCH_IMG_FILE}" "${STORAGE}" --format qcow2 2>/dev/null
|
|
qm set "${VMID}" --scsi0 "${STORAGE}:vm-${VMID}-disk-1,size=${DISK_SIZE}"
|
|
qm set "${VMID}" --boot order=scsi0
|
|
qm set "${VMID}" --scsihw virtio-scsi-single
|
|
|
|
# --- Configure cloud-init ----------------------------------------------------
|
|
echo ">>> Configuring cloud-init..."
|
|
|
|
qm set "${VMID}" --ide2 "${STORAGE}:cloudinit"
|
|
qm set "${VMID}" --ciuser "darkforge"
|
|
qm set "${VMID}" --cipassword "darkforge"
|
|
qm set "${VMID}" --ipconfig0 "ip=dhcp"
|
|
|
|
# Enable nested virtualization (for QEMU-in-QEMU boot tests)
|
|
qm set "${VMID}" --args "-cpu host,+vmx"
|
|
|
|
# Resize the disk to our desired size
|
|
echo ">>> Resizing disk to ${DISK_SIZE}..."
|
|
qm resize "${VMID}" scsi0 "${DISK_SIZE}" 2>/dev/null || true
|
|
|
|
# --- Generate cloud-init user-data snippet -----------------------------------
|
|
# This runs on first boot inside the VM
|
|
SNIPPET_DIR="/var/lib/vz/snippets"
|
|
mkdir -p "${SNIPPET_DIR}"
|
|
|
|
cat > "${SNIPPET_DIR}/darkforge-test-init.yaml" << 'CLOUDINIT'
|
|
#cloud-config
|
|
package_update: true
|
|
packages:
|
|
- base-devel
|
|
- git
|
|
- wget
|
|
- curl
|
|
- rust
|
|
- cargo
|
|
- qemu-full
|
|
- edk2-ovmf
|
|
- squashfs-tools
|
|
- xorriso
|
|
- dosfstools
|
|
- mtools
|
|
- python
|
|
- bc
|
|
- rsync
|
|
- openssh
|
|
|
|
runcmd:
|
|
# Grow the partition to fill the disk
|
|
- growpart /dev/sda 2 || true
|
|
- resize2fs /dev/sda2 || btrfs filesystem resize max / || true
|
|
|
|
# Clone the DarkForge project
|
|
- |
|
|
su - darkforge -c '
|
|
cd /home/darkforge
|
|
git clone --recurse-submodules https://git.dannyhaslund.dk/danny8632/darkforge.git 2>/dev/null || \
|
|
git clone --recurse-submodules https://github.com/danny8632/darkforge.git 2>/dev/null || \
|
|
echo "CLONE FAILED — manually clone the repo"
|
|
'
|
|
|
|
# Copy the test runner
|
|
- |
|
|
if [ -f /home/darkforge/darkforge/tests/proxmox/run-in-vm.sh ]; then
|
|
cp /home/darkforge/darkforge/tests/proxmox/run-in-vm.sh /home/darkforge/
|
|
chown darkforge:darkforge /home/darkforge/run-in-vm.sh
|
|
chmod +x /home/darkforge/run-in-vm.sh
|
|
fi
|
|
|
|
# Run the test suite automatically
|
|
- |
|
|
su - darkforge -c '
|
|
if [ -f /home/darkforge/run-in-vm.sh ]; then
|
|
bash /home/darkforge/run-in-vm.sh 2>&1 | tee /home/darkforge/test-output.log
|
|
fi
|
|
'
|
|
CLOUDINIT
|
|
|
|
qm set "${VMID}" --cicustom "user=local:snippets/darkforge-test-init.yaml"
|
|
|
|
# --- Start the VM ------------------------------------------------------------
|
|
echo ""
|
|
echo ">>> Starting VM ${VMID}..."
|
|
qm start "${VMID}"
|
|
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════"
|
|
echo " VM ${VMID} created and starting."
|
|
echo ""
|
|
echo " The VM will:"
|
|
echo " 1. Boot Arch Linux"
|
|
echo " 2. Install required packages via cloud-init"
|
|
echo " 3. Clone the DarkForge repository"
|
|
echo " 4. Run the full test suite"
|
|
echo ""
|
|
echo " Monitor progress:"
|
|
echo " qm terminal ${VMID} (serial console)"
|
|
echo ""
|
|
echo " SSH access (after boot):"
|
|
echo " ssh darkforge@\$(qm guest cmd ${VMID} network-get-interfaces | grep -oP '\"ip-address\":\\s*\"\\K[0-9.]+')"
|
|
echo " Password: darkforge"
|
|
echo ""
|
|
echo " Or get IP:"
|
|
echo " qm guest cmd ${VMID} network-get-interfaces"
|
|
echo ""
|
|
echo " Collect report after tests finish:"
|
|
echo " VM_IP=\$(qm guest cmd ${VMID} network-get-interfaces | grep -oP '\"ip-address\":\\s*\"\\K[0-9.]+')"
|
|
echo " scp darkforge@\$VM_IP:/home/darkforge/darkforge/tests/report.* ./"
|
|
echo "═══════════════════════════════════════════════"
|