Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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\<name>\<name>.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\<name>\<name>.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.
104 changes: 104 additions & 0 deletions start-android-emulator.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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,
[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
}

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."
}

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-IsoFile $IsoPath
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
84 changes: 84 additions & 0 deletions start-qemu-vm.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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,
[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
}

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"

$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-IsoFile $IsoPath
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