Skip to content

jacks4ever/macos-vm-config

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

macOS Sequoia 15 on CloudStack KVM — Working Configuration

VM: macOS-Sequoia (i-2-2657-VM, UUID 00b13b9f-35ad-4d0c-a451-b1ffb3782e0d)
Host: ringleader (192.168.46.145)
Disk (active layer): fac18bc1-3fb7-4d2c-b6c2-6e6043cfd71a
Disk (base): 3e4a6d36-83a3-43ab-84d7-34d2c6fd1dad


VNC Access

VNC listens on 192.168.46.10 (cloudbr0), not the physical NIC. SSH tunnel required:

ssh -i ~/.ssh/ringleader_key -L 5912:192.168.46.10:5912 rhettsaunders@192.168.46.145 -N

Then connect VNC to localhost:5912, password: BGnNR3Rr

Port may change after each VM restart. Check with: sudo ss -tlnp | grep qemu then match via sudo virsh dumpxml i-2-2657-VM | grep "graphics type"


VM Start / Stop

Always use CloudStack API — never virsh directly (causes state desync):

# Stop
sudo cmk stop virtualmachine id=00b13b9f-35ad-4d0c-a451-b1ffb3782e0d forced=true

# Start
sudo cmk start virtualmachine id=00b13b9f-35ad-4d0c-a451-b1ffb3782e0d

The Keyboard/Mouse Fix (Root Cause + Solution)

Problem 1 — macOS never booted (launchd kernel panic)

macOS 12.3+ stores libSystem.B.dylib inside an OS Cryptex DMG on the Preboot APFS volume. On KVM, the cryptex signature check fails — launchd panics on every boot with Library not loaded: /usr/lib/libSystem.B.dylib. macOS appeared to show a login screen (EFI framebuffer) but was actually dead.

Fix: Add amfi_get_out_of_my_way=1 to OpenCore boot-args. This tells AMFI to skip signature verification so the cryptex mounts and launchd starts cleanly.

Problem 2 — USB keyboard/mouse not working in macOS

Three separate root causes all present simultaneously:

a) DisableIoMapper was False
Without DisableIoMapper=True in OpenCore Kernel quirks, Apple's IOMapper blocks USB DMA on KVM hosts that don't expose a real IOMMU. USB controller initializes but devices are never enumerated.

b) Wrong kexts — UTBDefault is a placeholder only
UTBDefault.kext uses IOPCIClassMatch to attach USBToolBox to XHCI controllers, but USBToolBox then requires a separately generated UTBMap.kext with the actual port mapping. Without it, no ports are mapped and USB HID never loads. Fix: replace with USBInjectAll.kext which auto-enumerates QEMU XHCI ports without manual mapping.

c) Missing QEMU global flags
Without these two flags, USB devices behind PCIe root ports are invisible to macOS:

  • -global nec-usb-xhci.msi=off — prevents MSI assertion crash in QEMU USB stack
  • -global ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off — required for macOS to enumerate USB through PCIe bridges on q35

These are injected via the CloudStack hook at /etc/cloudstack/agent/hooks/macos-transform.py.

d) VoodooPS2Controller conflicts with USB HID
Remove it. QEMU's sendkey injects into PS/2 (i8042), not USB — so sendkey not working in macOS is expected and not a diagnostic signal when using USB input.


Working OpenCore Kexts (config.plist Kernel->Add order)

# Kext Purpose
1 Lilu.kext Dependency for patching kexts
2 USBInjectAll.kext Auto-maps QEMU XHCI ports
3 VirtualSMC.kext Apple SMC emulation
4 MCEReporterDisabler.kext Suppresses MCA error spam

Disabled (do not re-enable):

  • VoodooPS2Controller — conflicts with USB HID, PS/2 not used
  • UTBDefault / USBToolBox — placeholder only, needs UTBMap (not generated)
  • USBPorts.kext — targets nec-xhci device ID (0x01941033), wrong for qemu-xhci
  • WhateverGreen.kext — unnecessary for vmware-svga virtual GPU, causes black screen
  • CryptexFixup.kext — conflicts with amfi_get_out_of_my_way=1

Working OpenCore boot-args

keepsyms=1 -v amfi_get_out_of_my_way=1

