Skip to content

Latest commit

 

History

200 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

macOS QEMU Guest Agent

A native QEMU Guest Agent for macOS and OS X virtual machines. The missing guest agent for Mac — enables Proxmox VE, libvirt, plain QEMU, and UTM to manage macOS guests through the standard QGA protocol.

42 commands, real filesystem freeze, zero dependencies. Supports Mac OS X 10.4 Tiger through macOS 26 Tahoe. Works as both a macOS guest agent and an OS X guest agent for legacy VMs.

Quick Start (Proxmox VE)

1. On the PVE host — enable the guest agent:

qm set <vmid> --agent enabled=1,type=isa
qm stop <vmid> && sleep 5 && qm start <vmid>

Why type=isa? As of v2.5.0 the agent supports ISA serial only; VirtIO transport was removed. macOS guests run either under Apple's Virtualization.framework (UTM Virtualize, vz_run, anything VZVirtualMachine-backed) — where Apple's own 18-command AppleQEMUGuestAgent claims the VirtIO console channel — or under plain QEMU/KVM (Proxmox, libvirt, raw QEMU, UTM Emulate; typically OpenCore-booted) where Apple's agent never launches but VirtIO would still let an image silently start behaving differently if it ever moved between host classes. Restricting to ISA serial gives one transport, one install, one launchd config, and one channel-detection list everywhere — Apple16X50Serial.kext has shipped on every macOS from 10.4 Tiger onwards with an identical PCI class match, so this is universally satisfiable. See docs/COMPATIBILITY.md for the full evidence and CHANGELOG v2.5.0 BREAKING for the v2.4.x → v2.5.0 migration.

2. In the macOS VM:

# Download the tri-fat binary to /tmp (i386 + x86_64 + arm64 in one file) —
# covers macOS 10.4 Tiger through 26 Tahoe; one URL for every host.
# Note: on Tiger / Leopard / older Snow Leopard guests, the VM's TLS stack
# usually cannot reach GitHub directly. Download on a modern machine and
# copy the file to /tmp on the VM (scp / shared folder / USB).
curl -fL -o /tmp/mac-guest-agent https://github.com/mav2287/mac-guest-agent/releases/latest/download/mac-guest-agent

# Install. --install does everything: extracts the host-native slice in-process
# (no lipo, no Developer Tools), places it at /usr/local/bin, then writes and
# loads the LaunchDaemon. The /tmp file is only the source — it is NOT the
# install location; delete it afterwards or let it clear on the next reboot.
chmod +x /tmp/mac-guest-agent
sudo /tmp/mac-guest-agent --install
rm -f /tmp/mac-guest-agent           # optional: the daemon now lives in /usr/local/bin

The download is one tri-fat binary so a single URL covers every host; the installed file at /usr/local/bin/mac-guest-agent is the single slice your kernel actually runs (e.g. i386 on Tiger), extracted in-process. That matters beyond saving disk: the installed daemon's arch then matches the system tools it must exec — on a Tiger 10.4.7+ guest XNU would otherwise grade the x86_64 slice of a fat binary, which can't execve Tiger's i386-only /bin (issue #9). Don't hand-place the binary with lipo/mv/cp; let --install do it.

Or use the bootstrap wrapper (downloads, then runs --install for you; also supports --local PATH for air-gapped installs and the VirtIO override):

curl -fsSL https://raw.githubusercontent.com/mav2287/mac-guest-agent/main/scripts/install.sh | sudo bash

VirtIO override (unsupported): if your orchestrator hardcodes VirtIO (kubevirt-style) and ISA isn't available, see docs/NO_ISA_OVERRIDE.mdsudo ./mac-guest-agent --install --virtio runs the gated override path (macOS 11+ only, requires SIP disabled).

3. Verify:

# From PVE host
qm agent <vmid> ping
qm agent <vmid> get-osinfo

# From inside VM
sudo mac-guest-agent --self-test

Updating

To upgrade an existing install, download the new binary the same way and run it with --upgrade — the running daemon self-sources, so you don't pass a path:

# Download (or transfer) the new release to /tmp, then on the VM:
curl -fL -o /tmp/mac-guest-agent https://github.com/mav2287/mac-guest-agent/releases/latest/download/mac-guest-agent
chmod +x /tmp/mac-guest-agent
sudo /tmp/mac-guest-agent --upgrade
rm -f /tmp/mac-guest-agent

--upgrade backs up the current binary, extracts the native slice to /usr/local/bin, regenerates the plist, restarts the daemon, and verifies it — rolling back to the backup on verify failure. (--install on top of an existing install is refused; use --upgrade.)

How It Works

The agent communicates via an ISA serial port (16550 UART) using Apple's built-in Apple16X50Serial.kext driver, present on every macOS since 10.4. No custom kexts, no SIP changes, no code signing.

Note: macOS Big Sur and newer ship Apple's own AppleQEMUGuestAgent (18 commands, no filesystem freeze). It only launches on Apple Virtualization.framework hosts — its IOKit match (AppleVirtIOAgentDevice) is set by the applevirtio.console driver, which doesn't load under QEMU/OpenCore — so on Proxmox the VirtIO channel is actually free. We still default to ISA for symmetry across both host classes (see the Quick Start callout for the trade-off). Our agent provides 42 commands plus a real per-filesystem freeze dispatch — see docs/design/FREEZE_SEMANTICS.md for what "freeze" actually does per f_fstypename.

