Skip to content

Setup Assistant and MDM Bootstrap

Rod Christiansen edited this page Sep 3, 2026 · 1 revision

Setup Assistant and MDM Bootstrap

BootstrapMate's first phase runs inside a window most provisioning tools never see: after Remote Management enrollment, while Setup Assistant is still on screen and before anybody has logged in. This page describes what that environment actually offers and what falls out of it.

What Setup Assistant is

Setup Assistant is the first-boot experience a Mac shows after macOS installs or after an erase: language, region, network, then — on a Mac enrolled in Automated Device Enrollment (ADE) — the Remote Management screen, where the device contacts your MDM, receives its enrollment profile, and installs whatever the MDM has staged for enrollment. Only after that does Setup Assistant continue to account creation and the first user login.

Automated Device Enrollment means the device is claimed by your organisation in Apple's provisioning records, so the Remote Management screen appears automatically and the enrollment profile is applied without anybody typing an enrollment URL. Practically, it means your bootstrap package and your configuration profile are installed at a known point, early, unattended, and before a user exists.

That is where BootstrapMate's LaunchDaemon fires. RunAtLoad starts the CLI as root the moment the postinstall loads the daemon.

What exists on the device at that moment

  • Root. The daemon runs as root. Anything that needs elevation works.
  • The system volume, and usually the data volume. /Library may not be writable yet immediately after enrollment.
  • A configuration profile — eventually. Profile delivery and package delivery are not synchronised. The package can be running before the profile carrying com.github.bootstrapmate has landed.
  • Networking — eventually. The Mac had network in Setup Assistant to reach the MDM, but the interface state at daemon start is not guaranteed.

What does not exist:

  • No user. No console user, no login session, no home directory, no user defaults domain, no Dock, no Finder. SCDynamicStoreCopyConsoleUser returns either nothing or one of the system placeholders (loginwindow, _mbsetupuser, root).
  • No user-context anything. There is no launchctl asuser target, so nothing can be run as "the user" because there is no user.
  • A read-only system temp directory. BootstrapMate downloads into memory and writes payloads directly to each item's file path for exactly this reason.

What BootstrapMate does about it

Three waits at the top of every run absorb this timing, and none of them is fatal. Each logs and continues:

  1. Network. A path monitor waits up to --network-timeout seconds (default 120) for a satisfied path. On timeout it does one synchronous DNS resolution as a last check, then logs either Network is available or Network check timed out - proceeding anyway.
  2. Data volume. If /Library/Managed Bootstrap is not writable, it polls /Library once a second for up to 30 seconds, logging Data volume is now writable after Ns or Data volume still not writable after 30s - proceeding anyway.
  3. Management profile. Only when no --jsonurl was supplied and the configuration is not yet usable, it re-reads managed preferences once a second for up to 300 seconds, logging Still waiting for management config... (Ns elapsed) every 30 seconds. On expiry it logs Management configuration not received within 300s and continues — and then, with no manifest URL, exits 1.

Practical constraints

  • Nothing in preflight or setupassistant may assume a user. No ~, no user defaults, no GUI, no Dock manipulation. Work that needs a user belongs in userland.
  • The userland stage blocks until somebody logs in. BootstrapMate polls every two seconds for a console user that is not loginwindow, _mbsetupuser, root, and does not begin with an underscore. There is no timeout on this wait — on a Mac that is never logged into, the run sits there. See Troubleshooting and Gotchas.
  • userscript items run as root in the daemon context, not as the console user. Do not write a userland script that expects to be the user; derive the console user inside the script instead. This is covered in Troubleshooting and Gotchas.
  • A failed setupassistant item does not abort the run. Failures are recorded, and the remaining items still run, because a single bad package must not strand provisioning.
  • preflight is the escape hatch. Exactly one rootscript is honoured there. Exit 0 and the entire bootstrap is skipped and the daemon torn down; exit greater than zero and the run continues.
  • The device may reboot out from under you. There is no resume state. See Stages for what each phase is for and what re-running costs.

The Windows equivalent

On Windows the analogous window is the Out-of-Box Experience (OOBE) with the Autopilot Enrollment Status Page (ESP) on screen: same shape — device-context work, no user, MDM delivering payloads before anyone signs in — with different mechanics and, importantly, an ESP that will time out and show the user a failure, where Setup Assistant will not.

The sibling tool's page covers it: https://github.com/bootstrapmate/bootstrapmate-windows/wiki/Autopilot-and-the-ESP

See also

Clone this wiki locally