Skip to content

Deployment

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

Deployment

How BootstrapMate gets onto a Mac and how it gets invoked. Read this when you are building the enrollment package for a new fleet, or when you need to make an already-provisioned Mac run the bootstrap again.

The installer package

BootstrapMate ships as a single flat installer package with the identifier com.github.bootstrapmate, named BootstrapMate-<version>.pkg. Its payload is one non-relocatable app bundle:

/Applications/Utilities/BootstrapMate.app

Inside that bundle are the three executables. Contents/MacOS/managedbootstrapinstall is the command-line tool that does all the provisioning work; Contents/MacOS/BootstrapMateGUI is the SwiftUI app; Contents/MacOS/BootstrapMateHelper is the privileged XPC helper used only by the GUI.

The package built by this repository's public release workflow is unsigned and un-notarized. Sign, notarize and staple it in your own pipeline before deploying it — see Building and Signing.

What the postinstall does

The package's postinstall script runs as root and, in order:

  1. Creates /Library/Managed Bootstrap/logs and /Library/Managed Bootstrap/cache, mode 755. It does this before the daemon can start, so the first run always has somewhere to log.
  2. Creates the symlink /usr/local/bootstrapmate/managedbootstrapinstall pointing at the CLI inside the app bundle, and removes the legacy installapplications symlink from earlier versions.
  3. Sets /Library/LaunchDaemons/com.github.bootstrapmate.plist to 644 root:wheel, runs launchctl bootout system/com.github.bootstrapmate to clear any job already loaded under that label, then launchctl load -w on the plist. The bootout matters on upgrade: load no-ops when the label is already loaded, which would leave launchd pointing at the previous executable path.
  4. Copies the helper's plist out of the bundle to /Library/LaunchDaemons/com.github.bootstrapmate.helper.plist, rewrites its BundleProgram key into an absolute ProgramArguments entry, and runs launchctl bootstrap system on it.

Everything the script does is appended to /tmp/bootstrapmate-postinstall.log. That file is the first place to look when a package installed but nothing happened.

The LaunchDaemon

The bootstrap run is driven by one daemon:

Label com.github.bootstrapmate
Plist /Library/LaunchDaemons/com.github.bootstrapmate.plist
Program /Applications/Utilities/BootstrapMate.app/Contents/MacOS/managedbootstrapinstall
Keys RunAtLoad true, AbandonProcessGroup true

ProgramArguments carries the executable path and nothing else. There is no KeepAlive, and no StandardOutPath or StandardErrorPath — all output goes to the tool's own log directory.

AbandonProcessGroup is what lets donotwait items keep running after the daemon boots itself out at the end of the run.

The daemon is one-shot. At the end of every run, successful or not, BootstrapMate deletes its own plist and runs launchctl bootout system/com.github.bootstrapmate. An absent plist on a provisioned Mac is the normal, expected state — not evidence of a failed install.

MDM delivery

BootstrapMate is delivered like any other enrollment-time package. In vendor-neutral terms you need two things assigned to the device at Automated Device Enrollment:

  • A bootstrap package. Deliver the signed .pkg through whichever mechanism your MDM offers for installing a package during enrollment — an InstallEnterpriseApplication command, a "custom bootstrap package", or the equivalent pre-stage package slot. The postinstall loads the daemon, and RunAtLoad starts the run immediately.
  • A configuration profile carrying the preference domain com.github.bootstrapmate. This is how the run learns what to install.

Because the daemon passes no arguments, the manifest URL for a daemon-driven run must come from the profile. Set url (aliases jsonUrl, jsonurl, JsonUrl, ConfigURL, ManifestURL) in a com.apple.configuration.plist payload scoped to the system, with PreferenceDomain set to com.github.bootstrapmate. If the manifest is behind an authenticated endpoint, add headers in the same payload. See Preferences for the full key list and Serving Manifests and Packages for hosting.

If the profile has not landed by the time the daemon starts, BootstrapMate waits for it: with no --jsonurl and no usable configuration it re-reads managed preferences once a second for up to 300 seconds before logging Management configuration not received within 300s and continuing. If no manifest URL has arrived by then the run exits 1.

Re-running on a provisioned Mac

The daemon removes itself after a run, so rebooting alone will not repeat the bootstrap. Pick one of these.

Reinstall the package. The postinstall lays the plist back down and loads it, which starts a fresh run:

sudo installer -pkg BootstrapMate-<version>.pkg -target /

Or restore the plist yourself from a copy and load it:

sudo launchctl load -w /Library/LaunchDaemons/com.github.bootstrapmate.plist

Or invoke the CLI directly, which is the better option when you are testing a manifest. This bypasses the profile entirely:

sudo /usr/local/bootstrapmate/managedbootstrapinstall --jsonurl https://example.com/bootstrap/manifest.yaml --verbose

Add --dry-run --no-dialog to walk the manifest without installing anything. Every flag is listed in Command Line Reference.

Note that a run invoked this way still performs the one-shot cleanup at the end, so it will delete the LaunchDaemon plist if one is present.

Two mechanisms make a re-run cheaper rather than repeating work: a payload whose SHA-256 already matches is not downloaded again, and a package whose packageid and version are both set and already satisfied by a pkgutil receipt is not reinstalled.

See also

Clone this wiki locally