wip
This commit is contained in:
186
dist/run.sh
vendored
Executable file
186
dist/run.sh
vendored
Executable file
@@ -0,0 +1,186 @@
|
||||
#!/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=""
|
||||
GUEST_ARCH=""
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: run.sh <image.qcow2> [options]
|
||||
|
||||
Options:
|
||||
--arch <arch> Guest architecture (auto-detected from image name)
|
||||
--ssh-key <key.pub> SSH public key (auto-generates seed ISO)
|
||||
--seed-iso <iso> Pre-built seed ISO (alternative to --ssh-key)
|
||||
--projects <path> Mount host directory as ~/projects in VM
|
||||
--claude-dir <path> Mount host .claude directory for auth
|
||||
--memory <size> VM memory (default: 8G)
|
||||
--cpus <n> VM CPUs (default: 4)
|
||||
--ssh-port <port> SSH port forward (default: 2222)
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ "${1:-}" ] || usage
|
||||
|
||||
IMAGE="$1"
|
||||
shift
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--arch) GUEST_ARCH="$2"; shift 2 ;;
|
||||
--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
|
||||
|
||||
# detect guest architecture from image filename if not specified
|
||||
if [ -z "$GUEST_ARCH" ]; then
|
||||
case "$IMAGE" in
|
||||
*aarch64*|*arm64*) GUEST_ARCH="aarch64" ;;
|
||||
*x86_64*|*amd64*) GUEST_ARCH="x86_64" ;;
|
||||
*)
|
||||
# fallback to host architecture
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) GUEST_ARCH="x86_64" ;;
|
||||
aarch64|arm64) GUEST_ARCH="aarch64" ;;
|
||||
*) echo "error: cannot detect guest arch, use --arch"; exit 1 ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# normalize
|
||||
case "$GUEST_ARCH" in
|
||||
x86_64|amd64) GUEST_ARCH="x86_64" ;;
|
||||
aarch64|arm64) GUEST_ARCH="aarch64" ;;
|
||||
*) echo "error: unsupported architecture: $GUEST_ARCH"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# platform detection
|
||||
HOST_ARCH=$(uname -m)
|
||||
OS=$(uname -s)
|
||||
ACCEL="tcg"
|
||||
|
||||
case "$OS" in
|
||||
Linux)
|
||||
[ -r /dev/kvm ] && ACCEL="kvm"
|
||||
;;
|
||||
Darwin)
|
||||
# hvf only works when guest matches host
|
||||
case "$HOST_ARCH" in
|
||||
aarch64|arm64) [ "$GUEST_ARCH" = "aarch64" ] && ACCEL="hvf" ;;
|
||||
x86_64|amd64) [ "$GUEST_ARCH" = "x86_64" ] && ACCEL="hvf" ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$GUEST_ARCH" in
|
||||
x86_64) QEMU_BIN="qemu-system-x86_64" ;;
|
||||
aarch64) QEMU_BIN="qemu-system-aarch64" ;;
|
||||
esac
|
||||
|
||||
# auto-generate seed ISO from SSH key
|
||||
CLEANUP_SEED=""
|
||||
if [ -n "$SSH_KEY" ] && [ -z "$SEED_ISO" ]; then
|
||||
SEED_ISO="$(mktemp -d)/seed.iso"
|
||||
CLEANUP_SEED="$SEED_ISO"
|
||||
bash "$SCRIPT_DIR/make-seed.sh" "$SSH_KEY" "$SEED_ISO"
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
[ -n "$CLEANUP_SEED" ] && rm -rf "$(dirname "$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 guest needs machine type and uefi firmware
|
||||
if [ "$GUEST_ARCH" = "aarch64" ]; then
|
||||
if [ "$ACCEL" = "hvf" ]; then
|
||||
QEMU_ARGS+=(-machine virt -cpu host)
|
||||
else
|
||||
QEMU_ARGS+=(-machine virt -cpu max)
|
||||
fi
|
||||
|
||||
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 — mapped-xattr on linux, mapped-file on macos (no xattr support)
|
||||
case "$OS" in
|
||||
Linux) SECURITY_MODEL="mapped-xattr" ;;
|
||||
*) SECURITY_MODEL="mapped-file" ;;
|
||||
esac
|
||||
|
||||
if [ -n "$PROJECTS" ]; then
|
||||
QEMU_ARGS+=(
|
||||
-virtfs "local,path=$PROJECTS,mount_tag=projects,security_model=$SECURITY_MODEL,id=fs0"
|
||||
)
|
||||
fi
|
||||
|
||||
if [ -n "$CLAUDE_DIR" ]; then
|
||||
QEMU_ARGS+=(
|
||||
-virtfs "local,path=$CLAUDE_DIR,mount_tag=hostclaude,security_model=$SECURITY_MODEL,id=fs1"
|
||||
)
|
||||
# also mount parent home for .claude.json
|
||||
QEMU_ARGS+=(
|
||||
-virtfs "local,path=$(dirname "$CLAUDE_DIR"),mount_tag=hosthome,security_model=$SECURITY_MODEL,id=fs2,readonly=on"
|
||||
)
|
||||
fi
|
||||
|
||||
echo "---"
|
||||
echo "Guest: $GUEST_ARCH | Accel: $ACCEL"
|
||||
echo "SSH: ssh -A -p $SSH_PORT gordaina@localhost"
|
||||
echo "---"
|
||||
|
||||
exec "${QEMU_ARGS[@]}"
|
||||
Reference in New Issue
Block a user