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

Stages

A manifest is divided into three stages — preflight, setupassistant and userland — and BootstrapMate always runs them in that order within a single process. This page explains what each stage is for, what it can assume about the machine, and what happens when one is missing.

The stage names come from InstallApplications and mean the same things here.

The order is fixed

One run, one process, three stages, in the order the schema lists them. There is no way to reorder them, run one on its own from the manifest, or interleave items across them. Within a stage, items run sequentially in array order.

There is also no resume. Every invocation starts from scratch with a new run id. The only things that carry over between runs are on-disk side effects: a payload already present at its file path with a matching hash is not re-downloaded, and a package whose receipt already satisfies packageid plus version is not reinstalled.

preflight

Runs first, as root, before anything is installed. Its job is to answer one question: does this machine need provisioning at all?

Only the first item with type: rootscript in the array is used. Other items in preflight, including packages, are ignored entirely. The script's exit code drives the run:

  • Exit 0 — the machine is already configured. BootstrapMate skips both remaining stages, marks preflight Completed, tears down its LaunchDaemon and exits 0. The dialog row reads "Already configured".
  • Exit greater than 0 — continue. Logged as Preflight script exited N - continuing with bootstrap, and the dialog row reads "Continue setup".
  • Execution failure — the script could not be downloaded or could not be launched. The stage is marked Failed, setupassistant and userland are both skipped, and the run exits 1.

Note the inversion against normal shell convention: 0 means "stop", non-zero means "go on". A preflight script that ends with a successful command it did not intend as a verdict will silently cancel every provisioning run on the fleet.

If preflight is absent or empty, or contains no rootscript, the stage is recorded as Skipped and the run continues. That is the normal case for a simple manifest.

Keep the script small and self-contained: it runs before your management tooling exists. The example script shipped in the repo decides by reading /Library/Preferences/com.github.bootstrapmate.plist.

setupassistant

Runs as root, in the LaunchDaemon context, with no user session and often with Setup Assistant still on screen. This is where the bulk of provisioning belongs: management agents, security tooling, configuration scripts — anything that must be in place before the first user reaches the desktop.

Every item in the array is attempted. An item failure is recorded and makes the overall run unsuccessful, but it does not abort the stage and it does not prevent userland from running: one failed package must not be able to strand provisioning.

What this stage cannot assume: no console user, no user defaults to write into, no ~/Library, no Finder or Dock to talk to. The data volume may still be settling — the CLI waits up to 30 seconds for /Library to become writable before it starts. Anything needing a logged-in user goes in userland.

If setupassistant is absent or empty it is not run, and no status is recorded for it.

userland

Runs after setupassistant, and only once a real console user is logged in. BootstrapMate polls every two seconds for a console user that is not loginwindow, _mbsetupuser, root, and does not begin with _.

This wait has no timeout. A machine that reaches userland and never has a user log in will sit there indefinitely with the dialog reading "Waiting for user to log in...". If your fleet includes Macs that finish enrolment and are never signed into, keep userland empty and do the work in setupassistant.

Items then run in array order, each recorded as success or failure. As in setupassistant, a failure does not stop the remaining items.

The security context is the important caveat: userland items — including type: userscript — still run as root, in the daemon's context, not as the logged-in user. The stage tells you when work happens, not who it happens as. See Item Types.

Use userland for work that needs a user to exist (per-user configuration applied by a root script that targets the console user's home) and for the handoff: a donotwait script that starts the ongoing config-management run survives BootstrapMate's teardown. See Handoff to Munki.

Mapping to Windows

The macOS and Windows tools use the same three stage names in the same order, so a manifest reads the same on either side. Where they differ is the platform mechanics behind setupassistant: on macOS that stage runs from a root LaunchDaemon while Setup Assistant is up, and on Windows the equivalent moment is the Autopilot Enrollment Status Page (ESP) device phase, which runs before the user's Out-Of-Box Experience (OOBE) completes. userland corresponds to the ESP user phase. preflight means the same thing on both.

For the Windows detail, see https://github.com/bootstrapmate/bootstrapmate-windows/wiki/Stages.

See also

Clone this wiki locally