macOS Tahoe: working fully-offline unattended install
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
This commit is contained in:
parent
242e48a5fc
commit
58a317f5d2
9 changed files with 415 additions and 128 deletions
|
|
@ -1,6 +1,11 @@
|
|||
# Distribution-style flat package (xar + bom + cpio, built on Linux) for
|
||||
# `startosinstall --installpackage`: installs the vmix agent LaunchDaemon and
|
||||
# marks Setup Assistant as done so the first boot lands on loginwindow.
|
||||
# `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
|
||||
|
|
@ -9,36 +14,61 @@ let
|
|||
bomutils = pkgs.bomutils.overrideAttrs (_: { hardeningDisable = [ "fortify" ]; });
|
||||
postinstall = pkgs.writeText "postinstall" ''
|
||||
#!/bin/sh
|
||||
# $3 = target volume
|
||||
T="$3"
|
||||
mkdir -p "$T/private/var/db"
|
||||
# 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"
|
||||
chmod 755 "$T/Library/vmix/agent.sh"
|
||||
chown -R root:wheel "$T/Library/vmix" "$T/Library/LaunchDaemons/${id}.plist"
|
||||
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.gzip ];
|
||||
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
|
||||
chmod 755 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)
|
||||
(cd root && find . | cpio -o --format odc --owner 0:0 --quiet | gzip -c > ../flat/vmix-agent.pkg/Payload)
|
||||
# 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">
|
||||
<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/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue