From 4b9f311a6533f17c4888ed733d20d110dfe56163 Mon Sep 17 00:00:00 2001 From: Sarge Date: Mon, 18 May 2026 11:57:00 +1000 Subject: [PATCH 1/2] Add QEMU VM helper scripts Co-Authored-By: Oz --- README.md | 130 +++++++++++++++++++++++++++++++++++++ start-android-emulator.ps1 | 89 +++++++++++++++++++++++++ start-qemu-vm.ps1 | 69 ++++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 start-android-emulator.ps1 create mode 100644 start-qemu-vm.ps1 diff --git a/README.md b/README.md index 45cb7e09b5..d06e14f9b5 100644 --- a/README.md +++ b/README.md @@ -110,3 +110,133 @@ We'd like to call out a few of the [open source dependencies](https://docs.warp. - [FontKit](https://github.com/servo/font-kit) - [Core-foundation](https://github.com/servo/core-foundation-rs) - [Smol](https://github.com/smol-rs/smol) + +## QEMU VM Helper Scripts + +This repository includes two local PowerShell scripts for creating and starting simple QEMU virtual machines on Windows: + +- `start-qemu-vm.ps1` creates and starts a general-purpose QEMU VM. +- `start-android-emulator.ps1` creates and starts an Android-x86 QEMU VM. + +### Requirements + +- Windows PowerShell. +- QEMU installed and available on `PATH`. +- `qemu-img` available on `PATH`. +- `qemu-system-x86_64` available on `PATH`. +- Optional but recommended: Windows Hypervisor Platform enabled for WHPX acceleration. +- An installer ISO for installation workflows. + +To verify the QEMU commands are available: + +```powershell +qemu-img --version +qemu-system-x86_64 --version +``` + +If either command is not found, restart the shell so it picks up the persisted user `PATH`. + +### General VM Script + +Use `start-qemu-vm.ps1` for a standard QEMU VM backed by a QCOW2 disk. + +Create a disk and boot an installer ISO: + +```powershell +.\start-qemu-vm.ps1 -Name test-vm -IsoPath .\installer.iso -Install +``` + +Start the installed VM later: + +```powershell +.\start-qemu-vm.ps1 -Name test-vm +``` + +Customize CPU, memory, and disk size: + +```powershell +.\start-qemu-vm.ps1 -Name test-vm -MemoryMB 8192 -CpuCount 4 -DiskSizeGB 60 +``` + +Disable WHPX acceleration if it causes startup issues: + +```powershell +.\start-qemu-vm.ps1 -Name test-vm -NoAccel +``` + +Defaults: + +- VM name: `basic-vm` +- Disk size: `30` GB +- Memory: `4096` MB +- CPU count: `2` +- VM storage root: `vms` +- Disk path pattern: `vms\\.qcow2` + +### Android Emulator Script + +Use `start-android-emulator.ps1` with an Android-x86 ISO. It uses Android-friendly defaults, including USB tablet input and user-mode networking. + +Create a disk and boot the Android installer: + +```powershell +.\start-android-emulator.ps1 -IsoPath .\android-x86.iso -Install +``` + +Start the installed Android VM later: + +```powershell +.\start-android-emulator.ps1 +``` + +Boot an Android live session without creating or using a disk: + +```powershell +.\start-android-emulator.ps1 -IsoPath .\android-x86.iso -Live +``` + +Customize resources: + +```powershell +.\start-android-emulator.ps1 -Name android-test -MemoryMB 8192 -CpuCount 4 -DiskSizeGB 32 +``` + +Disable WHPX acceleration if needed: + +```powershell +.\start-android-emulator.ps1 -IsoPath .\android-x86.iso -Install -NoAccel +``` + +Defaults: + +- VM name: `google-pixel-9-pro-fold-x86` +- Disk size: `16` GB +- Memory: `4096` MB +- CPU count: `4` +- VM storage root: `vms` +- Disk path pattern: `vms\\.qcow2` + +### Common Parameters + +- `-Name`: VM name and disk folder name. +- `-IsoPath`: Path to an installer or live ISO. +- `-DiskSizeGB`: QCOW2 disk size to create if the disk does not already exist. +- `-MemoryMB`: RAM assigned to the VM. +- `-CpuCount`: Number of virtual CPUs assigned to the VM. +- `-VmRoot`: Root directory for VM storage. +- `-Install`: Boot from the ISO and attach a VM disk for OS installation. +- `-NoAccel`: Skip WHPX acceleration. + +The Android script also supports: + +- `-Live`: Boot from the Android ISO without creating or attaching a disk. + +### Notes and Troubleshooting + +- Press `Ctrl+Alt+G` to release mouse and keyboard capture from the QEMU window. +- The scripts create a disk only when one does not already exist. +- Providing `-IsoPath` without `-Install` in the general VM script boots from disk only. +- In the Android script, `-Install` and `-Live` require `-IsoPath`. +- In the Android script, `-Install` and `-Live` cannot be used together. +- If `-accel whpx` fails, enable Windows Hypervisor Platform or rerun with `-NoAccel`. +- A dummy `.iso` file can test script argument handling, but a real bootable ISO is required to install or run an operating system. diff --git a/start-android-emulator.ps1 b/start-android-emulator.ps1 new file mode 100644 index 0000000000..2cb97729ae --- /dev/null +++ b/start-android-emulator.ps1 @@ -0,0 +1,89 @@ +param( + [string]$Name = "google-pixel-9-pro-fold-x86", + [string]$IsoPath, + [int]$DiskSizeGB = 16, + [int]$MemoryMB = 4096, + [int]$CpuCount = 4, + [string]$VmRoot = (Join-Path $PSScriptRoot "vms"), + [switch]$Install, + [switch]$Live, + [switch]$NoAccel +) + +$ErrorActionPreference = "Stop" + +function Require-Command { + param([string]$CommandName) + + $command = Get-Command $CommandName -ErrorAction SilentlyContinue + if (-not $command) { + throw "$CommandName was not found on PATH. Restart your shell or add the QEMU install directory to PATH." + } + + return $command.Source +} + +if ($Install -and $Live) { + throw "Use either -Install or -Live, not both." +} + +if (($Install -or $Live) -and -not $IsoPath) { + throw "-Install and -Live require -IsoPath." +} + +$qemuImg = Require-Command "qemu-img" +$qemuSystem = Require-Command "qemu-system-x86_64" + +$vmDir = Join-Path $VmRoot $Name +$diskPath = Join-Path $vmDir "$Name.qcow2" + +New-Item -ItemType Directory -Force -Path $vmDir | Out-Null + +if (-not $Live) { + if (-not (Test-Path $diskPath)) { + Write-Host "Creating Android VM disk: $diskPath ($DiskSizeGB GB)" + & $qemuImg create -f qcow2 $diskPath "$($DiskSizeGB)G" + if ($LASTEXITCODE -ne 0) { + throw "qemu-img failed with exit code $LASTEXITCODE." + } + } else { + Write-Host "Using existing Android VM disk: $diskPath" + } +} + +$qemuArgs = @( + "-m", $MemoryMB, + "-smp", $CpuCount, + "-vga", "std", + "-usb", + "-device", "usb-tablet", + "-netdev", "user,id=net0", + "-device", "e1000,netdev=net0" +) + +if (-not $NoAccel) { + $qemuArgs = @("-accel", "whpx") + $qemuArgs +} + +if (-not $Live) { + $qemuArgs += @("-drive", "file=$diskPath,format=qcow2,if=ide") +} + +if ($Install -or $Live) { + $resolvedIso = (Resolve-Path $IsoPath).Path + Write-Host "Booting Android ISO: $resolvedIso" + $qemuArgs += @("-cdrom", $resolvedIso, "-boot", "d") +} + +if ($Live) { + Write-Host "Starting Android live session with $MemoryMB MB RAM and $CpuCount CPU(s)." +} elseif ($Install) { + Write-Host "Starting Android installer for VM '$Name' with $MemoryMB MB RAM and $CpuCount CPU(s)." +} else { + Write-Host "Starting installed Android VM '$Name' with $MemoryMB MB RAM and $CpuCount CPU(s)." +} + +Write-Host "Press Ctrl+Alt+G to release mouse/keyboard capture from the QEMU window." + +& $qemuSystem @qemuArgs +exit $LASTEXITCODE diff --git a/start-qemu-vm.ps1 b/start-qemu-vm.ps1 new file mode 100644 index 0000000000..908929bbd2 --- /dev/null +++ b/start-qemu-vm.ps1 @@ -0,0 +1,69 @@ +param( + [string]$Name = "basic-vm", + [string]$IsoPath, + [int]$DiskSizeGB = 30, + [int]$MemoryMB = 4096, + [int]$CpuCount = 2, + [string]$VmRoot = (Join-Path $PSScriptRoot "vms"), + [switch]$Install, + [switch]$NoAccel +) + +$ErrorActionPreference = "Stop" + +function Require-Command { + param([string]$CommandName) + + $command = Get-Command $CommandName -ErrorAction SilentlyContinue + if (-not $command) { + throw "$CommandName was not found on PATH. Restart your shell or add the QEMU install directory to PATH." + } + + return $command.Source +} + +$qemuImg = Require-Command "qemu-img" +$qemuSystem = Require-Command "qemu-system-x86_64" + +$vmDir = Join-Path $VmRoot $Name +$diskPath = Join-Path $vmDir "$Name.qcow2" + +New-Item -ItemType Directory -Force -Path $vmDir | Out-Null + +if (-not (Test-Path $diskPath)) { + Write-Host "Creating disk: $diskPath ($DiskSizeGB GB)" + & $qemuImg create -f qcow2 $diskPath "$($DiskSizeGB)G" + if ($LASTEXITCODE -ne 0) { + throw "qemu-img failed with exit code $LASTEXITCODE." + } +} else { + Write-Host "Using existing disk: $diskPath" +} + +$qemuArgs = @( + "-m", $MemoryMB, + "-smp", $CpuCount, + "-drive", "file=$diskPath,format=qcow2" +) + +if (-not $NoAccel) { + $qemuArgs = @("-accel", "whpx") + $qemuArgs +} + +if ($IsoPath) { + $resolvedIso = (Resolve-Path $IsoPath).Path + if ($Install) { + Write-Host "Booting installer ISO: $resolvedIso" + $qemuArgs += @("-cdrom", $resolvedIso, "-boot", "d") + } else { + Write-Host "ISO provided but -Install was not set; booting from disk only." + } +} elseif ($Install) { + throw "-Install requires -IsoPath." +} + +Write-Host "Starting VM '$Name' with $MemoryMB MB RAM and $CpuCount CPU(s)." +Write-Host "Press Ctrl+Alt+G to release mouse/keyboard capture from the QEMU window." + +& $qemuSystem @qemuArgs +exit $LASTEXITCODE From c9e17f8955140caca0ccddb962be05a592b8c765 Mon Sep 17 00:00:00 2001 From: Sarge Date: Mon, 18 May 2026 16:01:11 +1000 Subject: [PATCH 2/2] Harden VM helper scripts Co-Authored-By: Oz --- start-android-emulator.ps1 | 17 ++++++++++++++++- start-qemu-vm.ps1 | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/start-android-emulator.ps1 b/start-android-emulator.ps1 index 2cb97729ae..13c7b032e7 100644 --- a/start-android-emulator.ps1 +++ b/start-android-emulator.ps1 @@ -1,8 +1,11 @@ param( [string]$Name = "google-pixel-9-pro-fold-x86", [string]$IsoPath, + [ValidateRange(1, 4096)] [int]$DiskSizeGB = 16, + [ValidateRange(512, 1048576)] [int]$MemoryMB = 4096, + [ValidateRange(1, 256)] [int]$CpuCount = 4, [string]$VmRoot = (Join-Path $PSScriptRoot "vms"), [switch]$Install, @@ -23,6 +26,18 @@ function Require-Command { return $command.Source } +function Resolve-IsoFile { + param([string]$Path) + + $resolvedPath = Resolve-Path -LiteralPath $Path -ErrorAction Stop + $item = Get-Item -LiteralPath $resolvedPath.Path -ErrorAction Stop + if (-not $item.PSIsContainer) { + return $item.FullName + } + + throw "ISO path must point to a file: $Path" +} + if ($Install -and $Live) { throw "Use either -Install or -Live, not both." } @@ -70,7 +85,7 @@ if (-not $Live) { } if ($Install -or $Live) { - $resolvedIso = (Resolve-Path $IsoPath).Path + $resolvedIso = Resolve-IsoFile $IsoPath Write-Host "Booting Android ISO: $resolvedIso" $qemuArgs += @("-cdrom", $resolvedIso, "-boot", "d") } diff --git a/start-qemu-vm.ps1 b/start-qemu-vm.ps1 index 908929bbd2..e0269a70c7 100644 --- a/start-qemu-vm.ps1 +++ b/start-qemu-vm.ps1 @@ -1,8 +1,11 @@ param( [string]$Name = "basic-vm", [string]$IsoPath, + [ValidateRange(1, 4096)] [int]$DiskSizeGB = 30, + [ValidateRange(512, 1048576)] [int]$MemoryMB = 4096, + [ValidateRange(1, 256)] [int]$CpuCount = 2, [string]$VmRoot = (Join-Path $PSScriptRoot "vms"), [switch]$Install, @@ -22,6 +25,18 @@ function Require-Command { return $command.Source } +function Resolve-IsoFile { + param([string]$Path) + + $resolvedPath = Resolve-Path -LiteralPath $Path -ErrorAction Stop + $item = Get-Item -LiteralPath $resolvedPath.Path -ErrorAction Stop + if (-not $item.PSIsContainer) { + return $item.FullName + } + + throw "ISO path must point to a file: $Path" +} + $qemuImg = Require-Command "qemu-img" $qemuSystem = Require-Command "qemu-system-x86_64" @@ -51,7 +66,7 @@ if (-not $NoAccel) { } if ($IsoPath) { - $resolvedIso = (Resolve-Path $IsoPath).Path + $resolvedIso = Resolve-IsoFile $IsoPath if ($Install) { Write-Host "Booting installer ISO: $resolvedIso" $qemuArgs += @("-cdrom", $resolvedIso, "-boot", "d")