Skip to content

Baseline Mode

Rod Christiansen edited this page Oct 8, 2026 · 1 revision

Baseline Mode

Baseline mode lets one manifest serve two kinds of Mac: a new one that needs provisioning, and one that is already provisioned and in use but whose management tooling should be brought back to the versions the manifest publishes. This page covers how the preflight chooses it, what a baseline run does and does not do, the install ledger that keeps it from reinstalling things, and the throttle that keeps it from repeating too often.

Choosing baseline

The preflight decides. A rootscript in preflight that exits 2 chooses baseline:

Exit code Mode What runs
0 Skip Nothing. The run ends and the one-shot LaunchDaemon removes itself.
2 Baseline setupassistant items, with no SwiftDialog window, no userland stage and no reboot.
any other positive Provision The full bootstrap: setupassistant, then userland.

Every root script runs with BOOTSTRAPMATE_BASELINE_EXIT_CODE=2 in its environment. A build without baseline mode does not set it and treats exit 2 as provision, so a preflight that may be run by an older build checks for the variable before asking for baseline:

if [[ -n "${BOOTSTRAPMATE_BASELINE_EXIT_CODE:-}" ]]; then exit "$BOOTSTRAPMATE_BASELINE_EXIT_CODE"; fi

The repository's examples/preflight.sh decides between skip (0) and provision (1) only. Add the baseline branch for the Macs you want refreshed — typically those on a production Munki manifest that should still receive tooling updates.

A dry run never runs the preflight, so it never runs in baseline mode. Neither does --userscript.

What a baseline run does

  • Runs setupassistant items, leaving out any with "baseline": false. Those are logged as <name> (excluded from baseline) and recorded as skipped.
  • Shows no SwiftDialog window, whatever enableDialog says, so a working user is never interrupted.
  • Skips userland entirely; the phase is recorded as Skipped.
  • Never reboots, even with --reboot.
  • Records the session's run type as baseline, reports like any other run, and removes its LaunchDaemon at the end.

Not reinstalling what is already there

A baseline run on a current Mac installs nothing. Three checks make that so, applied in order to each package item:

  1. The receipt. A package with packageid and version is skipped when pkgutil reports that receipt at that version or newer. This applies to every run, not only baselines.
  2. The install ledger. BootstrapMate records the SHA-256 of every package file it installs successfully in /Library/Managed Bootstrap/installed.json, on every run. A baseline run skips any package whose hash is in the ledger, logging <name> - this build was already installed by BootstrapMate. That covers payload-free packages, which leave no receipt, and items whose packageid does not match the receipt. The ledger is overruled when the package's receipt shows an older version than the manifest names, which means the software was replaced since and needs reinstalling.
  3. The download cache. A package skipped by either check is not downloaded at all. Scripts and packages that do run reuse an unchanged file already at file while retainCache is on.

Scripts in setupassistant run on every baseline that is not throttled. Write them to be safe to repeat, or mark them "baseline": false.

The baseline throttle

A baseline may download payloads, so it must not repeat sooner than intended. The outcome of each baseline run is kept in /Library/Managed Bootstrap/baseline.json. The throttle applies only after the preflight has chosen baseline mode: the manifest and the preflight script are still fetched, which is small, but a throttled baseline downloads and installs no items. A Mac whose preflight chooses provisioning always provisions, however recent its last baseline.

The rules, checked in this order:

  • While the file named by forceRunFile exists, owned by root in a directory only root can write, the baseline goes ahead. Any other force file is ignored and logged. The throttle only looks; the preflight is what removes the file.
  • With no baseline record, the baseline goes ahead. A Mac being provisioned has none, and every provisioning run deletes the record.
  • A baseline that was interrupted — stopped by SIGTERM, or left running by a restart or crash — is retried by the next run however recent it was, until one ends.
  • With baselineMinIntervalHours at 0 or below, the throttle is off.
  • After a completed baseline, the next waits baselineMinIntervalHours (default 144, six days, so a weekly schedule still runs every time) — unless the running BootstrapMate version differs from the one that last completed a baseline. A new version always runs its baseline. A record written by a build that kept no version counts as a different version.
  • After a baseline that ended partial_failure or failed, the next waits 24 hours (or the minimum interval, if that is shorter), whatever the version. One retry is allowed then; if it does not complete either, the full interval applies again, unless the version has changed.

A throttled baseline logs Baseline throttle: skipping this baseline: <reason>, ends with run type skip, and removes its LaunchDaemon like any other finished run. An allowed one logs Baseline throttle: baseline allowed: <reason>.

The record is written as running before any item runs, so a baseline cut short is retried rather than waiting out the interval. At the end it becomes completed, or partial_failure if any item failed. Dry runs never write it.

When baselines happen

BootstrapMate never relaunches itself to retry or to repeat. A baseline happens only when something starts a run: the LaunchDaemon loading when the package is installed or reinstalled, or your MDM's own schedule running the CLI. The throttle is what makes it safe to schedule that generously.

Files

Path Contents
/Library/Managed Bootstrap/baseline.json end_time, status, consecutive_failures, tool_version, completed_version
/Library/Managed Bootstrap/installed.json The install ledger, keyed by lowercase SHA-256
/Library/Managed Bootstrap/.bootstrapmate-force-run The default force file

Both state files are used only when they are root-owned and writable by root alone; a file that fails that check is ignored, which can only make a run do more. See Security and Package Verification.

See also

Clone this wiki locally