-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
This page walks one Mac through a complete first run: get the package, write a manifest with a single item in it, host that manifest, install BootstrapMate, run it by hand against the manifest URL, and read the log to confirm what happened. Do this on a test machine you can wipe. Once it works by hand, Deployment covers delivering the same thing through MDM.
Tagged builds are published on this repository's Releases page as
BootstrapMate-<version>.pkg, where the version follows the YYYY.MM.DD.HHMM scheme.
The package produced by the public release workflow is unsigned and un-notarized. It
is fine for a hands-on test on a machine you control, but an MDM cannot deploy it as-is —
sign and notarize your own build first. See Building and Signing
for the make targets that build, sign, notarize and staple.
Install it on the test Mac:
sudo installer -pkg BootstrapMate-2026.01.15.0930.pkg -target /
The package installs /Applications/Utilities/BootstrapMate.app and creates a symlink at
/usr/local/bootstrapmate/managedbootstrapinstall pointing at the CLI inside the bundle.
The postinstall script also loads /Library/LaunchDaemons/com.github.bootstrapmate.plist,
which starts a run immediately with no arguments. With no manifest URL configured yet
that run waits up to 300 seconds for a management profile, logs
Management configuration not received within 300s, then exits 1. That is expected here;
the rest of this page runs the CLI directly instead. The postinstall's own record is at
/tmp/bootstrapmate-postinstall.log.
A manifest is a JSON or YAML document with up to three top-level arrays — preflight,
setupassistant and userland. All three are optional. Start with one package in
setupassistant.
Every item needs file (the absolute path the download is written to), url, hash (a
lowercase hex SHA-256 of the file at that URL) and type. Compute the hash from the exact
file you are going to host:
shasum -a 256 munkitools-6.0.0.pkg
Save this as bootstrap.json, substituting your own URL and the digest you just computed:
{
"setupassistant": [
{
"name": "Munki Tools",
"file": "/Library/Application Support/BootstrapMate/munkitools.pkg",
"url": "https://example.com/bootstrap/packages/munkitools-6.0.0.pkg",
"hash": "0000000000000000000000000000000000000000000000000000000000000000",
"type": "package",
"packageid": "com.googlecode.munki.core",
"version": "6.0.0"
}
]
}packageid and version are optional, but supplying both makes the run idempotent: if
pkgutil reports that receipt at that version or newer, the item is skipped instead of
reinstalled. Item keys are matched against the Swift property names exactly, so the
camelCase spellings (skipIf, followRedirects, expectedTeamID) are the only ones
that decode. The full list is in the Manifest Reference.
Put bootstrap.json and the .pkg on any HTTPS host the Mac can reach — a web server, a
bucket, a CDN. The format is chosen by the URL's path extension: .yaml or .yml is
parsed as YAML, .json as JSON, and anything else is tried as JSON then as YAML. Query
parameters do not interfere with that detection.
If the host needs authentication, pass a complete header value with --headers (for
example Basic ...); it is sent on the manifest fetch and on every item download. HTTP
status codes are not inspected on downloads, so a 404 page gets written to the
destination path and then fails the hash check — a "Hash mismatch" in the log often means
a bad URL rather than a bad file. See
Serving Manifests and Packages.
--dry-run performs no installer actions. --no-dialog keeps the run headless, and
--verbose adds the effective configuration and the debug lines to the log and console.
sudo /usr/local/bootstrapmate/managedbootstrapinstall --jsonurl https://example.com/bootstrap/bootstrap.json --verbose --no-dialog --dry-run
This proves the manifest downloads, decodes and orders correctly before anything is installed.
sudo /usr/local/bootstrapmate/managedbootstrapinstall --jsonurl https://example.com/bootstrap/bootstrap.json --verbose --no-dialog
The CLI must run as root: it writes to /Library, calls /usr/sbin/installer and manages
its own LaunchDaemon. Packages are signature-checked with pkgutil --check-signature
before installer runs; an untrusted or unsigned package is refused unless you set
allowUnsigned. See
Security and Package Verification.
Each run writes exactly one log file, named for the time it started, into
/Library/Managed Bootstrap/logs/. There is no size-based rotation; files older than 30
days are deleted when a later run starts.
ls -t "/Library/Managed Bootstrap/logs" | head -1
Open the newest file. A successful run opens with === BootstrapMate Session Started ===
and a header block (version, PID, user, machine, OS, architecture, command line), then
BootstrapMate v<version> started, then the per-stage and per-item lines, and closes with
=== BootstrapMate Session Ended === (Duration: N.Ns). Lines are stamped and levelled:
[2026-09-01 13:15:14] INFO Session started
[2026-09-01 13:15:14] ERROR Failed to load manifest
To see just the failures:
grep -E '\] ERROR ' "/Library/Managed Bootstrap/logs/2026-09-01-131514.log"
The same run is also visible in the unified log:
log show --predicate 'subsystem == "com.github.bootstrapmate"' --last 1h
Machine-readable per-phase results — stage, exit code, timestamps and last error for
Preflight, SetupAssistant and Userland — are written to
/Library/Managed Bootstrap/status.json. Be aware that on a successful run the
companion plist /Library/Preferences/com.github.bootstrapmate.plist is overwritten with
only LastRunVersion, LastUpdated and Architecture, so the per-phase detail there
survives only for failing runs. Logging and Reporting has the
full picture.
At the end of every run — success, failure, or a preflight that decided to skip —
BootstrapMate deletes /Library/LaunchDaemons/com.github.bootstrapmate.plist and boots
the job out. The daemon is one-shot. An absent plist is normal after a completed run, not
a fault. To trigger another daemon-driven run, reinstall the package or lay the plist back
down and launchctl load -w it. Running the CLI by hand, as above, is unaffected.
Once the single item works, add a preflight rootscript that exits 0 when the Mac is
already provisioned, add the rest of your setupassistant items, and configure the
manifest URL by profile so the LaunchDaemon can run unattended — the daemon is launched
with no arguments, so everything it needs must come from the
com.github.bootstrapmate Preferences domain.