#!/bin/bash # dev-vm.sh — Launch a QEMU VM for development and testing # Usage: ./hack/dev-vm.sh [path-to-iso-or-img] [--shell] [--debug] # # Works on both Linux (with KVM) and macOS (TCG emulation). # On macOS/Apple Silicon, x86_64 guests run under TCG (~5-15x slower than KVM). set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" VERSION="$(cat "$PROJECT_ROOT/VERSION")" ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}" DEFAULT_ISO="$PROJECT_ROOT/output/kubesolo-os-${VERSION}.iso" DEFAULT_IMG="$PROJECT_ROOT/output/kubesolo-os-${VERSION}.img" IMAGE="" EXTRA_APPEND="" # Parse all arguments — flags and optional image path for arg in "$@"; do case "$arg" in --shell) EXTRA_APPEND="$EXTRA_APPEND kubesolo.shell" ;; --debug) EXTRA_APPEND="$EXTRA_APPEND kubesolo.debug" ;; --edge-id=*) EXTRA_APPEND="$EXTRA_APPEND kubesolo.edge_id=${arg#--edge-id=}" ;; --edge-key=*) EXTRA_APPEND="$EXTRA_APPEND kubesolo.edge_key=${arg#--edge-key=}" ;; *) IMAGE="$arg" ;; esac done # Auto-detect image if [ -z "$IMAGE" ]; then if [ -f "$DEFAULT_ISO" ]; then IMAGE="$DEFAULT_ISO" elif [ -f "$DEFAULT_IMG" ]; then IMAGE="$DEFAULT_IMG" else echo "ERROR: No image found. Run 'make iso' or 'make disk-image' first." echo " Or specify path: $0 " exit 1 fi fi echo "==> Launching QEMU with: $IMAGE" echo " Press Ctrl+A, X to exit" echo "" DATA_APPEND="" DATA_DISK="" # Find mkfs.ext4 (Homebrew on macOS installs to a non-PATH location) MKFS_EXT4="" if command -v mkfs.ext4 >/dev/null 2>&1; then MKFS_EXT4="mkfs.ext4" elif [ -x "/opt/homebrew/opt/e2fsprogs/sbin/mkfs.ext4" ]; then MKFS_EXT4="/opt/homebrew/opt/e2fsprogs/sbin/mkfs.ext4" elif [ -x "/usr/local/opt/e2fsprogs/sbin/mkfs.ext4" ]; then MKFS_EXT4="/usr/local/opt/e2fsprogs/sbin/mkfs.ext4" fi # Create and attach a formatted data disk for persistent K8s state. if [ -n "$MKFS_EXT4" ]; then DATA_DISK="$(mktemp /tmp/kubesolo-data-XXXXXX).img" dd if=/dev/zero of="$DATA_DISK" bs=1M count=2048 2>/dev/null "$MKFS_EXT4" -q -L KSOLODATA "$DATA_DISK" 2>/dev/null DATA_APPEND="kubesolo.data=/dev/vda" echo " Data disk: 2 GB ext4 (persistent)" else echo "ERROR: mkfs.ext4 not found. Install e2fsprogs:" if [ "$(uname)" = "Darwin" ]; then echo " brew install e2fsprogs" else echo " apt install e2fsprogs # Debian/Ubuntu" echo " dnf install e2fsprogs # Fedora/RHEL" fi exit 1 fi EXTRACT_DIR="" cleanup() { [ -n "$DATA_DISK" ] && rm -f "$DATA_DISK" "${DATA_DISK%.img}" [ -n "$EXTRACT_DIR" ] && rm -rf "$EXTRACT_DIR" } trap cleanup EXIT # Build QEMU command QEMU_ARGS=(-m 2048 -smp 2 -nographic -cpu max) QEMU_ARGS+=(-net "nic,model=virtio") QEMU_ARGS+=(-net "user,hostfwd=tcp::6443-:6443,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:8080") if [ -n "$DATA_DISK" ]; then QEMU_ARGS+=(-drive "file=$DATA_DISK,format=raw,if=virtio") fi # Enable KVM on Linux, fall back to TCG everywhere else if [ -w /dev/kvm ] 2>/dev/null; then QEMU_ARGS+=(-accel kvm) echo " KVM acceleration: enabled" else QEMU_ARGS+=(-accel tcg) echo " TCG emulation (no KVM — expect slower boot)" fi case "$IMAGE" in *.iso) # -append only works with -kernel, not -cdrom. # Extract kernel + initramfs and use direct kernel boot. VMLINUZ="" INITRAMFS="" # Prefer build artifacts if present (no extraction needed) if [ -f "$ROOTFS_DIR/vmlinuz" ] && [ -f "$ROOTFS_DIR/kubesolo-os.gz" ]; then VMLINUZ="$ROOTFS_DIR/vmlinuz" INITRAMFS="$ROOTFS_DIR/kubesolo-os.gz" echo " Using kernel/initramfs from build directory" else # Extract kernel + initramfs from ISO. # Try multiple methods: bsdtar > isoinfo > loop mount EXTRACT_DIR="$(mktemp -d /tmp/kubesolo-extract-XXXXXX)" EXTRACTED=0 echo " Extracting kernel/initramfs from ISO..." # Method 1: bsdtar (ships with macOS, libarchive-tools on Linux) if [ $EXTRACTED -eq 0 ] && command -v bsdtar >/dev/null 2>&1; then if bsdtar -xf "$IMAGE" -C "$EXTRACT_DIR" boot/vmlinuz boot/kubesolo-os.gz 2>/dev/null; then echo " Extracted via bsdtar" EXTRACTED=1 fi fi # Method 2: isoinfo (genisoimage/cdrtools on Linux) if [ $EXTRACTED -eq 0 ] && command -v isoinfo >/dev/null 2>&1; then mkdir -p "$EXTRACT_DIR/boot" isoinfo -i "$IMAGE" -x "/BOOT/VMLINUZ;1" > "$EXTRACT_DIR/boot/vmlinuz" 2>/dev/null || true isoinfo -i "$IMAGE" -x "/BOOT/KUBESOLO-OS.GZ;1" > "$EXTRACT_DIR/boot/kubesolo-os.gz" 2>/dev/null || true # isoinfo writes empty files on failure; check size if [ -s "$EXTRACT_DIR/boot/vmlinuz" ] && [ -s "$EXTRACT_DIR/boot/kubesolo-os.gz" ]; then echo " Extracted via isoinfo" EXTRACTED=1 else rm -f "$EXTRACT_DIR/boot/vmlinuz" "$EXTRACT_DIR/boot/kubesolo-os.gz" fi fi # Method 3: loop mount (Linux only, requires root) if [ $EXTRACTED -eq 0 ] && [ "$(uname)" = "Linux" ]; then ISO_MOUNT="$EXTRACT_DIR/mnt" mkdir -p "$ISO_MOUNT" if mount -o loop,ro "$IMAGE" "$ISO_MOUNT" 2>/dev/null; then mkdir -p "$EXTRACT_DIR/boot" cp "$ISO_MOUNT/boot/vmlinuz" "$EXTRACT_DIR/boot/" 2>/dev/null || true cp "$ISO_MOUNT/boot/kubesolo-os.gz" "$EXTRACT_DIR/boot/" 2>/dev/null || true umount "$ISO_MOUNT" 2>/dev/null || true if [ -f "$EXTRACT_DIR/boot/vmlinuz" ] && [ -f "$EXTRACT_DIR/boot/kubesolo-os.gz" ]; then echo " Extracted via loop mount" EXTRACTED=1 fi fi fi if [ $EXTRACTED -eq 0 ]; then echo "ERROR: Failed to extract kernel/initramfs from ISO." echo " Install one of: bsdtar (libarchive-tools), isoinfo (genisoimage), or run as root for loop mount." echo " Or run 'make rootfs initramfs' to produce build artifacts." exit 1 fi VMLINUZ="$EXTRACT_DIR/boot/vmlinuz" INITRAMFS="$EXTRACT_DIR/boot/kubesolo-os.gz" if [ ! -f "$VMLINUZ" ] || [ ! -f "$INITRAMFS" ]; then echo "ERROR: ISO does not contain expected boot/vmlinuz and boot/kubesolo-os.gz" exit 1 fi fi qemu-system-x86_64 \ "${QEMU_ARGS[@]}" \ -kernel "$VMLINUZ" \ -initrd "$INITRAMFS" \ -append "console=ttyS0,115200n8 $DATA_APPEND $EXTRA_APPEND" ;; *.img) qemu-system-x86_64 \ "${QEMU_ARGS[@]}" \ -drive "file=$IMAGE,format=raw,if=virtio" ;; *) echo "ERROR: Unrecognized image format: $IMAGE" exit 1 ;; esac