# Customize a macOS image by booting it with a VMIX volume: the vmix agent # (LaunchDaemon installed by makeImage) runs `script` as root, records the exit # status on the volume and powers off. Optionally re-installs OpenCore with a new # SMBIOS identity (`smbios`). Counterpart of the Windows auditScript flow. # # Templates provide: # script — sh script run as root on the booted system # files — [{ source; name; }] extra files placed next to it on /Volumes/VMIX # smbios — { model? serial? mlb? uuid? mac? seed? } → fresh OpenCore config in the ESP { pkgs, lib, qemu, ident, makeVmixVolume, makeOpenCore, installBootloader, vmixReadback, vmDriver, ... }: originalImage: { name ? "", script ? "", files ? [], smbios ? null, diskSize ? "", impure ? true, vncDisplay ? null, smp ? 4, memSize ? 4096, cpu ? qemu.defaultCpu, timeout ? 3600, }: let originalImageName = lib.strings.removeSuffix "-vmix" (lib.strings.removeSuffix ".qcow2" originalImage.name); customImageName = (if name != "" then name else "custom") + "-${originalImageName}-vmix.qcow2"; resultImg = "./disk.qcow2"; hasScript = script != ""; hasSmbios = smbios != null; model = originalImage.model or "MacPro7,1"; seed = if hasSmbios && (smbios.seed or null) != null then smbios.seed else null; mac = if !hasSmbios then originalImage.macAddress else if (smbios.mac or null) != null then smbios.mac else if seed != null then ident.macFromSeed seed else originalImage.macAddress; uuid = if !hasSmbios then null else if (smbios.uuid or null) != null then smbios.uuid else if seed != null then ident.uuidFromSeed seed else originalImage.opencore.uuid; esp = if hasSmbios then makeOpenCore ({ name = "${name}-${originalImageName}-opencore"; model = smbios.model or model; inherit mac uuid; } // builtins.removeAttrs smbios [ "seed" "mac" "uuid" "model" ]) else originalImage.opencore; runScript = pkgs.writeText "${name}-vmix-run.sh" '' #!/bin/sh echo "=== vmix: ${name} ===" ${script} ''; vmixVol = makeVmixVolume { name = "${name}-${originalImageName}"; files = [ { source = runScript; name = "vmix-run.sh"; } ] ++ files; }; driverPython = pkgs.python3.withPackages (p: [ p.pillow ]); bootCommands = lib.optionalString hasScript '' cp ${vmixVol} vmix.img chmod +w vmix.img cp ${pkgs.OVMF.fd}/FV/OVMF_VARS.fd vars.fd chmod +w vars.fd VMIX_DISPLAY="-display none" ${lib.optionalString (vncDisplay != null) ''VMIX_DISPLAY="-display none -vnc ${vncDisplay}"''} ${lib.optionalString (vncDisplay == null) '' VMIX_DF=$(ls -t /tmp/.vmix-display-* 2>/dev/null | head -1) if [ -n "$VMIX_DF" ] && [ "$(stat -c %s "$VMIX_DF")" -lt 256 ] && ! grep -q -P '[^\x20-\x7e\n]' "$VMIX_DF"; then export DISPLAY=$(tr -d '\n' < "$VMIX_DF") export HOME=$(mktemp -d) export XDG_RUNTIME_DIR=$HOME export SDL_VIDEODRIVER=x11 VMIX_DISPLAY="-display sdl" fi ''} echo "=== vmix: booting ${originalImageName} for ${name} ===" python3 ${vmDriver} --mode boot --name "${name}-${originalImageName}" --timeout ${toString timeout} --progress-file ${resultImg} -- \ qemu-system-x86_64 $VMIX_DISPLAY \ ${qemu.machineArgs { inherit cpu smp memSize; }} \ ${qemu.firmwareArgs "vars.fd"} \ ${qemu.sataDrive { id = "system"; port = 0; file = resultImg; }} \ ${qemu.sataDrive { id = "vmix"; port = 1; file = "vmix.img"; format = "raw"; }} \ ${qemu.netArgs { mac = originalImage.macAddress; }} \ || { echo "vmix: VM failed during ${name} (see /tmp/vmix-macos/${name}-${originalImageName})"; exit 1; } ${vmixReadback "vmix.img"} [ "$STATUS" = "0" ] || { echo "vmix: ${name} script failed (status '$STATUS')"; exit 1; } echo "=== vmix: ${name} complete ===" ''; builtImage = pkgs.runCommand customImageName ({ nativeBuildInputs = with pkgs; [ pkgs.qemu mtools driverPython libguestfs-with-appliance ]; requiredSystemFeatures = [ "kvm" ]; } // lib.optionalAttrs impure { __noChroot = true; }) '' qemu-img create -q -f qcow2 -b ${originalImage} -F qcow2 ${resultImg} [ -n "${diskSize}" ] && qemu-img resize ${resultImg} ${diskSize} ${bootCommands} ${lib.optionalString hasSmbios (installBootloader { inherit esp; image = resultImg; })} mv ${resultImg} $out ''; in builtImage // { _vmixOsType = "macos"; macAddress = mac; opencore = esp; model = esp.model or model; }