vmix.nix/lib/images/macos/helpers/fetchRecovery.nix
Git Sagar 242e48a5fc macOS Tahoe VM images (OpenCore/QEMU), Apple-ID compatible, VNC
Add a macOS image pipeline mirroring the Windows one: unattended install,
generalization and user creation, driven end to end in QEMU on a KVM host.

lib/images/macos:
- makeOpenCore: OSX-KVM OpenCore ESP with a config.plist rewritten per image —
  SMBIOS model + serial/MLB (macserial) + UUID + ROM=en0 MAC (built-in NIC pinned
  to PciRoot(0x0)/Pci(0x12,0x0)) for Apple ID / iMessage / App Store; boot disk;
  OpenCore self-entry hidden. ident.nix derives MAC+UUID from a seed so the NixOS
  module and CLI know the NIC MAC at eval time.
- makeImage: one QEMU session driven by vm-driver.py (QMP + screenshot settle
  detection + OCR of the menu bar) — boots the recovery via OpenCore, opens
  Terminal (Ctrl-F2 -> Utilities -> Terminal), types the bootstrap command;
  vmix-install.sh erases the disk as APFS, lays down the host-extracted installer
  app skeleton + a byte-exact SharedSupport.dmg (raw disk mapped to that byte range
  of the pkg, dd'd in — Recovery's xar truncates an 18 GB member), runs
  startosinstall with the vmix agent pkg. OpenCore is then copied into the image's
  ESP so it boots standalone with OVMF.
- customizeImage / templates: boot the image with a FAT-then-HFS+ VMIX volume; the
  vmix agent (LaunchDaemon) runs a script as root, records status and powers off —
  the macOS counterpart of Windows Audit Mode. generalize creates the admin user +
  auto-login (kcpassword), suppresses Setup Assistant, sets hostname/timezone,
  grows APFS, and assigns a fresh SMBIOS identity. Templates: noUpdates,
  performance, remoteAccess (ssh + screen sharing).
- fetchRecovery: Apple recovery BaseSystem, retried until the pinned Tahoe build
  (osrecovery load-balances Sequoia/Tahoe during the rollout). makeAgentPkg builds
  a distribution flat pkg on Linux (xar+bom+cpio) for startosinstall --installpackage.

CLI: vmix build/copy/run for macOS (run --macos --vnc, reads the image's MAC from
its ESP), and a `vmix macserial` helper. NixOS module: disks.os.file carrying
_vmixOsType="macos" auto-enables the macOS QEMU profile (AppleSMC+OSK, Skylake
CPU spoof, AHCI system disk, VMware SVGA, pinned NIC); macos.{enable,cpu,mac}.

Status: proven through the installer prepare phase (SharedSupport.dmg mounts,
version 26.6.2 read, SU catalog loads). Two blockers remain, documented in
lib/images/macos/README.md: (1) startosinstall's OSISVerifyBaseSystemOperation
rejects the byte-identical plain-UDIF SharedSupport as "pkgdmg missing a footer"
in this Tahoe recovery/VM; (2) Apple's CDN unreliably serves the Tahoe recovery
during rollout (self-hosting the verified BaseSystem is the robust fix).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsESshRCoBoUVWV9qKURUF
2026-09-10 13:33:38 -03:00

40 lines
2 KiB
Nix

# macOS Recovery BaseSystem.dmg from Apple's recovery servers (osrecovery.apple.com)
# via OSX-KVM's fetch-macOS-v2.py. The server has no stable URL (session-based) and,
# during a macOS rollout, "latest" is load-balanced across CDN nodes serving DIFFERENT
# builds (e.g. Sequoia and Tahoe at once). So the download is non-deterministic: the
# builder retries until it gets the exact build pinned by sha256. The recovery MUST
# match the installer's major version or startosinstall rejects it as "damaged".
# When Apple retires this build, update recovery.sha256 (download once, check
# /System/Library/CoreServices/SystemVersion.plist reports the wanted version).
{ pkgs, upstream, ... }:
{ shortname, sha256 }:
let
script = pkgs.fetchurl { inherit (upstream.opencore.fetchRecoveryScript) url sha256; };
in
pkgs.runCommand "macos-${shortname}-BaseSystem.dmg" {
nativeBuildInputs = [ pkgs.python3 pkgs.coreutils ];
outputHashMode = "flat";
outputHashAlgo = "sha256";
outputHash = sha256;
SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
expected = sha256;
} ''
cp ${script} fetch-macOS-v2.py
sed -i 's/os.get_terminal_size().columns/80/' fetch-macOS-v2.py
want=$(nix-hash --type sha256 --to-base16 "$expected" 2>/dev/null || echo "$expected")
for attempt in $(seq 1 40); do
rm -f BaseSystem.dmg BaseSystem.chunklist
echo "=== vmix: fetching ${shortname} recovery (attempt $attempt) ==="
python3 fetch-macOS-v2.py --action download -s ${shortname} -o . -n BaseSystem || true
if [ -f BaseSystem.dmg ]; then
got=$(sha256sum BaseSystem.dmg | cut -d' ' -f1)
echo "got $got (want $want)"
[ "$got" = "$want" ] && { mv BaseSystem.dmg $out; exit 0; }
echo "=== vmix: wrong build (Apple is rotating builds during rollout), retrying ==="
fi
sleep 5
done
echo "vmix: could not fetch the pinned ${shortname} recovery after 40 attempts."
echo "Apple may have retired build $want; download it manually and update recovery.sha256."
exit 1
''