Working OpenCore Kernel Quirks (non-default values)

DisableIoMapper: True          ← critical for USB on KVM without real IOMMU
DisableLinkeditJettison: True
PanicNoKextDump: True
PowerTimeoutKernelPanic: True
ProvideCurrentCpuInfo: True
XhciPortLimit: False           ← must stay False (causes crash on macOS 11.3+)

Hook Script (/etc/cloudstack/agent/hooks/macos-transform.py)

Transforms CloudStack libvirt XML before QEMU starts. Does:

  1. CPU → host-passthrough (required for macOS)
  2. KVM hidden → <kvm><hidden state='on'/></kvm> (hides hypervisor from macOS)
  3. SMBIOS → iMac19,1 Apple branding
  4. Injects qemu-xhci at PCI bus=0x03 slot=0x00 (explicit address mandatory)
  5. Injects <input type='keyboard' bus='usb'/> (CloudStack only adds tablet)
  6. Injects isa-applesmc with OSK string via qemu:commandline
  7. Injects -global nec-usb-xhci.msi=offkeyboard/mouse fix
  8. Injects -global ICH9-LPC.acpi-pci-hotplug-with-bridge-support=offkeyboard/mouse fix

EFI Partition Access Procedure

VM must be stopped first. Always mount the active layer, never the base:

sudo qemu-nbd -c /dev/nbd2 /export/primary/fac18bc1-3fb7-4d2c-b6c2-6e6043cfd71a
sleep 2
sudo fsck.fat -a /dev/nbd2p1   # fix dirty bit — always required after unclean unmount
sudo mount /dev/nbd2p1 /tmp/efi
# ... edit /tmp/efi/EFI/OC/config.plist, Kexts/, ACPI/ ...
sudo sync
sudo umount /tmp/efi
sudo qemu-nbd --disconnect /dev/nbd2

For APFS system volume (macOS logs, diagnostics):

sudo /tmp/apfs-fuse/build/apfs-fuse -o allow_other /dev/nbd2p2 /tmp/macos
# Preboot volume (cryptex, EFI boot files):
sudo /tmp/apfs-fuse/build/apfs-fuse -v 1 /dev/nbd2p2 /tmp/macos_preboot

apfs-fuse binary lives at /tmp/apfs-fuse/build/apfs-fuse — rebuilt from source each session since /tmp is cleared on reboot.


See Also

LESSONS_LEARNED.md — full troubleshooting history, dead ends, and explanations


NVRAM Recovery

If the OVMF NVRAM is ever corrupted or reset, restore from backup:

sudo cp /home/rhettsaunders/macos-vm-config/OVMF_VARS_WORKING.fd \
    /var/lib/libvirt/qemu/nvram/00b13b9f-35ad-4d0c-a451-b1ffb3782e0d.fd
sudo chmod 600 /var/lib/libvirt/qemu/nvram/00b13b9f-35ad-4d0c-a451-b1ffb3782e0d.fd

If backup is lost, recreate with uefivars:

pip3 install uefivars
NVRAM=/var/lib/libvirt/qemu/nvram/00b13b9f-35ad-4d0c-a451-b1ffb3782e0d.fd
sudo cp /usr/share/OVMF/OVMF_VARS_4M.fd $NVRAM
sudo uefivars -i edk2 -o json -I $NVRAM > /tmp/nvram.json
python3 -c "
import json, struct
with open('/tmp/nvram.json') as f: data = json.load(f)
data['variables'].append({'guid':'7235c51c-0c80-4cab-87ac-3b084a6304b1','name':'Setup','attr':7,'data':struct.pack('<II',1920,1080).hex()})
with open('/tmp/nvram_fixed.json','w') as f: json.dump(data,f)
"
sudo uefivars -i json -o edk2 -I /tmp/nvram_fixed.json -O $NVRAM

CloudStack Details Update Warning

CRITICAL: cmk update virtualmachine details[0].X=Y REPLACES the entire details map. Always include ALL 8 required details in every update call:

