MacOS on QEMU Notes

I tried getting a MacOS VM running on Linux with the goal of having a simple disposable libvirt prototype so I don’t have to pay AWS $10 a minute for EC2 or drag ancient Apple hardware out of the tech closet just to run a one-off MacOS task every other month. My goal failed on simplicity so this is me documenting the why, how and other notable specifics before I move on and forget about the whole ordeal.

Downloading the “.ISO”

Used quickget which masquerades as an Apple laptop requesting a network OS recovery from Apple to download a “recovery image”:

-rw-r--r--. 1 david david 3.0G Dec 28 15:03 RecoveryImage.img

Unsurprisingly, Apple doesn’t distribute a proper .ISO but the file that quickget downloads from them does look like a disk image file with an HFS+ fs on it. I wasn’t able to get mount to cooperate but after changing the file extension to .iso, GVFS’s fuse magic did the rest:

17:23:34 [strago /run/media/david/macOS Base System]$ tree -L 1
.
├── Applications
├── bin
├── cores
├── dev
├── etc -> private/etc
├── Install macOS Ventura.app
├── Library
├── opt
├── private
├── sbin
├── System
├── tmp -> private/tmp
├── Users
├── usr
├── var -> private/var
└── Volumes

The image is mountable but apparently not bootable so quickget brings in a bunch of other ROMs (this is where “simplicity” as a goal starts to move out of reach):

-rw-r--r--. 1 david david  18M Dec 28 13:02 OpenCore.qcow2
-rw-r--r--. 1 david david 1.9M Dec 28 13:02 OVMF_CODE.fd
-rw-r--r--. 1 david david 128K Dec 28 13:02 OVMF_VARS-1920x1080.fd
  • OVMF_CODE.fd – UEFI firmware?
  • OVMF_VARS-1920x1080.fd – nvram data?
  • OpenCore.qcow2 – custom bootloader which provides GUI for choosing the boot option, both for the “recovery” phase and for the “just start my desktop already” phase

Installing the OS

Not immediately obvious how to overlay those items onto a libvirt XML guest definition so I used quickemu to generate a launcher script in the VM directory containing this massive qemu incantation:

/usr/bin/qemu-system-x86_64 \
    -name macos-ventura,process=macos-ventura \
    -machine q35,hpet=off,smm=off,vmport=off,accel=kvm \
    -global kvm-pit.lost_tick_policy=discard \
    -global ICH9-LPC.disable_s3=1 \
    -device isa-applesmc,osk=ourhardworkbythesewordsguardedpleasedontsteal\(c\)AppleComputerInc \
    -global nec-usb-xhci.msi=off \
    -cpu Haswell-v2,vendor=GenuineIntel,-pdpe1gb,+avx,+sse,+sse2,+ssse3,vmware-cpuid-freq=on,+avx2,+sse4.2,+abm,+adx,+aes,+apic,+arat,+bmi1,+bmi2,+clflush,+cmov,+cx8,+cx16,+de,+erms,+f16c,+fma,+fsgsbase,+fxsr,+invpcid,+lahf-lm,+lm,+mca,+mce,+mmx,+movbe,+msr,+mtrr,+nx,+pae,+pat,-pcid,+pge,+pse,+popcnt,+pse36,+rdrand,+rdtscp,+sep,+smep,+syscall,+tsc,+vaes,+vpclmulqdq,+x2apic,+xgetbv1,+xsave,+xsaveopt \
    -smp cores=4,threads=2,sockets=1 \
    -m 16G \
    -device virtio-balloon \
    -rtc base=localtime,clock=host,driftfix=slew \
    -pidfile macos-ventura.pid \
    -vga none \
    -device VGA,xres=1280,yres=800,vgamem_mb=256 \
    -display sdl,gl=on \
    -device virtio-rng-pci,rng=rng0 \
    -object rng-random,id=rng0,filename=/dev/urandom \
    -device nec-usb-xhci,id=spicepass \
    -chardev spicevmc,id=usbredirchardev1,name=usbredir \
    -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1 \
    -chardev spicevmc,id=usbredirchardev2,name=usbredir \
    -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2 \
    -chardev spicevmc,id=usbredirchardev3,name=usbredir \
    -device usb-redir,chardev=usbredirchardev3,id=usbredirdev3 \
    -device pci-ohci,id=smartpass \
    -device usb-ccid \
    -chardev spicevmc,id=ccid,name=smartcard \
    -device ccid-card-passthru,chardev=ccid \
    -device qemu-xhci,id=input \
    -device usb-kbd,bus=input.0 \
    -k en-us \
    -device usb-tablet,bus=input.0 \
    -audiodev pa,id=audio0 \
    -device intel-hda \
    -device hda-micro,audiodev=audio0 \
    -device virtio-net,netdev=nic \
    -netdev user,hostname=macos-ventura,hostfwd=tcp::22220-:22,id=nic \
    -global driver=cfi.pflash01,property=secure,value=on \
    -drive if=pflash,format=raw,unit=0,file=OVMF_CODE.fd,readonly=on \
    -drive if=pflash,format=raw,unit=1,file=OVMF_VARS-1920x1080.fd \
    -device ahci,id=ahci \
    -device ide-hd,bus=ahci.0,drive=BootLoader,bootindex=0 \
    -drive id=BootLoader,if=none,format=qcow2,file=OpenCore.qcow2 \
    -device ide-hd,bus=ahci.1,drive=RecoveryImage \
    -drive id=RecoveryImage,if=none,format=raw,file=RecoveryImage.img \
    -device virtio-blk-pci,drive=SystemDisk \
    -drive id=SystemDisk,if=none,format=qcow2,file=disk.qcow2 \
    -monitor unix:macos-ventura-monitor.socket,server,nowait \
    -serial unix:macos-ventura-serial.socket,server,nowait

Aside from the weirdo on line 6 (which is apparently a real device type), most of this is standard boilerplate you’d see with any libvirt VM. The -device, -drive, -device stanzas at the bottom deal with the fact that the image has no boot loader. The command puts three disks into the VM and marks the BootLoader as the boot device, which will then let you choose which boot option to take. Those three disks are:

  • OpenCore.qcow2 – again, this is the bootloader
  • RecoveryImage.img – this is the installation disk source
  • disk.qcow2 – a new empty disk (qemu-img create -f qcow2 disk.qcow2 128G)

I had to “Erase” the empty disk before System Recovery would let me install anything on it. After 2 or 3 rounds of install reboots, disk.qcow2 was at about 28GB before booting into it for the first time and going thru OS onboarding.

Booting into the actual OS

A lot of sluggishness during system onboarding which surprisingly just vanished once I got to the desktop. But it does work. Well, specifically, the only thing I actually care about, the iTunes Store via Apple Music sort of works with only the page footer mysteriously disappearing when clicked for some probably totally valid reason:

screenshot

I figured I could do the install once, yank the post-install pre-onboarding disk.qcow2 into a new libvirt guest without the bootloader cascade shenanigans but the “System Recovery” process doesn’t seem to make the system drive bootable so it would always requires the secondary OpenCore disk.

screenshot

Conclusion

I could overlook the clunky bootloader process (assuming I could even model it in libvirt XML) I don’t like the provenance story of the firmware and images involved in the bootloader cascade so I’d always be nervous about putting Apple Account credentials into this thing so I’m tabling the idea.

Somehow Windows 10 + iTunes seems to be the simplest solution here.

Resources

loading blog data