The base image (macos.images.tahoe.upstream) now installs and boots end to end with no dependency on Apple's servers — proven on the KVM host: the finished qcow2 boots standalone (OpenCore from its own ESP) to the macOS 26.6.2 loginwindow. Install driving (vm-driver.py, screenshot + OCR over QMP): - map the whole InstallAssistant.pkg as a raw disk and dd it byte-exact into the app as SharedSupport.dmg (it is a pkgdmg: xar + koly footer — the bare xar member fails startosinstall with "pkgdmg is missing a footer") - offline install: no NIC + /etc/hosts blackhole of Apple install/verify endpoints so startosinstall's calls fail fast instead of hanging (SecureBootModel=Disabled allows the sealed-volume install offline) - keep the recovery display awake with a tiny mouse jiggle; coarse settle fingerprint so the cursor is not seen as a change - guest watchdog re-erases/retries a startosinstall attempt that stalls or runs too long (prepare is intermittently slow) - disk-aware boot watchdog: QMP system_reset only when the screen is dark AND the disk is idle (never interrupts a slow-but-working boot); recovery-restart if a post-prepare reboot lands back on recovery - detect the bright loginwindow and power the VM down (install complete); a black + disk-idle screen is treated as a completed halt - copy OpenCore into the image ESP so it boots standalone recovery.file pins a content-addressed local BaseSystem.dmg (Apple's CDN load-balances Sequoia/Tahoe during the rollout). fetchRecovery retries to the pinned hash when used instead. Not yet done: .generalize (user creation) — the first-boot agent LaunchDaemon is blocked by Ventura+ Background Task Management on headless boots; next step is offline user injection from the agent pkg postinstall. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XsESshRCoBoUVWV9qKURUF
105 lines
5 KiB
Nix
105 lines
5 KiB
Nix
# Distribution-style flat package (xar + bom + cpio, built on Linux) for
|
|
# `startosinstall --installpackage`. macOS installs it during the first boot of the
|
|
# installed system (bootinstalld, "Installer Progress"): it places the vmix agent
|
|
# LaunchDaemon, marks Setup Assistant as done, starts the agent, and schedules a
|
|
# reboot as a fallback so the daemon runs even if bootstrapping failed.
|
|
#
|
|
# The files are shipped inside Scripts and copied by postinstall: installd unpacks
|
|
# our Scripts archive fine, but "shoves 0 items" from a Linux-made Payload.
|
|
{ pkgs, lib, ... }:
|
|
{ version ? "1.0" }:
|
|
let
|
|
id = "ch.vmix.agent";
|
|
# nixpkgs' bomutils aborts under _FORTIFY_SOURCE
|
|
bomutils = pkgs.bomutils.overrideAttrs (_: { hardeningDisable = [ "fortify" ]; });
|
|
postinstall = pkgs.writeText "postinstall" ''
|
|
#!/bin/sh
|
|
# Runs during the OS install (bootinstalld) with $3 = the target system root.
|
|
# Only place files; the ch.vmix.agent LaunchDaemon then runs on the installed
|
|
# system's first boot via RunAtLoad (confirmed loading on Tahoe).
|
|
T="''${3%/}"
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
LOG="$T/private/var/log/vmix-agent-install.log"
|
|
mkdir -p "$T/private/var/log"
|
|
exec >>"$LOG" 2>&1
|
|
echo "=== vmix agent pkg postinstall $(date) target=[$3] ==="
|
|
mkdir -p "$T/Library/LaunchDaemons" "$T/Library/vmix" "$T/private/var/db"
|
|
cp "$HERE/agent.sh" "$T/Library/vmix/agent.sh"
|
|
cp "$HERE/${id}.plist" "$T/Library/LaunchDaemons/${id}.plist"
|
|
chmod 755 "$T/Library/vmix/agent.sh"
|
|
chmod 644 "$T/Library/LaunchDaemons/${id}.plist"
|
|
chown -R root:wheel "$T/Library/vmix" "$T/Library/LaunchDaemons/${id}.plist"
|
|
touch "$T/private/var/db/.AppleSetupDone"
|
|
chown root:wheel "$T/private/var/db/.AppleSetupDone"
|
|
ls -la "$T/Library/vmix/agent.sh" "$T/Library/LaunchDaemons/${id}.plist"
|
|
# A pkg LaunchDaemon is registered with Background Task Management but stays
|
|
# pending approval, so it will not auto-run headless. Two BTM-exempt triggers:
|
|
# - bootstrap it now (starts it in the installer env; the agent no-ops there)
|
|
# - a root cron @reboot job (Apple's cron daemon is trusted, runs it at boot)
|
|
launchctl bootstrap system "$T/Library/LaunchDaemons/${id}.plist" 2>&1 && echo "bootstrapped" || echo "bootstrap returned $?"
|
|
mkdir -p "$T/usr/lib/cron/tabs"
|
|
printf '@reboot /bin/sh /Library/vmix/agent.sh\n' > "$T/usr/lib/cron/tabs/root"
|
|
chmod 600 "$T/usr/lib/cron/tabs/root"
|
|
chown root:wheel "$T/usr/lib/cron/tabs/root"
|
|
echo "cron @reboot installed"
|
|
exit 0
|
|
'';
|
|
in
|
|
pkgs.runCommand "vmix-agent-${version}.pkg" {
|
|
nativeBuildInputs = [ pkgs.xar bomutils pkgs.cpio pkgs.libarchive pkgs.gzip ];
|
|
} ''
|
|
mkdir -p root/Library/LaunchDaemons root/Library/vmix scripts flat/vmix-agent.pkg
|
|
cp ${../guest/agent.sh} root/Library/vmix/agent.sh
|
|
cp ${../guest/ch.vmix.agent.plist} root/Library/LaunchDaemons/${id}.plist
|
|
chmod 755 root/Library/vmix/agent.sh
|
|
chmod 644 root/Library/LaunchDaemons/${id}.plist
|
|
# the same files ride along in Scripts, which is what postinstall installs from
|
|
cp ${postinstall} scripts/postinstall
|
|
cp ${../guest/agent.sh} scripts/agent.sh
|
|
cp ${../guest/ch.vmix.agent.plist} scripts/${id}.plist
|
|
chmod 755 scripts/postinstall scripts/agent.sh
|
|
|
|
NFILES=$(find root | wc -l)
|
|
KBYTES=$(du -sk root | cut -f1)
|
|
# bsdcpio keeps the "./" prefix the Bom uses (GNU cpio strips it and installd then extracts nothing)
|
|
(cd root && find . | bsdcpio -o --format odc --quiet | gzip -c > ../flat/vmix-agent.pkg/Payload)
|
|
(cd scripts && find . | cpio -o --format odc --owner 0:0 --quiet | gzip -c > ../flat/vmix-agent.pkg/Scripts)
|
|
mkbom -u 0 -g 80 root flat/vmix-agent.pkg/Bom
|
|
|
|
cat > flat/vmix-agent.pkg/PackageInfo <<XML
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<pkg-info overwrite-permissions="true" relocatable="false" identifier="${id}" postinstall-action="none" version="${version}" format-version="2" generated-by="vmix" auth="root" install-location="/">
|
|
<payload installKBytes="$KBYTES" numberOfFiles="$NFILES"/>
|
|
<bundle-version/>
|
|
<upgrade-bundle/>
|
|
<update-bundle/>
|
|
<atomic-update-bundle/>
|
|
<strict-identifier/>
|
|
<relocate/>
|
|
<scripts>
|
|
<postinstall file="./postinstall"/>
|
|
</scripts>
|
|
</pkg-info>
|
|
XML
|
|
cat > flat/Distribution <<XML
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<installer-gui-script minSpecVersion="1">
|
|
<title>vmix agent</title>
|
|
<options customize="never" require-scripts="false" hostArchitectures="x86_64,arm64" rootVolumeOnly="true"/>
|
|
<product id="${id}" version="${version}"/>
|
|
<choices-outline>
|
|
<line choice="default">
|
|
<line choice="${id}"/>
|
|
</line>
|
|
</choices-outline>
|
|
<choice id="default"/>
|
|
<choice id="${id}" visible="false">
|
|
<pkg-ref id="${id}"/>
|
|
</choice>
|
|
<pkg-ref id="${id}" version="${version}" onConclusion="none" installKBytes="$KBYTES">#vmix-agent.pkg</pkg-ref>
|
|
</installer-gui-script>
|
|
XML
|
|
sed -i 's/^ //' flat/vmix-agent.pkg/PackageInfo flat/Distribution
|
|
(cd flat && xar --compression none -cf $out Distribution vmix-agent.pkg)
|
|
xar -t -f $out
|
|
''
|