Add QEMU VM helper script for development environment

Introduces bin/yino-vm with create, boot, delete, and status subcommands
for managing a QEMU x86_64 VM on macOS. Auto-detects OVMF firmware paths
and CPU/accelerator (HVF on Intel, TCG on Apple Silicon). VM artifacts
are stored in vm/ (gitignored). Completes TODO #1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Smith 2026-02-16 00:30:31 +01:00
parent 60b0bb3037
commit 4b30682e17
4 changed files with 251 additions and 1 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/download
/vm
.claude

View File

@ -9,7 +9,7 @@
## TODO
- [ ] Set up development environment (QEMU VM for testing)
- [x] Set up development environment (QEMU VM for testing)
- [ ] Create ISO generation pipeline (Debian ISO with unattended install that triggers Yino installer)
- [ ] Configure boot and full disk encryption (LUKS, initramfs, bootloader) as part of unattended install
- [ ] Port Omarchy installer to Debian (runs post-install, equivalent of Omarchy's phase 2-5 scripts)

249
bin/yino-vm Executable file
View File

@ -0,0 +1,249 @@
#!/usr/bin/env bash
set -euo pipefail
VM_DIR="$(cd "$(dirname "$0")/.." && pwd)/vm"
DISK_IMAGE="$VM_DIR/yino-vm.qcow2"
UEFI_VARS="$VM_DIR/yino-vm-vars.fd"
# --- OVMF firmware discovery ---
find_ovmf_code() {
local paths=(
/opt/homebrew/share/qemu/edk2-x86_64-code.fd
/usr/local/share/qemu/edk2-x86_64-code.fd
)
for p in "${paths[@]}"; do
[[ -f "$p" ]] && echo "$p" && return
done
# Cellar fallbacks
local cellar_dirs=(/opt/homebrew/Cellar/qemu /usr/local/Cellar/qemu)
for d in "${cellar_dirs[@]}"; do
if [[ -d "$d" ]]; then
local found
found=$(find "$d" -name 'edk2-x86_64-code.fd' -print -quit 2>/dev/null)
[[ -n "$found" ]] && echo "$found" && return
fi
done
return 1
}
find_ovmf_vars_template() {
local paths=(
/opt/homebrew/share/qemu/edk2-i386-vars.fd
/usr/local/share/qemu/edk2-i386-vars.fd
)
for p in "${paths[@]}"; do
[[ -f "$p" ]] && echo "$p" && return
done
local cellar_dirs=(/opt/homebrew/Cellar/qemu /usr/local/Cellar/qemu)
for d in "${cellar_dirs[@]}"; do
if [[ -d "$d" ]]; then
local found
found=$(find "$d" -name 'edk2-i386-vars.fd' -print -quit 2>/dev/null)
[[ -n "$found" ]] && echo "$found" && return
fi
done
return 1
}
# --- CPU/accelerator auto-detection ---
detect_accel() {
local arch
arch=$(uname -m)
if [[ "$arch" == "arm64" ]]; then
echo "tcg"
else
echo "hvf"
fi
}
detect_cpu() {
local arch
arch=$(uname -m)
if [[ "$arch" == "arm64" ]]; then
echo "qemu64"
else
echo "host"
fi
}
# --- Subcommands ---
cmd_create() {
if ! command -v qemu-system-x86_64 &>/dev/null; then
echo "Error: qemu-system-x86_64 not found." >&2
echo "Install it with: brew install qemu" >&2
exit 1
fi
if [[ -f "$DISK_IMAGE" ]]; then
echo "Error: Disk image already exists: $DISK_IMAGE" >&2
echo "Run 'yino-vm delete' first to remove it." >&2
exit 1
fi
local vars_template
if ! vars_template=$(find_ovmf_vars_template); then
echo "Error: OVMF vars template (edk2-i386-vars.fd) not found." >&2
echo "Install it with: brew install qemu" >&2
exit 1
fi
echo "Creating 30G sparse disk image..."
qemu-img create -f qcow2 "$DISK_IMAGE" 30G
echo "Copying UEFI vars template..."
cp "$vars_template" "$UEFI_VARS"
echo "VM created:"
echo " Disk: $DISK_IMAGE"
echo " UEFI vars: $UEFI_VARS"
}
cmd_boot() {
if ! command -v qemu-system-x86_64 &>/dev/null; then
echo "Error: qemu-system-x86_64 not found." >&2
echo "Install it with: brew install qemu" >&2
exit 1
fi
if [[ ! -f "$DISK_IMAGE" ]]; then
echo "Error: Disk image not found: $DISK_IMAGE" >&2
echo "Run 'yino-vm create' first." >&2
exit 1
fi
local ovmf_code
if ! ovmf_code=$(find_ovmf_code); then
echo "Error: OVMF firmware (edk2-x86_64-code.fd) not found." >&2
echo "Install it with: brew install qemu" >&2
exit 1
fi
if [[ ! -f "$UEFI_VARS" ]]; then
echo "Error: UEFI vars not found: $UEFI_VARS" >&2
echo "Run 'yino-vm create' first." >&2
exit 1
fi
local accel cpu
accel=$(detect_accel)
cpu=$(detect_cpu)
if [[ "$accel" == "tcg" ]]; then
echo "Warning: Running x86_64 VM under TCG emulation (Apple Silicon host)."
echo "This works but is significantly slower than hardware virtualization."
echo ""
fi
local iso_args=()
while [[ $# -gt 0 ]]; do
case "$1" in
--iso)
if [[ -z "${2:-}" ]]; then
echo "Error: --iso requires a path argument." >&2
exit 1
fi
if [[ ! -f "$2" ]]; then
echo "Error: ISO file not found: $2" >&2
exit 1
fi
iso_args=(-drive "file=$2,media=cdrom,readonly=on" -boot d)
shift 2
;;
*)
echo "Error: Unknown option: $1" >&2
echo "Usage: yino-vm boot [--iso PATH]" >&2
exit 1
;;
esac
done
echo "Booting VM (accel=$accel, cpu=$cpu)..."
echo "SSH available at: ssh -p 2222 localhost"
echo ""
qemu-system-x86_64 \
-machine q35 \
-accel "$accel" \
-cpu "$cpu" \
-m 4G \
-smp 2 \
-drive "file=$DISK_IMAGE,if=virtio,cache=writeback" \
-drive "if=pflash,format=raw,readonly=on,file=$ovmf_code" \
-drive "if=pflash,format=raw,file=$UEFI_VARS" \
-device virtio-net-pci,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-display cocoa \
-vga virtio \
"${iso_args[@]}"
}
cmd_delete() {
if [[ ! -f "$DISK_IMAGE" && ! -f "$UEFI_VARS" ]]; then
echo "Nothing to delete — no VM files found."
exit 0
fi
echo "This will delete:"
[[ -f "$DISK_IMAGE" ]] && echo " $DISK_IMAGE"
[[ -f "$UEFI_VARS" ]] && echo " $UEFI_VARS"
echo ""
read -rp "Are you sure? [y/N] " confirm
if [[ "$confirm" != [yY] ]]; then
echo "Cancelled."
exit 0
fi
[[ -f "$DISK_IMAGE" ]] && rm "$DISK_IMAGE" && echo "Removed $DISK_IMAGE"
[[ -f "$UEFI_VARS" ]] && rm "$UEFI_VARS" && echo "Removed $UEFI_VARS"
echo "VM deleted."
}
cmd_status() {
echo "VM directory: $VM_DIR"
echo ""
if [[ -f "$DISK_IMAGE" ]]; then
local size
size=$(qemu-img info --output=json "$DISK_IMAGE" 2>/dev/null \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(f\"virtual: {d['virtual-size']/(1<<30):.0f}G, actual: {d['actual-size']/(1<<20):.1f}M\")" 2>/dev/null \
|| echo "present")
echo "Disk image: $size"
else
echo "Disk image: not created"
fi
if [[ -f "$UEFI_VARS" ]]; then
echo "UEFI vars: present"
else
echo "UEFI vars: not created"
fi
}
cmd_help() {
cat <<'EOF'
Usage: yino-vm <command> [options]
Commands:
create Create VM disk image and UEFI vars
boot [--iso P] Boot the VM (--iso to attach installer ISO)
delete Delete VM disk image and UEFI vars
status Show VM status
help Show this help
EOF
}
# --- Main ---
case "${1:-help}" in
create) cmd_create ;;
boot) shift; cmd_boot "$@" ;;
delete) cmd_delete ;;
status) cmd_status ;;
help|--help|-h) cmd_help ;;
*)
echo "Error: Unknown command: $1" >&2
cmd_help >&2
exit 1
;;
esac

0
vm/.keep Normal file
View File