Some checks failed
CI / Go Tests (push) Has been cancelled
CI / Build Go Binaries (amd64, linux, linux-amd64) (push) Has been cancelled
CI / Build Go Binaries (arm64, linux, linux-arm64) (push) Has been cancelled
CI / Shellcheck (push) Has been cancelled
Release / Test (push) Has been cancelled
Release / Build Binaries (amd64, linux, linux-amd64) (push) Has been cancelled
Release / Build Binaries (arm64, linux, linux-arm64) (push) Has been cancelled
Release / Build ISO (amd64) (push) Has been cancelled
Release / Create Release (push) Has been cancelled
- Try bsdtar first (macOS + Linux with libarchive-tools) - Fall back to isoinfo (genisoimage/cdrtools) - Fall back to loop mount (Linux only, requires root) - Platform-aware error messages for e2fsprogs install Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
194 lines
7.2 KiB
Bash
Executable File
194 lines
7.2 KiB
Bash
Executable File
#!/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 <path-to-iso-or-img>"
|
|
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
|