Skip to content
Closed
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
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,22 +173,25 @@ Runners created from this image preinstall all dependencies (including those spe

To prepare a server for macOS 13/14/15 guests, build a clean image, replacing “13” with the macOS version as needed:

- Clone the OSX-KVM repo: `git clone --recursive https://github.com/kholia/OSX-KVM.git /var/lib/libvirt/images/OSX-KVM`
- Download the BaseSystem.dmg: `( cd /var/lib/libvirt/images/OSX-KVM; ./fetch-macOS-v2.py )`
- Rename it to reflect the macOS version: `mv /var/lib/libvirt/images/OSX-KVM/BaseSystem{,.macos13}.dmg`
- Convert that .dmg to .img: `dmg2img -i /var/lib/libvirt/images/OSX-KVM/BaseSystem.macos13.{dmg,img}`
- Clone the OSX-KVM repo: `git clone --recursive https://github.com/kholia/OSX-KVM.git /Users/servo/OSX-KVM`
- Download the BaseSystem.dmg: `( cd /Users/servo/OSX-KVM; ./fetch-macOS-v2.py )`
- Rename it to reflect the macOS version: `mv /Users/servo/OSX-KVM/BaseSystem{,.macos13}.dmg`
- Convert that .dmg to .img: `dmg2img -i /Users/servo/OSX-KVM/BaseSystem.macos13.{dmg,img}`
- Reduce the OpenCore `Timeout` setting:
- `cd /var/lib/libvirt/images/OSX-KVM/OpenCore`
- `cd /Users/servo/OSX-KVM/OpenCore`
- `vim config.plist`
- Type `/<key>Timeout<`, press **Enter**, type `j0f>wcw5`, press **Escape**, type `:x`, press **Enter**
- `rm OpenCore.qcow2`
- `./opencore-image-ng.sh --cfg config.plist --img OpenCore.qcow2`
- `cp /var/lib/libvirt/images/OSX-KVM/OpenCore/OpenCore{,.macos13}.qcow2`
- `cp /Users/servo/OSX-KVM/OpenCore/OpenCore{,.macos13}.qcow2`
- Create zvol and libvirt guest with random UUID and MAC address
- `zfs create -V 90G tank/base/servo-macos13.clean`
- `virsh define profiles/servo-macos13/guest.xml`
- `virt-clone --preserve-data --check path_in_use=off -o servo-macos13.init -n servo-macos13.clean --nvram /var/lib/libvirt/images/OSX-KVM/OVMF_VARS.servo-macos13.clean.fd --skip-copy sda -f /dev/zvol/tank/base/servo-macos13.clean --skip-copy sdc`
- `cp /var/lib/libvirt/images/OSX-KVM/{OVMF_VARS-1920x1080.fd,OVMF_VARS.servo-macos13.clean.fd}`
- `virt-clone --preserve-data --check path_in_use=off -o servo-macos13.init -n servo-macos13.clean --nvram /Users/servo/OSX-KVM/OVMF_VARS.servo-macos13.clean.fd --skip-copy sda -f /dev/zvol/tank/base/servo-macos13.clean --skip-copy sdc`
- `cp /Users/servo/OSX-KVM/{OVMF_VARS-1920x1080.fd,OVMF_VARS.servo-macos13.clean.fd}`
- Pad the firmware images to 64MiB [as required by qemu on aarch64](https://www.kraxel.org/blog/2022/05/edk2-virt-quickstart/):
- `truncate -s 64M OVMF_CODE.fd`
- `truncate -s 64M OVMF_VARS.servo-macos15-arm.clean.fd`
- `virsh undefine --keep-nvram servo-macos13.init`
- TODO: improve per-vm nvram management
- `virsh start servo-macos13.clean`
Expand Down
114 changes: 114 additions & 0 deletions profiles/servo-macos15-arm/boot-script
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env zsh
set -euxo pipefail -o bsdecho

download() {
curl -fsSLO "http://192.168.100.1:8000/image-deps/macos13/$1"
}

install_github_actions_runner() {
if ! [ -e actions-runner-osx-x64.tar.gz ]; then
download actions-runner-osx-x64.tar.gz
rm -Rf actions-runner
mkdir -p actions-runner
( cd actions-runner; tar xf ../actions-runner-osx-x64.tar.gz )
fi
}

bake_servo_repo() (
# Note the parentheses around this block, so we only cd for this function
cd ~/a/servo/servo

# Fix the remote url, since it’s still set to our cache
git remote set-url origin https://github.com/servo/servo.git

# Install the Rust toolchain, for checkouts without servo#35795
rustup show active-toolchain || rustup toolchain install

./mach bootstrap --force
# Build the same way as a typical macOS libservo job, to allow for incremental builds.
# FIXME: `cargo build -p libservo` is busted on most platforms <https://github.com/servo/servo/issues/37939>
# FIXME: `cargo build -p libservo` is untested in CI <https://github.com/servo/servo/issues/38015>
# cargo build -p libservo --all-targets --release --target-dir target/libservo
# Build the same way as a typical macOS build job, to allow for incremental builds.
./mach build --use-crown --locked --release
)

start_github_actions_runner() {
curl -fsS --max-time 5 --retry 99 --retry-all-errors http://192.168.100.1:8000/github-jitconfig | jq -er . > jitconfig
actions-runner/run.sh --jitconfig $(cat jitconfig)
}

mkdir -p /Users/servo/ci
cd /Users/servo/ci

# Resize the window to occupy more of the 1280x800 display
# - Method based on <https://apple.stackexchange.com/a/290802>
# - Another method for exclusive fullscreen <https://apple.stackexchange.com/a/58962>
# - Another method with unclear automation <https://apple.stackexchange.com/a/228052>
osascript -e 'tell application "Terminal"' -e 'activate' -e 'set the bounds of the first window to {0,0,1280,600}' -e 'end tell'

# Disable sleep and display sleep
# <https://apple.stackexchange.com/a/458157>
sudo pmset sleep 0
sudo pmset displaysleep 0

# ~/.cargo/env requires HOME to be set
export HOME=/Users/servo

# Ensure uv is on PATH
export PATH=$HOME/.local/bin:$PATH

if ! [ -e image-built ]; then
# Install Xcode CLT (Command Line Tools) non-interactively
# <https://github.com/actions/runner-images/blob/3d5f09a90fd475a3531b0ef57325aa7e27b24595/images/macos/scripts/build/install-xcode-clt.sh>
download install-xcode-clt.sh
chmod +x install-xcode-clt.sh
sudo -i mkdir -p /var/root/utils
sudo -i touch /var/root/utils/utils.sh
sudo -i $PWD/install-xcode-clt.sh

# Install Homebrew
if ! [ -e /usr/local/bin/brew ]; then
download install-homebrew.sh
chmod +x install-homebrew.sh
NONINTERACTIVE=1 ./install-homebrew.sh
fi

set -- gnu-tar # Install gtar(1)
set -- "$@" jq # Used by start_github_actions_runner()

brew install "$@"

# Install rustup and the latest Rust
if ! [ -e ~/.rustup ]; then
download rustup-init
chmod +x rustup-init
./rustup-init -y --quiet
mkdir -p ~/.cargo
curl -fsSLo ~/.cargo/config.toml http://192.168.100.1:8000/image-deps/cargo-config.toml
fi

# Install uv
if ! [ -e ~/.local/bin/uv ]; then
download uv-installer.sh
chmod +x uv-installer.sh
./uv-installer.sh
fi
fi

# Set up Cargo
. ~/.cargo/env

if ! [ -e image-built ]; then
# Clone and bake the Servo repo
mkdir -p ~/a/servo
git clone http://192.168.100.1:8000/cache/servo/.git ~/a/servo/servo
bake_servo_repo

install_github_actions_runner
touch image-built
sudo shutdown -h now
exit # `shutdown` does not exit
else
start_github_actions_runner
fi
218 changes: 218 additions & 0 deletions profiles/servo-macos15-arm/guest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Based on <https://github.com/kholia/OSX-KVM/blob/182e2dd0715175801521f6342ac7cc715044cb12/macOS-libvirt-Catalina.xml> -->
<domain type='hvf' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<!--
macOS libvirt XML configuration.

Run "virt-xml-validate macOS-libvirt-Catalina.xml" to validate this file.

To install this file, you may place it at ~/.config/libvirt/qemu/
and run: virsh define macOS-libvirt.xml.

This configuration has been tested in Ubuntu 20.04 with stock QEMU-KVM.

Move/rename images and loader/nvmram files and paths as you wish.

!!! Don't forget to replace CHANGEME with your values !!!

Adjust memory and currentMemory to 3145728 if you want only 3 GiB.

Consider removing some cpu features if your hardware lacks support.

Replace spice with vnc if you prefer it.

Current network configuration is a local bridge (192.168.12x.x).
Change it to if you prefer a public bridge instead:
Change interface to <interface type='user'>
and remove the <source bridge='virbr0'/>
Or use virt-manager to edit this line instead of virsh edit.

Note: Default configuration caused severe clock problems
under Fedora 27 w/ i7-5820K. This is because Darwin uses
tsc (time since last tick) for time, and for me did not
fall back to rtc in the event of a clock mismatch with
libvirt's default time source. Therefore we must explicitly
give the clock a tsc timer for kvm to pass to the guest.
See comments on the <kvm> and <clock> attributes.
-->
<name>servo-macos15-arm.init</name>
<uuid>512d54e2-486d-409c-a873-6d8d5b9fa43f</uuid>
<memory unit="KiB">25165824</memory>
<currentMemory unit="KiB">25165824</currentMemory>
<vcpu placement='static'>16</vcpu>
<os>
<type arch='aarch64'>hvm</type>
<!-- We don't need patched OVMF anymore when using latest OpenCore, stock one is okay -->
<loader readonly='yes' type='pflash'>/Users/shuppy/OSX-KVM/OVMF_CODE.fd</loader>
<nvram>/Users/shuppy/OSX-KVM/OVMF_VARS.fd</nvram>
</os>
<features>
<acpi/>
<apic/>
</features>
<clock offset='utc'>
<timer name='rtc' tickpolicy='catchup'/>
<timer name='pit' tickpolicy='delay'/>
<timer name='hpet' present='no'/>
</clock>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/opt/homebrew/bin/qemu-system-aarch64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='writeback' io='threads'/>
<source file='/Users/shuppy/OSX-KVM/OpenCore/OpenCore.macos15-arm.qcow2'/>
<target dev='sda' bus='sata'/>
<boot order='2'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<disk type='block' device='disk'>
<!-- TODO: can we benefit from these OSX-KVM defaults? -->
<!-- <driver name='qemu' type='qcow2' cache='writeback' io='threads'/> -->
<driver name='qemu' type='raw' cache='none' io='native' discard='unmap'/>
<!-- virt-clone(1) will replace this with the first `-f` -->
<!--
FIXME:
2025-10-30T05:06:57.286979Z qemu-system-aarch64: -blockdev {"driver":"host_device","filename":"/dev/null","aio":"native","node-name":"libvirt-2-storage","read-only":false,"discard":"unmap","cache":{"direct":true,"no-flush":false}}: aio=native was specified, but is not supported in this build.
-->
<source dev='/dev/null'/>
<target dev='sdb' bus='sata'/>
<boot order='1'/>
<address type='drive' controller='0' bus='0' target='0' unit='1'/>
</disk>
<disk type="file" device="disk">
<driver name="qemu" type="raw" cache="writeback"/>
<source file="/Users/shuppy/OSX-KVM/BaseSystem.macos15-arm.img"/>
<target dev="sdc" bus="sata"/>
<boot order="3"/>
<address type="drive" controller="0" bus="0" target="0" unit="2"/>
</disk>
<controller type='sata' index='0'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x1f' function='0x2'/>
</controller>
<controller type='pci' index='0' model='pcie-root'/>
<controller type='pci' index='1' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='1' port='0x8'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0' multifunction='on'/>
</controller>
<controller type='pci' index='2' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='2' port='0x9'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<controller type='pci' index='3' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='3' port='0xa'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='4' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='4' port='0xb'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x3'/>
</controller>
<controller type='pci' index='5' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='5' port='0xc'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x4'/>
</controller>
<controller type='pci' index='6' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='6' port='0xd'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x5'/>
</controller>
<controller type='pci' index='7' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='7' port='0xe'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x6'/>
</controller>
<controller type='virtio-serial' index='0'>
<address type='pci' domain='0x0000' bus='0x02' slot='0x00' function='0x0'/>
</controller>
<controller type='usb' index='0' model='ich9-ehci1'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x7'/>
</controller>
<controller type='usb' index='0' model='ich9-uhci1'>
<master startport='0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0' multifunction='on'/>
</controller>
<controller type='usb' index='0' model='ich9-uhci2'>
<master startport='2'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x1'/>
</controller>
<controller type='usb' index='0' model='ich9-uhci3'>
<master startport='4'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x2'/>
</controller>
<!-- Make sure you put your nic in bus 0x0 and slot 0x0y(y is numeric), this will make nic built-in and apple-store work-->
<!-- TODO <https://www.sobyte.net/post/2022-10/mac-qemu-bridge/>? -->
<!-- <interface type="bridge">
<mac address="52:54:00:e6:85:40"/>
<source bridge="cinet"/>
<model type="vmxnet3"/>
<address type="pci" domain="0x0000" bus="0x00" slot="0x00" function="0x0"/>
</interface> -->
<!-- <serial type='pty'>
<target type='isa-serial' port='0'>
<model name='isa-serial'/>
</target>
</serial> -->
<console type='pty'>
<target type='serial' port='0'/>
</console>
<channel type='unix'>
<target type='virtio' name='org.qemu.guest_agent.0'/>
<address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>
<graphics type="vnc" port="5901" autoport="yes" listen="127.0.0.1">
<listen type="address" address="127.0.0.1"/>
</graphics>
<video>
<model type="virtio" heads="1" primary="yes"/>
</video>
<!-- If you wanna passthrough GPU, make sure the gfx and audio are in the same bus (like 0x01) but different function (0x00 and 0x01)-->
<!-- <hostdev mode='subsystem' type='pci' managed='yes'>
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0x2d' slot='0x00' function='0x0'/>
</source>
<rom file='/mnt/disks/backups/BIOS/RX580/Ellesmere.rom'/>
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0' multifunction='on'/>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='yes'>
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0x2d' slot='0x00' function='0x1'/>
</source>
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x1'/>
</hostdev> -->
<!-- If you wanna passthrough onboard audio(like 30:00.4), make sure you put it in bus 0x00 and slot 0x0y(y is numeric), otherwise AppleALC won't recognized it -->
<!-- <hostdev mode='subsystem' type='pci' managed='yes'>
<driver name='vfio'/>
<source>
<address domain='0x0000' bus='0x30' slot='0x00' function='0x4'/>
</source>
<address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
</hostdev> -->
<memballoon model='none'/>
</devices>
<!-- Note: Enable the next line when SELinux is enabled -->
<!-- seclabel type='dynamic' model='selinux' relabel='yes'/> -->
<qemu:commandline>
<qemu:arg value='-device'/>
<qemu:arg value='isa-applesmc,osk=ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc'/>
<qemu:arg value='-smbios'/>
<qemu:arg value='type=2'/>
<qemu:arg value='-usb'/>
<qemu:arg value='-device'/>
<qemu:arg value='usb-tablet'/>
<qemu:arg value='-device'/>
<qemu:arg value='usb-kbd'/>
<!-- <qemu:arg value='Penryn,vendor=GenuineIntel,+hypervisor,+invtsc,kvm=on,+fma,+avx,+avx2,+aes,+ssse3,+sse4_2,+popcnt,+sse4a,+bmi1,+bmi2'/> -->
<!-- If you wanna use cpu host-passthrough mode, uncomments below-->
<!-- <qemu:arg value='host,vendor=GenuineIntel,+hypervisor,+invtsc,kvm=on,+fma,+avx,+avx2,+aes,+ssse3,+sse4_2,+popcnt,+sse4a,+bmi1,+bmi2'/> -->
<!-- If you wanna use cpu emulating mode like Skylake-Server, uncomments below-->
<!-- <qemu:arg value='Skylake-Server,vendor=GenuineIntel,+hypervisor,+invtsc,kvm=on,+fma,+avx,+avx2,+aes,+ssse3,+sse4_2,+popcnt,+sse4a,+bmi1,+bmi2'/> -->
</qemu:commandline>
</domain>