# Generalize a macOS image: create the user, auto-login, hostname, timezone, # suppress Setup Assistant prompts, then remove the vmix agent. Also gives the # image a fresh SMBIOS identity (serial/MLB from macserial, MAC + UUID from # `seed`) so every generalized VM looks like a distinct Mac to Apple ID/iMessage. # Usage: (templates.generalize { username = "User"; password = ""; hostname = "MAC"; }) # delayOobeRun = true: no user, Setup Assistant runs on first real boot (like Windows OOBE) { pkgs, lib, ... }: { username ? "User", password ? "", fullName ? username, autoLogon ? true, hostname ? "MAC-VM", locale ? "en-US", timezone ? "UTC", delayOobeRun ? false, # SMBIOS identity; anything unset is generated model ? null, serial ? null, mlb ? null, uuid ? null, mac ? null, seed ? "${hostname}-${username}", # accepted for CLI parity with Windows, not supported on macOS bgColor ? null, }: let kcpasswordFile = pkgs.runCommand "kcpassword" { nativeBuildInputs = [ pkgs.python3 ]; } '' python3 ${../guest/kcpassword.py} ${lib.escapeShellArg password} > $out ''; macLocale = builtins.replaceStrings [ "-" ] [ "_" ] locale; tempPassword = "vmix-temp-password"; setupKeys = [ "DidSeeCloudSetup" "DidSeeSiriSetup" "DidSeePrivacy" "DidSeeTouchIDSetup" "DidSeeAppearanceSetup" "DidSeeScreenTime" "DidSeeAccessibility" "DidSeeTrueTonePrivacy" "DidSeeActivationLock" "DidSeeiCloudLoginForStorageServices" "DidSeeSyncSetup" "DidSeeSyncSetup2" "DidSeeAppleIDSyncSetup" "DidSeeApplePaySetup" "DidSeeIntelligence" "DidSeeLockdownMode" "DidSeeAppStore" "SkipFirstLoginOptimization" ]; in { name = if delayOobeRun then "generalize-delay-oobe" else "generalize"; files = [ { source = kcpasswordFile; name = "kcpassword"; } ]; smbios = { inherit seed; } // lib.filterAttrs (_: v: v != null) { inherit model serial mlb uuid mac; }; script = '' set -x ${lib.optionalString (bgColor != null) ''echo "vmix: bgColor is not supported on macOS, ignoring"''} ${lib.optionalString (!delayOobeRun) '' # --- user account (admin) if ! id "${username}" >/dev/null 2>&1; then sysadminctl -addUser "${username}" -fullName ${lib.escapeShellArg fullName} \ -password ${lib.escapeShellArg (if password == "" then tempPassword else password)} \ -admin -home "/Users/${username}" || exit 1 ${lib.optionalString (password == "") '' dscl . -passwd "/Users/${username}" "${tempPassword}" "" || echo "vmix: WARNING: could not set an empty password, password is '${tempPassword}'" ''} fi ${lib.optionalString autoLogon '' defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser "${username}" cp /Volumes/VMIX/kcpassword /etc/kcpassword chmod 600 /etc/kcpassword chown root:wheel /etc/kcpassword ''} # --- no Setup Assistant / "What's new" prompts at first login P="/Users/${username}/Library/Preferences/com.apple.SetupAssistant" VER=$(sw_vers -productVersion) BUILD=$(sw_vers -buildVersion) for k in ${lib.concatStringsSep " " setupKeys}; do defaults write "$P" "$k" -bool true done defaults write "$P" GestureMovieSeen none defaults write "$P" LastSeenCloudProductVersion "$VER" defaults write "$P" LastSeenBuddyBuildVersion "$BUILD" defaults write "$P" LastSeenSiriProductVersion "$VER" defaults write "$P" LastPreLoginTasksPerformedVersion "$VER" defaults write "/Users/${username}/Library/Preferences/.GlobalPreferences" AppleLocale "${macLocale}" chown -R "${username}" "/Users/${username}/Library/Preferences" ''} # --- machine identity scutil --set ComputerName "${hostname}" scutil --set HostName "${hostname}" scutil --set LocalHostName "${hostname}" defaults write /Library/Preferences/.GlobalPreferences AppleLocale "${macLocale}" systemsetup -settimezone "${timezone}" >/dev/null 2>&1 || ln -sfn "/usr/share/zoneinfo/${timezone}" /etc/localtime # --- never sleep (VM) pmset -a sleep 0 displaysleep 0 disksleep 0 hibernatemode 0 || true # --- use the whole (possibly grown) disk STORE=$(diskutil info / | awk '/APFS Physical Store/ {print $NF}') [ -n "$STORE" ] && diskutil apfs resizeContainer "$STORE" 0 || true ${lib.optionalString delayOobeRun '' # Setup Assistant will run on the next boot rm -f /var/db/.AppleSetupDone ''} # --- the agent's job is done: remove it (this is the last vmix step) rm -f /Library/LaunchDaemons/ch.vmix.agent.plist rm -rf /Library/vmix ''; }