#!/bin/sh # vmix: automated macOS install. Runs inside macOS Recovery's Terminal, started # by vm-driver.py which types "sh /Volumes/VMIX/run.sh" for us. # # 1. erase the target disk (found by size) as APFS "Macintosh HD" # 2. rebuild "Install macOS .app": app skeleton from installer-app.tar # (host-extracted Payload) + SharedSupport.dmg = the WHOLE InstallAssistant.pkg # dd'd byte-exact from a raw disk (Apple's own postinstall hardlinks the pkg # there: it is a "pkgdmg" whose koly footer points at the dmg inside; the bare # xar member fails startosinstall with "pkgdmg is missing a footer") # 3. startosinstall unattended, with the vmix agent pkg as --installpackage # 4. startosinstall reboots itself into the install phase; the vmix agent pkg # installs during that phase and runs on the installed system's first boot # # On first boot of the installed system the agent runs /Volumes/VMIX/vmix-run.sh # and powers off, which ends the QEMU session on the host. # macOS Recovery invokes us as `sh` (bash in POSIX mode, no process substitution); # re-exec once under bash so `>(tee ...)` and other bashisms work. if [ -z "${VMIX_REEXEC:-}" ]; then VMIX_REEXEC=1 exec bash "$0" "$@"; fi V="/Volumes/VMIX" # tee to the Terminal (visible in host screenshots) and to a log on the volume exec > >(tee "$V/install.log") 2>&1 set -x . "$V/vmix.conf" # keep the recovery display awake so the host driver can watch the screen caffeinate -dimsu -t 86400 >/dev/null 2>&1 & pmset -a displaysleep 0 sleep 0 >/dev/null 2>&1 || true fail() { echo "vmix-install: FAIL: $*" cp /var/log/install.log "$V/system-install.log" 2>/dev/null || true echo 1 >"$V/install.status" sync sleep 2 shutdown -h now 2>/dev/null || halt 2>/dev/null || true exit 1 } # whole-disk identifier (diskN) whose size in bytes is exactly $1 disk_by_size() { for d in $(diskutil list | grep -oE '^/dev/disk[0-9]+'); do s=$(diskutil info "$d" | sed -n 's/.*Disk Size:.*(\([0-9][0-9]*\) Bytes).*/\1/p') [ "$s" = "$1" ] && { echo "${d#/dev/}"; return 0; } done return 1 } echo "vmix-install: $(date) app=$APP_NAME volume=$VOLUME_NAME" TARGET=$(disk_by_size "$TARGET_BYTES") || fail "target disk ($TARGET_BYTES bytes) not found" SSDISK=$(disk_by_size "$PKG_DISK_BYTES") || fail "installer pkg disk ($PKG_DISK_BYTES bytes) not found" echo "vmix-install: target=$TARGET sharedsupport=$SSDISK" # --- 1. erase the target disk as an APFS volume diskutil eraseDisk APFS "$VOLUME_NAME" GPT "$TARGET" || fail "eraseDisk $TARGET" VOL="/Volumes/$VOLUME_NAME" [ -d "$VOL" ] || fail "$VOL not mounted" # --- 2. rebuild the installer app on the target volume tar -xf "$V/installer-app.tar" -C "$VOL" || fail "untar installer-app.tar" APP="$VOL/$APP_NAME" SOI="$APP/Contents/Resources/startosinstall" [ -x "$SOI" ] || fail "startosinstall missing in $APP" SS="$APP/Contents/SharedSupport/SharedSupport.dmg" mkdir -p "$APP/Contents/SharedSupport" FULL=$((PKG_BYTES / 1048576)) REM=$((PKG_BYTES % 1048576)) dd if="/dev/r$SSDISK" of="$SS" bs=1048576 count=$FULL || fail "dd SharedSupport.dmg" if [ "$REM" -gt 0 ]; then dd if="/dev/r$SSDISK" bs=1048576 skip=$FULL count=1 2>/dev/null | dd bs=1 count=$REM >>"$SS" || fail "dd SharedSupport.dmg tail" fi [ "$(stat -f %z "$SS")" = "$PKG_BYTES" ] || fail "SharedSupport.dmg size mismatch: $(stat -f %z "$SS") != $PKG_BYTES" tail -c 512 "$SS" | grep -qa koly || fail "SharedSupport.dmg has no UDIF koly footer" chflags -h norestricted "$SS" 2>/dev/null || true echo "vmix-install: app=$APP SharedSupport.dmg=$(stat -f %z "$SS") bytes" # macOS certificate validation needs a sane clock; a fresh VM RTC can be wrong. echo "vmix-install: guest clock is $(date) (UTC $(date -u))" if [ -n "${BUILD_DATE:-}" ]; then date -u "$BUILD_DATE" && echo "vmix-install: set clock to $(date)" fi # Blackhole Apple's install/verify endpoints so osinstallersetupd's network calls # fail immediately instead of timing out (prepare otherwise crawls). Fully offline. for d in swscan.apple.com swcdn.apple.com swdist.apple.com swquery.apple.com \ gs.apple.com gsa.apple.com gdmf.apple.com mesu.apple.com xp.apple.com \ albert.apple.com captive.apple.com deviceservices-external.apple.com \ identity.apple.com ppq.apple.com crl.apple.com ocsp.apple.com \ ocsp2.apple.com valid.apple.com; do echo "127.0.0.1 $d" >> /etc/hosts done echo "vmix-install: blackholed Apple install endpoints for a fast offline prepare" # --- 3. unattended install. startosinstall prepares then reboots the machine # itself into the install phase. Prepare intermittently stalls (~46% — an online # verify/personalization step through the VM's NAT), so a watchdog kills and # retries startosinstall if the target volume makes no write progress for a while. # The vmix agent pkg installs during the install phase and runs on first boot. # quote args properly — $VOL contains a space ("Macintosh HD") run_soi() { "$SOI" --volume "$VOL" --agreetolicense --nointeraction --rebootdelay 5 "$@"; } free_kb() { df -k "$VOL" 2>/dev/null | awk 'NR==2 {print $4}'; } attempt=0 while [ "$attempt" -lt 10 ]; do attempt=$((attempt + 1)) echo "vmix-install: startosinstall attempt $attempt" if [ "$attempt" -eq 1 ]; then run_soi --installpackage "$V/vmix-agent.pkg" 2>&1 & else # a stalled attempt leaves the volume dirty; re-erase and rebuild for a clean retry diskutil eraseDisk APFS "$VOLUME_NAME" GPT "$TARGET" || fail "eraseDisk on retry" tar -xf "$V/installer-app.tar" -C "$VOL" || fail "untar on retry" mkdir -p "$APP/Contents/SharedSupport" dd if="/dev/r$SSDISK" of="$SS" bs=1048576 count=$FULL 2>/dev/null [ "$REM" -gt 0 ] && dd if="/dev/r$SSDISK" bs=1048576 skip=$FULL count=1 2>/dev/null | dd bs=1 count=$REM >>"$SS" 2>/dev/null chflags -h norestricted "$SS" 2>/dev/null || true run_soi --installpackage "$V/vmix-agent.pkg" 2>&1 & fi SOI_PID=$! # watchdog: kill startosinstall if free space stalls for ~4 min OR the attempt # simply takes too long (prepare is intermittently slow; healthy = a few minutes) last=$(free_kb); stalled=0; elapsed=0 while kill -0 "$SOI_PID" 2>/dev/null; do sleep 30; elapsed=$((elapsed + 30)) now=$(free_kb) if [ "$now" = "$last" ]; then stalled=$((stalled + 30)); else stalled=0; last=$now; fi if [ "$stalled" -ge 240 ] || [ "$elapsed" -ge 540 ]; then echo "vmix-install: prepare too slow (stalled=${stalled}s elapsed=${elapsed}s), killing to retry" kill -9 "$SOI_PID" 2>/dev/null; pkill -9 -f startosinstall 2>/dev/null break fi done wait "$SOI_PID" 2>/dev/null # on success startosinstall reboots the machine and we never get here echo "vmix-install: startosinstall attempt $attempt ended without rebooting" sleep 3 done fail "startosinstall did not complete after $attempt attempts"