#!/usr/bin/env bash set -euo pipefail SSH_PORT=2222 MEMORY=8G CPUS=4 MOUNTS=() CLAUDE=false IMAGE="" usage() { cat < [options] Options: --mount Mount host directory into VM (repeatable) --claude Mount claude config dir (auto-detects from CLAUDE_CONFIG_DIR or ~/.claude) --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 --mount) MOUNTS+=("$2"); shift 2 ;; --claude) CLAUDE=true; shift ;; --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 ACCEL="tcg" [ -r /dev/kvm ] && ACCEL="kvm" QEMU_ARGS=( qemu-system-x86_64 -accel "$ACCEL" -m "$MEMORY" -smp "$CPUS" -drive "file=$IMAGE,format=qcow2,snapshot=on" -nic "user,hostfwd=tcp::${SSH_PORT}-:22" -nographic ) if [ "$ACCEL" != "tcg" ]; then QEMU_ARGS+=(-cpu host) fi FS_ID=0 for mount_path in "${MOUNTS[@]}"; do mount_path=$(realpath "$mount_path") name=$(basename "$mount_path") QEMU_ARGS+=( -virtfs "local,path=$mount_path,mount_tag=mount_$name,security_model=none,id=fs${FS_ID}" ) FS_ID=$((FS_ID + 1)) done if [ "$CLAUDE" = true ]; then if [ -z "${CLAUDE_CONFIG_DIR:-}" ]; then echo "error: --claude requires CLAUDE_CONFIG_DIR to be set" exit 1 fi mkdir -p "$CLAUDE_CONFIG_DIR" claude_dir=$(realpath "$CLAUDE_CONFIG_DIR") QEMU_ARGS+=( -virtfs "local,path=$claude_dir,mount_tag=claude,security_model=none,id=fs${FS_ID}" ) fi exec "${QEMU_ARGS[@]}"