Skip to content

Getting Started

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

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.

1. Obtain the package

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.

2. Author a minimal manifest

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.

3. Host the manifest and the package

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.

4. Do a dry run

--dry-run suppresses downloads only; the install and script steps are still attempted, so expect failures for payloads it never fetched. --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.

5. Run it for real

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.

6. Confirm success in the log

Each run gets its own session directory under /Library/Managed Bootstrap/logs/, named YYYY-MM-DD/HHMMSS/ for the time it started. Inside it, bootstrap.log is the human log and events.jsonl and session.json are the same run in machine-readable form. There is no size-based rotation; day directories older than 30 days are removed when a later run starts.

ls -d /Library/Managed\ Bootstrap/logs/*/*/ | sort | tail -1

Open bootstrap.log in the newest one. 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/bootstrap.log"

Or ask the run itself how it ended:

/usr/bin/plutil -extract status raw -- "/Library/Managed Bootstrap/logs/2026-09-01/131514/session.json"

completed means the run logged no errors; partial_failure means it logged at least one.

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.

7. Run it again

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.

Next steps

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.

See also

Clone this wiki locally