Docs menu

Docs › Reference

Architecture

How the app, the helpers and the guest tools fit together

VMPane is a macOS app for Apple silicon that runs macOS, Linux and Windows 11 ARM virtual machines. This page describes how VMPane is built today. A part not built yet would be marked planned; at the moment none is.

Processes

processwhat it does
VMPane.appVM library, creation wizard, settings. Never runs a VM itself.
VMPaneVM.app (one per running VM)Owns one VM: its window, Dock tile, control socket and backend. A crash takes down one VM, not the app.
vmpane CLIScripting and the MCP server (vmpane mcp). Talks to helpers over their control sockets.
qemu-system-aarch64, swtpmChild processes of a Windows VM's helper, run under deny-by-default sandbox profiles.
guest agent (Rust)Runs inside the guest, installed with the guest tools: clipboard, exec, file transfer, display resize. Talks to the host over vsock (Linux, macOS) or virtio-serial (Windows).

Backends

Backend contract (VMBackend, VMPaneCore)

The helper drives every backend the same way; a new backend implements exactly this:

Swift packages

All logic that can be unit-tested lives in Packages/VMPaneKit; the Xcode targets keep only entry points, windows and views.

Code
VMPaneCore      bundle format, config, storage, snapshots, backend protocol, policy, audit
VMPaneControl   JSON-RPC over Unix sockets, control methods, peer identity, agent framing   -> Core
VMPaneVZ        Virtualization.framework backend                                            -> Core
VMPaneQEMU      QEMU child-process backend                                                  -> Core
VMPaneHost      helper-process logic: agent link, install, networking, import               -> Core, Control, VZ, QEMU
VMPaneMCP       MCP server (hand-rolled, see ADR 0002)                                      -> Core, Control
VMPaneUI        shared SwiftUI views and view models                                        -> Core, Control, Host

Packages/VMPaneSpice (helper only) is the Windows display: CocoaSpice vendored with our patches (scripts/vendor-cocoaspice.sh), built against SpiceSDK/ (headers and link stubs of the SPICE client libraries, generated from the QEMU build by qemu/scripts/spice-sdk.sh; CI fails on drift).

App bundle

Code
VMPane.app/Contents/
  MacOS/VMPane                     main app
  Helpers/vmpane                   CLI (not in MacOS/: `vmpane` and `VMPane` are the same name on case-insensitive APFS)
  Helpers/VMPaneVM.app             per-VM helper
  Helpers/VMPaneVM.app/Contents/Frameworks/   SPICE client libraries (weak-linked; from the QEMU bundle)
  Helpers/qemu/{bin,lib}/          QEMU, qemu-img, swtpm and their libraries
  Resources/qemu/share/            firmware and QEMU data files, passed with -L
  Resources/qemu/MANIFEST.json     the QEMU bundle's identity (part of the runtime copy's name)
  Resources/GuestTools/{linux,macos}/                 guest tools, shared read-only into VZ guests (vmpane-tools)
  Resources/GuestTools/vmpane-guest-tools-windows.iso the Windows guest tools disc (drivers and the agent)

Every Mach-O is listed in Config/signing-manifest.json with its signing identifier and entitlements; scripts/sign.sh signs inside-out from it and --verify checks a built bundle against it.

Trust between processes

Helpers accept control connections only from peers whose code signature satisfies our designated requirement (checked from the socket's audit token, not the PID). A Windows VM's isolation and accepted risks are in security-model.md.

QEMU (Windows guests)

A Windows VM's helper runs QEMU from its runtime copy (runtime/<version>/qemu; a helper started from the app bundle makes the copy itself):

  1. Firmware. The shipped EDK2 code is padded to the 64 MiB flash in run/<sid>/ at each start; the VM's variables (current/aux/efi_vars.fd) are made once from the Secure Boot template.
  2. swtpm, then QEMU, each through sandbox-exec with its profile (VMPaneQEMU/Sandbox/*.sb), recorded in run/<sid>.children before it runs (by pid and start time: the launcher execs), holding the bundle lock, with a minimal environment and TMPDIR in the run directory. Paths handed to them are real paths (sandbox rules match those).
  3. QMP (run/<sid>/qmp.sock) controls QEMU: pause, ACPI power button, quit, screenshots (screendump, PPM), input (input-send-event), RAM state (file migration), port forwards (hostfwd_add/hostfwd_remove). Its events drive the state machine; an unexpected exit, or the TPM going away, is a failure carrying the end of logs/qemu.log.
  4. Display: the helper's SPICE client on run/<sid>/spice.sock, reconnected whenever QEMU restarts. QEMU runs with -no-shutdown: a guest power-off pauses it, the client disconnects, then QEMU quits (QEMU's spice-server can hang in its cleanup with a client still connected).
  5. Disk space: NVMe has no write-error policy, so the helper pauses the guest when the bundle's volume drops below 2 GiB free and resumes it only from 3 GiB (diskFull event).

Suspend and live snapshots save RAM state with a plain file migration after stop (state/state.qemu, with state-qemu.json naming the QEMU that wrote it: only the same QEMU loads it); the swtpm state travels in the stream. Unlike VZ's, QEMU's saved state does not need an unlocked Mac.

Edit this page on GitHubLast updated