macOS: drive the install and all customization from a Recovery "PE", no GUI
Replace the screenshot/OCR/keystroke driving of Apple's Recovery with a "PE": BaseSystem.dmg (a journaled HFS+ volume, writable from Linux) with one LaunchDaemon added (makeRecoveryPE) that runs /Volumes/VMIX/run.sh as root at boot, records the status and powers off. launchd loads it alongside its signed cache (verified on Tahoe 26.6.2); same idea as AutoNBI/Imagr NetBoot images. - makeImage: the PE runs vmix-install.sh (erase, installer app, SharedSupport pkgdmg, startosinstall). Progress is read from the serial console (boot-args serial=3 -v, VMIX-* markers) and screenshots (brightness only). Fully offline; prepare now takes ~5 min instead of ~10. - customizeImage: boots the PE with the image attached and runs the template offline against the mounted System/Data volumes; OpenCore ScanPolicy restricted to HFS+/SATA so only the PE can boot. One PE boot ~30 s. The installed macOS is never booted for customization, so nothing depends on launchd/BTM approval or a first-boot agent (removed). - templates rewritten for offline use: generalize creates the user with dscl -f (admin, home, auto-login kcpassword, Setup Assistant suppression, hostname, locale, timezone, keyboard type, container resize); remote-access, no-updates, performance edit the target's plists. - makeBootDisk: build-time OpenCore variant (serial console, ScanPolicy). - vm-driver.py rewritten: passive observation only (serial markers, kernel boots, panics, brightness), disk+serial-aware hang watchdog, reboot-death reset, halt/loginwindow detection. No OCR/tesseract. - OpenCore: four SMBIOS DIMMs for MacPro7,1 (no "Memory Modules Misconfigured" warning). - tools/soak.sh: repeatability harness. Verified on daku: base install 23 min end to end; basic + generalize in three ~30 s PE boots; the result auto-logs into the desktop with the created user. Root cause of the "first-boot hang" (from the serial log): the guest's restart path panics (IOPlatformHaltRestartAction -> AppleSMC, SMCWDT smcWriteKey kSMCBadCommand, nested panic) because the pinned OSX-KVM Lilu disables itself on macOS 26, so VirtualSMC never loads. Handled by the driver (reset within 60 s); kext update to follow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XsESshRCoBoUVWV9qKURUF
This commit is contained in:
parent
58a317f5d2
commit
8dc8f4265d
24 changed files with 802 additions and 977 deletions
51
lib/images/macos/guest/pe-lib.sh
Normal file
51
lib/images/macos/guest/pe-lib.sh
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# vmix PE helpers, sourced by run.sh scripts running in the recovery.
|
||||
# Expects V=/Volumes/VMIX (set by pe.sh) and VOLUME_NAME from vmix.conf.
|
||||
V=${V:-/Volumes/VMIX}
|
||||
[ -f "$V/vmix.conf" ] && . "$V/vmix.conf"
|
||||
VOLUME_NAME=${VOLUME_NAME:-Macintosh HD}
|
||||
|
||||
pe_log() { echo "VMIX: $*"; }
|
||||
pe_fail() { echo "VMIX-FAIL: $*"; exit 1; }
|
||||
|
||||
# Mount the installed system's APFS volume group (System read-only, Data rw) and
|
||||
# export SYS / DATA mount points plus SYS_ID / DATA_ID device identifiers.
|
||||
pe_mount_target() {
|
||||
local list; list=$(diskutil list)
|
||||
DATA_ID=$(echo "$list" | awk -v n="APFS Volume $VOLUME_NAME - Data" 'index($0, n) {print $NF; exit}')
|
||||
SYS_ID=$(echo "$list" | awk -v n="APFS Volume $VOLUME_NAME " '!/ - Data/ && index($0, n) {print $NF; exit}')
|
||||
[ -n "$DATA_ID" ] && [ -n "$SYS_ID" ] || { pe_log "target volumes not found"; echo "$list"; return 1; }
|
||||
diskutil mount "$SYS_ID" >/dev/null 2>&1 || true
|
||||
diskutil mount "$DATA_ID" >/dev/null 2>&1 || true
|
||||
SYS=$(diskutil info "$SYS_ID" | sed -n 's/^ *Mount Point: *//p')
|
||||
DATA=$(diskutil info "$DATA_ID" | sed -n 's/^ *Mount Point: *//p')
|
||||
[ -d "$DATA/private/var/db" ] || { pe_log "Data volume not mounted (SYS=[$SYS] DATA=[$DATA])"; return 1; }
|
||||
pe_log "target mounted: SYS=[$SYS] DATA=[$DATA]"
|
||||
export SYS DATA SYS_ID DATA_ID
|
||||
}
|
||||
|
||||
pe_unmount_target() {
|
||||
sync
|
||||
diskutil unmount "$DATA_ID" >/dev/null 2>&1 || true
|
||||
diskutil unmount "$SYS_ID" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# plist helpers on files of the (offline) target: create the file if missing.
|
||||
pe_plist_set() { # FILE KEYPATH TYPE VALUE (TYPE: string|bool|integer|float)
|
||||
local f=$1 k=$2 t=$3 v=$4
|
||||
[ -f "$f" ] || plutil -create xml1 "$f"
|
||||
plutil -replace "$k" "-$t" "$v" "$f"
|
||||
}
|
||||
pe_plist_dict() { # FILE KEYPATH — make sure a dictionary exists at KEYPATH
|
||||
local f=$1 k=$2
|
||||
[ -f "$f" ] || plutil -create xml1 "$f"
|
||||
plutil -extract "$k" xml1 -o /dev/null "$f" >/dev/null 2>&1 || plutil -insert "$k" -dictionary "$f"
|
||||
}
|
||||
# launchd service override on the target (disabled.plist): pe_service LABEL true|false
|
||||
pe_service_disabled() {
|
||||
local f="$DATA/private/var/db/com.apple.xpc.launchd/disabled.plist"
|
||||
mkdir -p "$(dirname "$f")"
|
||||
pe_plist_set "$f" "$1" bool "$2"
|
||||
}
|
||||
# version of the installed system
|
||||
pe_target_version() { plutil -extract ProductVersion raw -o - "$SYS/System/Library/CoreServices/SystemVersion.plist" 2>/dev/null; }
|
||||
pe_target_build() { plutil -extract ProductBuildVersion raw -o - "$SYS/System/Library/CoreServices/SystemVersion.plist" 2>/dev/null; }
|
||||
Loading…
Add table
Add a link
Reference in a new issue