Compatibility

Single download: mac-guest-agent (i386 + x86_64 + arm64 in one tri-fat Mach-O, covers macOS 10.4 Tiger through 26 Tahoe). dyld picks the right slice at load time. (v2.5.0 published this as mac-guest-agent-darwin-universal for one day; v2.5.1+ drops the suffix.)

ISA serial driver (Apple16X50Serial.kext) verified present with identical PCI class match on every macOS from 10.4 Tiger (2005) through 26.3 Tahoe (2026). See the compatibility matrix for per-version evidence.

Tiger / Leopard (10.4 / 10.5): /usr/local/bin is not in the default shell PATH. Invoke the binary by its absolute path (sudo /usr/local/bin/mac-guest-agent ...) or add it to PATH for the session: export PATH=/usr/local/bin:$PATH. The LaunchDaemon itself uses the absolute path and is unaffected.

If the agent doesn't start

Open an issue at https://github.com/mav2287/mac-guest-agent/issues/new with as much of the following as you can collect. Items 1-3 work even when the binary won't launch (dyld rejection); items 4-6 require the binary to start successfully.

Loader-safe (no execution needed):

  1. sw_vers — macOS version, build
  2. file /usr/local/bin/mac-guest-agent — shows the installed native slice (e.g. Mach-O executable i386 on Tiger). --install thins the download to a single slice, so this is not a fat binary; the wrong arch here (or a fat binary) points at a slice-extraction problem.
  3. file ./mac-guest-agent on the downloaded file (before install) — confirms the tri-fat download is intact (Mach-O universal binary with 3 architectures).

If the binary starts:

  1. mac-guest-agent --version
  2. mac-guest-agent --self-test-json — pipe to a file or grep selected_arch directly (do NOT pipe through python -m json.tool on Tiger/Leopard — those versions lack a reliable stdlib json module)
  3. tail -50 /var/log/mac-guest-agent.log

The universal binary is the supported install path; if it doesn't load on your specific host configuration we want to fix it, not work around it.

Service Management

sudo launchctl list com.macos.guest-agent     # Status
sudo launchctl start com.macos.guest-agent    # Start
sudo launchctl stop com.macos.guest-agent     # Stop
tail -f /var/log/mac-guest-agent.log          # Logs
sudo mac-guest-agent --uninstall              # Remove

Building from Source

make build              # Current architecture (no SDK needed)
make build-arm64        # Apple Silicon (11.0+, no SDK needed)
make test               # Run all tests

The legacy slices (i386 for 10.4+, x86_64 for 10.4+) require the macOS 10.13 SDK because modern Xcode SDKs no longer expose pre-10.7 deployment targets. Download it once, then pass it to make:

curl -fL -o /tmp/sdk.tar.xz \
  https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz
# SHA256 1d2984acab2900c73d076fbd40750035359ee1abe1a6c61eafcd218f68923a5a
tar xf /tmp/sdk.tar.xz -C /tmp

make build-i386       LEGACY_SDK=/tmp/MacOSX10.13.sdk   # 10.4+ (32-bit)
make build-x86_64     LEGACY_SDK=/tmp/MacOSX10.13.sdk   # 10.4+ (64-bit, Tiger-compatible via -ld_classic)
make build-universal  LEGACY_SDK=/tmp/MacOSX10.13.sdk   # tri-fat (release shape)

The legacy slices also depend on Apple's ld-classic linker (-Wl,-ld_classic in the Makefile) to honor -mmacosx-version-min for entry-point selection; the default ld-prime hardcodes LC_MAIN on x86_64 and produces binaries that 10.4/10.5 dyld cannot load (issue #4). See .github/workflows/build.yml for the full toolchain story.

Documentation

Guide Description
Proxmox VE VM settings, backup config, TRIM, security profiles, troubleshooting
libvirt / virt-manager Domain XML, virsh commands, quiesced snapshots, troubleshooting
UTM Local macOS VM automation, utmctl integration, CI/CD workflows
All Platforms Plain QEMU setup, ISA serial transport
VM Configs & Troubleshooting Per-OS known-good VM configs, and is it the VM or the agent? symptom→fix catalog
Commands All 42 commands with status, Linux parity, privilege requirements
Compatibility Support tiers, kext timeline, verification evidence per version
Backup & Freeze Filesystem freeze, APFS snapshots, hook scripts, TRIM
Freeze Semantics What "freeze" actually does per f_fstypename; divergences from upstream QGA
Security Trust model, recommended profiles, freeze-state restrictions
Architecture Data flow, protocol spec, macOS API usage
CLI Reference All flags, config file format, examples
Changelog Release history
Contributing Build, style, testing guidelines

License

MIT

About

Native QEMU Guest Agent for macOS and OS X VMs. ISA serial — works on every version from OS X 10.4 Tiger to macOS Tahoe. Real filesystem freeze, 45 QGA commands, zero dependencies. Drop-in for Proxmox VE, libvirt, UTM.

Topics

Resources

Contributing

Security policy

Stars

17 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages