Skip to content

Handoff to Munki

Rod Christiansen edited this page Sep 5, 2026 · 2 revisions

Handoff to Munki

BootstrapMate is a bootstrapper, not a config-management system. It exists to get a freshly enrolled Mac to the point where Munki can take over, and then to get out of the way. This page covers what it leaves behind, how the handoff is arranged, and how to verify it happened.

What BootstrapMate leaves behind

After a run, the device has:

  • Whatever your manifest installed — in a typical deployment, the Munki tools package and a script that writes /Library/Preferences/ManagedInstalls.plist with SoftwareRepoURL and ClientIdentifier.
  • /Applications/Utilities/BootstrapMate.app, still installed.
  • /Library/Managed Bootstrap/logs/<yyyy-MM-dd>/<HHmmss>/ for the run, holding bootstrap.log, events.jsonl and session.json, plus /Library/Managed Bootstrap/status.json and /Library/Preferences/com.github.bootstrapmate.plist.
  • No LaunchDaemon. /Library/LaunchDaemons/com.github.bootstrapmate.plist is deleted and the job booted out at the end of every run, so launchd never fires it again.
  • Downloaded payloads, still on disk at each item's file path. BootstrapMate does not clean them up.

From that point on there is no BootstrapMate process, no timer, and no agent. Everything ongoing is Munki's.

What Munki bootstrap mode is

Munki has a built-in mode for exactly this situation: an unattended, repeated managedsoftwareupdate run at startup that keeps going until the device's manifest is fully satisfied, showing the setup UI rather than the normal background behaviour. It is triggered by the presence of one file:

/Users/Shared/.com.googlecode.munki.checkandinstallatstartup

While that file exists, Munki runs its check-and-install cycle at startup. Munki removes it itself once there is nothing left to install, and the device drops into its normal periodic schedule.

This is Munki's contract, not BootstrapMate's. Creating that file is the handoff.

What you have to arrange yourself

BootstrapMate does not create the marker file. There is no preference key for it, no manifest field for it, and no implicit behaviour. Nothing in BootstrapMate references checkandinstallatstartup. If you want Munki bootstrap mode, a manifest item must create it.

The usual shape is a rootscript in the setupassistant stage, running after the Munki tools package and after whatever configures ManagedInstalls.plist. Because the stage runs as root before any user exists, /Users/Shared is writable and the script needs no elevation logic — it touches the marker and exits.

A minimal manifest fragment, with your own hash and URL:

setupassistant:
  - file: /Library/Application Support/BootstrapMate/munkitools.pkg
    hash: <sha256>
    url: https://example.com/bootstrap/packages/munkitools.pkg
    name: Munki Tools
    type: package
    packageid: com.googlecode.munki.core

  - file: /Library/Application Support/BootstrapMate/configure-munki.sh
    hash: <sha256>
    url: https://example.com/bootstrap/scripts/configure-munki.sh
    name: Configure Munki
    type: rootscript

Put the marker creation in configure-munki.sh, alongside writing SoftwareRepoURL and ClientIdentifier, so the device is never in bootstrap mode without a repo to talk to.

Some deployments instead run managedsoftwareupdate directly from a manifest item rather than relying on startup mode. If you do that in the userland stage, set donotwait: true on the item: the daemon plist sets AbandonProcessGroup, which is specifically what keeps a fire-and-forget child alive after BootstrapMate boots itself out. Without donotwait the run blocks until the Munki run finishes.

Ordering, retries and skipping are covered in Stages, Retries and Timeouts and Conditions and Skipping.

Verifying the handoff

Check the marker exists before the reboot that is meant to consume it:

ls -la /Users/Shared/.com.googlecode.munki.checkandinstallatstartup

Check Munki actually got configured, since the marker on its own does nothing useful:

/usr/libexec/PlistBuddy -c "Print :SoftwareRepoURL" /Library/Preferences/ManagedInstalls.plist
/usr/libexec/PlistBuddy -c "Print :ClientIdentifier" /Library/Preferences/ManagedInstalls.plist

Check that Munki has pulled manifests from the repo:

ls /Library/Managed\ Installs/manifests

Check what BootstrapMate itself thought happened, in the run log:

grep '\[OUTPUT\]' /Library/Managed\ Bootstrap/logs/*/*/bootstrap.log

The [OUTPUT] <scriptname>: lines are your configure script's own stdout and stderr, captured into the BootstrapMate log — stdout at INFO, stderr at WARN. If your script logs what it did, this is where you read it back.

Finally, confirm BootstrapMate is done rather than stuck: the absence of /Library/LaunchDaemons/com.github.bootstrapmate.plist means a run completed and tore itself down. That is the normal end state, not a problem.

Re-provisioning

Because the daemon is one-shot, a Mac that is already in production will not re-run BootstrapMate on reboot. The preflight stage is the intended gate for the opposite case: a single rootscript that inspects ManagedInstalls.plist (and BootstrapMate's own /Library/Preferences/com.github.bootstrapmate.plist) and exits 0 when the device is already on a production manifest, which skips the whole bootstrap. examples/preflight.sh in the repository is a worked implementation of that check.

The Windows equivalent

The same handoff on Windows targets Cimian instead of Munki: https://github.com/bootstrapmate/bootstrapmate-windows/wiki/Handoff-to-Cimian

See also

Clone this wiki locally