#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # defaults SSH_PORT=2222 MEMORY=8G CPUS=4 PROJECTS="" CLAUDE_DIR="" SSH_KEY="" SEED_ISO="" IMAGE="" usage() { cat < [options] Options: --ssh-key SSH public key (auto-generates seed ISO) --seed-iso Pre-built seed ISO (alternative to --ssh-key) --projects Mount host directory as ~/projects in VM --claude-dir Mount host .claude directory for auth --memory VM memory (default: 8G) --cpus VM CPUs (default: 4) --ssh-port SSH port forward (default: 2222) EOF exit 1 } [ "${1:-}" ] || usage IMAGE="$1" shift while [ $# -gt 0 ]; do case "$1" in --ssh-key) SSH_KEY="$2"; shift 2 ;; --seed-iso) SEED_ISO="$2"; shift 2 ;; --projects) PROJECTS="$2"; shift 2 ;; --claude-dir) CLAUDE_DIR="$2"; shift 2 ;; --memory) MEMORY="$2"; shift 2 ;; --cpus) CPUS="$2"; shift 2 ;; --ssh-port) SSH_PORT="$2"; shift 2 ;; *) echo "unknown option: $1"; usage ;; esac done if [ ! -f "$IMAGE" ]; then echo "error: image not found: $IMAGE" exit 1 fi # platform and accelerator detection ACCEL="tcg" ARCH=$(uname -m) OS=$(uname -s) case "$OS" in Linux) [ -r /dev/kvm ] && ACCEL="kvm" ;; Darwin) ACCEL="hvf" ;; esac case "$ARCH" in x86_64|amd64) QEMU_BIN="qemu-system-x86_64" ;; aarch64|arm64) QEMU_BIN="qemu-system-aarch64" ;; *) echo "error: unsupported architecture: $ARCH"; exit 1 ;; esac # auto-generate seed ISO from SSH key CLEANUP_SEED="" if [ -n "$SSH_KEY" ] && [ -z "$SEED_ISO" ]; then SEED_ISO=$(mktemp /tmp/seed-XXXXXX.iso) CLEANUP_SEED="$SEED_ISO" bash "$SCRIPT_DIR/make-seed.sh" "$SSH_KEY" "$SEED_ISO" fi cleanup() { [ -n "$CLEANUP_SEED" ] && rm -f "$CLEANUP_SEED" } trap cleanup EXIT # build qemu command QEMU_ARGS=( "$QEMU_BIN" -accel "$ACCEL" -m "$MEMORY" -smp "$CPUS" -drive "file=$IMAGE,format=qcow2,snapshot=on" -nic "user,hostfwd=tcp::${SSH_PORT}-:22" -nographic ) # aarch64-specific flags if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then QEMU_ARGS+=(-machine virt -cpu host) # uefi firmware EFI_CODE="" for p in \ /opt/homebrew/share/qemu/edk2-aarch64-code.fd \ /usr/local/share/qemu/edk2-aarch64-code.fd \ /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \ /usr/share/AAVMF/AAVMF_CODE.fd; do [ -f "$p" ] && EFI_CODE="$p" && break done if [ -z "$EFI_CODE" ]; then echo "error: aarch64 EFI firmware not found" echo " macos: brew install qemu" echo " linux: apt install qemu-efi-aarch64" exit 1 fi QEMU_ARGS+=(-bios "$EFI_CODE") fi # seed ISO if [ -n "$SEED_ISO" ]; then QEMU_ARGS+=(-drive "file=$SEED_ISO,format=raw,media=cdrom,readonly=on") fi # 9p mounts if [ -n "$PROJECTS" ]; then QEMU_ARGS+=( -virtfs "local,path=$PROJECTS,mount_tag=projects,security_model=mapped-xattr,id=fs0" ) fi if [ -n "$CLAUDE_DIR" ]; then QEMU_ARGS+=( -virtfs "local,path=$CLAUDE_DIR,mount_tag=hostclaude,security_model=mapped-xattr,id=fs1" ) # also mount parent home for .claude.json QEMU_ARGS+=( -virtfs "local,path=$(dirname "$CLAUDE_DIR"),mount_tag=hosthome,security_model=mapped-xattr,id=fs2,readonly=on" ) fi echo "---" echo "SSH: ssh -A -p $SSH_PORT gordaina@localhost" echo "Password: sandbox" echo "---" exec "${QEMU_ARGS[@]}"