sudo cmk update virtualmachine id=00b13b9f-35ad-4d0c-a451-b1ffb3782e0d \
  "details[0].boot.mode=LEGACY" \
  "details[0].dataDiskController=sata" \
  "details[0].firmware=UEFI" \
  "details[0].kvm.guest.os.machine.type=pc-q35-noble" \
  "details[0].nicAdapter=Virtio" \
  "details[0].rootDiskController=sata" \
  "details[0].smc.present=true" \
  "details[0].video.hardware=vmvga"

Files in This Directory

File Purpose
macos-transform.py Working hook script (deployed to /etc/cloudstack/agent/hooks/)
OVMF_VARS_WORKING.fd Working NVRAM backup with 1920x1080 resolution set
LESSONS_LEARNED.md Full troubleshooting history and configuration notes
README.md Quick reference (this file)
libvirt-vm-xml-transformer.groovy CloudStack Groovy hook (UEFI/Q35/macOS)
libvirt-vm-xml-transformer.sh Shell wrapper (routes macOS VM to Python)
macos_q35.xml Reference libvirt XML
macos_transform_input.xml Captured CloudStack XML input
USBToolBox.kext/ USB controller driver (archive — replaced by USBInjectAll)
UTBDefault.kext/ Codeless kext (archive — replaced by USBInjectAll)
VoodooPS2Controller.kext/ PS/2 driver (archive — removed, conflicts with USB HID)

OpenCore Boot Entry Fix — Skip EFI Partition (2026-03-25)

Problem

With ShowPicker=False, OpenCore boots the FIRST entry it finds, which is the EFI partition (not macOS). This causes a black screen or UEFI shell on every boot.

Fix: ScanPolicy excludes EFI System Partition

Set Misc -> Security -> ScanPolicy to a bitmask that allows APFS + SATA but excludes OC_SCAN_ALLOW_FS_ESP (0x400):

ScanPolicy = 0x10103 = 65795
  0x1     OC_SCAN_FILE_SYSTEM_LOCK (restrict to defined FS)
  0x2     OC_SCAN_DEVICE_LOCK (restrict to defined device types)
  0x100   OC_SCAN_ALLOW_FS_APFS
  0x10000 OC_SCAN_ALLOW_DEVICE_SATA

With this, OpenCore never sees the EFI partition as bootable — macOS is the only entry and boots directly.

Full working picker config

ShowPicker: False
Timeout: 0
ScanPolicy: 65795
PollAppleHotKeys: True  (hold Option key to get picker for troubleshooting)
AllowSetDefault: True

ScanPolicy reference (useful bits)

Bit Value Meaning
0 0x1 FS lock — restrict to defined filesystems
1 0x2 Device lock — restrict to defined device types
8 0x100 Allow APFS
9 0x200 Allow HFS+
10 0x400 Allow ESP (EFI System Partition) — EXCLUDE THIS
16 0x10000 Allow SATA
19 0x80000 Allow NVMe
21 0x200000 Allow USB

NVRAM resolution changes — binary patch only

NEVER use uefivars export/import to change resolution — it corrupts the NVRAM. Instead, binary-patch the 8-byte resolution struct directly:

# Example: change 1920x1080 to 1440x900
import struct
data = open(NVRAM_PATH, "rb").read()
old = struct.pack("<II", 1920, 1080)  # 80 07 00 00 38 04 00 00
new = struct.pack("<II", 1440, 900)   # A0 05 00 00 84 03 00 00
idx = data.find(old)
if idx >= 0:
    data = data[:idx] + new + data[idx+8:]
    open(NVRAM_PATH, "wb").write(data)

VRAM limits (vmware-svga)

VRAM Result
16MB (default) Works, no wallpaper
64MB Works, wallpaper renders
128MB Works
512MB Works (current)
1GB Black screen — too large for vmware-svga

VM resources (current)

  • vCPUs: 8 (Large Instance, Xeon Gold 6132)
  • RAM: 16GB
  • VRAM: 512MB
  • Resolution: 1280x768
  • Service Offering: e24a4e1e-1a3e-4571-bbd5-7aab3f694633 (Large Instance)

Hook CPU topology must match service offering

When changing service offerings (CPU count), update the hook's topology:

"<topology sockets='1' dies='1' cores='8' threads='1'/>"

Mismatch causes "Unable to create deployment" error.

About

macOS VM configuration: libvirt XML configs, OVMF firmware, OpenCore kexts, and transform scripts

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors