#!/bin/bash # run-vm.sh — Launch QEMU VM for testing (reusable by other test scripts) # Usage: ./test/qemu/run-vm.sh [options] # # Options: # --arch Architecture: x86_64 (default) or arm64 # --data-disk Use existing data disk (default: create temp) # --data-size Size of temp data disk (default: 1024) # --memory VM memory (default: 2048) # --cpus VM CPUs (default: 2) # --serial-log Write serial output to file # --api-port Forward K8s API to host port (default: 6443) # --ssh-port Forward SSH to host port (default: 2222) # --background Run in background, print PID # --append Extra kernel append args # --kernel Kernel image (required for arm64) # --initrd Initramfs image (required for arm64) # # Outputs (on stdout): # QEMU_PID= # DATA_DISK= # SERIAL_LOG= set -euo pipefail IMAGE="${1:?Usage: $0 [options]}" shift # Defaults ARCH="x86_64" DATA_DISK="" DATA_SIZE_MB=1024 MEMORY=2048 CPUS=2 SERIAL_LOG="" API_PORT=6443 SSH_PORT=2222 BACKGROUND=0 EXTRA_APPEND="" CREATED_DATA_DISK="" VM_KERNEL="" VM_INITRD="" # Parse options while [ $# -gt 0 ]; do case "$1" in --arch) ARCH="$2"; shift 2 ;; --data-disk) DATA_DISK="$2"; shift 2 ;; --data-size) DATA_SIZE_MB="$2"; shift 2 ;; --memory) MEMORY="$2"; shift 2 ;; --cpus) CPUS="$2"; shift 2 ;; --serial-log) SERIAL_LOG="$2"; shift 2 ;; --api-port) API_PORT="$2"; shift 2 ;; --ssh-port) SSH_PORT="$2"; shift 2 ;; --background) BACKGROUND=1; shift ;; --append) EXTRA_APPEND="$2"; shift 2 ;; --kernel) VM_KERNEL="$2"; shift 2 ;; --initrd) VM_INITRD="$2"; shift 2 ;; *) echo "Unknown option: $1" >&2; exit 1 ;; esac done # Create data disk if not provided if [ -z "$DATA_DISK" ]; then DATA_DISK=$(mktemp /tmp/kubesolo-data-XXXXXX.img) CREATED_DATA_DISK="$DATA_DISK" dd if=/dev/zero of="$DATA_DISK" bs=1M count="$DATA_SIZE_MB" 2>/dev/null mkfs.ext4 -q -L KSOLODATA "$DATA_DISK" 2>/dev/null fi # Create serial log if not provided if [ -z "$SERIAL_LOG" ]; then SERIAL_LOG=$(mktemp /tmp/kubesolo-serial-XXXXXX.log) fi # Build QEMU command based on architecture if [ "$ARCH" = "arm64" ] || [ "$ARCH" = "aarch64" ]; then # ARM64: qemu-system-aarch64 with -machine virt # No KVM for cross-arch emulation (TCG only) CONSOLE="ttyAMA0" # ARM64 requires explicit kernel + initrd (no -cdrom support with -machine virt) if [ -z "$VM_KERNEL" ] || [ -z "$VM_INITRD" ]; then echo "ERROR: ARM64 mode requires --kernel and --initrd options" >&2 exit 1 fi QEMU_CMD=( qemu-system-aarch64 -machine virt -cpu cortex-a72 -m "$MEMORY" -smp "$CPUS" -nographic -net "nic,model=virtio" -net "user,hostfwd=tcp::${API_PORT}-:6443,hostfwd=tcp::${SSH_PORT}-:22" -drive "file=$DATA_DISK,format=raw,if=virtio" -serial "file:$SERIAL_LOG" -kernel "$VM_KERNEL" -initrd "$VM_INITRD" -append "console=${CONSOLE} kubesolo.data=/dev/vda kubesolo.debug $EXTRA_APPEND" ) else # x86_64: standard QEMU CONSOLE="ttyS0,115200n8" # Detect KVM availability KVM_FLAG="" if [ -w /dev/kvm ] 2>/dev/null; then KVM_FLAG="-enable-kvm" fi QEMU_CMD=( qemu-system-x86_64 -m "$MEMORY" -smp "$CPUS" -nographic -net "nic,model=virtio" -net "user,hostfwd=tcp::${API_PORT}-:6443,hostfwd=tcp::${SSH_PORT}-:22" -drive "file=$DATA_DISK,format=raw,if=virtio" -serial "file:$SERIAL_LOG" ) [ -n "$KVM_FLAG" ] && QEMU_CMD+=("$KVM_FLAG") case "$IMAGE" in *.iso) QEMU_CMD+=( -cdrom "$IMAGE" -boot d -append "console=${CONSOLE} kubesolo.data=/dev/vda kubesolo.debug $EXTRA_APPEND" ) ;; *.img) QEMU_CMD+=( -drive "file=$IMAGE,format=raw,if=virtio" ) ;; *) echo "ERROR: Unrecognized image format: $IMAGE" >&2 exit 1 ;; esac fi # Launch "${QEMU_CMD[@]}" & QEMU_PID=$! # Output metadata echo "QEMU_PID=$QEMU_PID" echo "DATA_DISK=$DATA_DISK" echo "SERIAL_LOG=$SERIAL_LOG" echo "CREATED_DATA_DISK=$CREATED_DATA_DISK" if [ "$BACKGROUND" = "0" ]; then # Foreground mode — wait for QEMU to exit wait "$QEMU_PID" || true # Clean up temp data disk [ -n "$CREATED_DATA_DISK" ] && rm -f "$CREATED_DATA_DISK" fi