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

FAQ

The questions administrators ask in the first week, answered from the shipping behaviour. Where an answer turns on something surprising, it links to the page that covers it in full.

How is this different from InstallApplications?

BootstrapMate is a Swift tool descended from an InstallApplications migration, and it keeps the vocabulary: the same three stages (preflight, setupassistant, userland), the same item types, and the same key names for hash, packageid, version, retries, retrywait and donotwait. What it adds is package signature verification with expectedTeamID and allowUnsigned, YAML manifests alongside JSON, a SwiftDialog progress window, per-phase status files and an optional run-summary POST. The most important difference in practice is spelling: manifest keys decode against Swift property names, so it is skipIf and followRedirects in camelCase, not skip_if.

Does it replace Munki?

No. BootstrapMate runs once, installs what the manifest lists, and deletes its own LaunchDaemon. It has no schedule, no check-in, no concept of uninstalling software and no awareness of what should be on the Mac tomorrow. The intended shape is that its manifest installs and configures Munki, and Munki manages the machine from then on. See Handoff to Munki.

Does BootstrapMate do the Munki handoff for me?

It does not — there is no Munki-specific code anywhere in it. Installing munkitools, writing Munki's preferences and putting Munki into bootstrap mode are all things you author as ordinary manifest items, typically a package followed by a rootscript in the setupassistant stage. That is deliberate; it is also why nothing in the tool will warn you if you forget.

What happens if the network is down?

The run waits for a usable network path for up to --network-timeout seconds (default 120), then does one DNS check as a last resort, then continues regardless. A network timeout is never fatal on its own — you get Network check timed out - proceeding anyway in the log, and the failure surfaces later as download failures. Note that setting networkTimeout in a configuration profile has no effect; only the CLI option is honoured.

What happens if a package fails?

It depends on the stage. A failure in setupassistant or userland is recorded against that item, the remaining items still run, and the run as a whole exits 1 — a single bad package must not strand provisioning. A preflight script that cannot be downloaded or launched is different: it fails the phase and skips both remaining stages. There is no retry on an install failure; retries and retrywait govern downloads only.

Can items run as the logged-in user?

Not today. userland items wait for a real console user to appear before running, but userscript items are executed as root in the daemon process just like rootscript items. The routine that would run a script as the console user exists in the source and is never called. If an item needs to act on the current user, it has to determine and switch to that user itself. This one catches people out; it is also listed on Troubleshooting and Gotchas.

Can it run more than once?

The CLI can be run by hand as often as you like. The unattended daemon cannot: cleanup at the end of every run — including a run that preflight skipped — deletes /Library/LaunchDaemons/com.github.bootstrapmate.plist and boots the job out. To get another unattended run, reinstall the package or lay the plist back down and launchctl load -w it. A missing plist is the normal state of a Mac that has already provisioned, not a fault.

Is a second run safe if the first one half-completed?

Broadly yes, but by re-doing work rather than by resuming it. There is no state machine: every run starts from scratch with a new run id. Files whose SHA-256 already matches are not re-downloaded, and a package with both packageid and version set is skipped when pkgutil already reports that receipt at that version or newer. Anything without those two keys will be installed again.

How do I tell what version is installed?

Not from --version. The version string the CLI prints — and the one in the log header, the reporting payload and the User-Agent — is generated from the clock when it is first read at runtime, so it reports the current time rather than the build. Ask the installed bundle instead:

defaults read /Applications/Utilities/BootstrapMate.app/Contents/Info CFBundleShortVersionString

pkgutil --pkg-info com.github.bootstrapmate gives the version of the package receipt.

Are unsigned packages allowed?

Only if you say so. Every package item is checked with pkgutil --check-signature before installer runs, and an untrusted or unsigned package is refused with Refusing to install <pkg>: untrusted or missing signature. Setting allowUnsigned — as a preference, as a per-item key, or with --allow-unsigned — permits it with a warning in the log. A Team ID mismatch against expectedTeamID is refused in every case; allowUnsigned does not override it. Scripts are never signature-checked at all — their only protection is the SHA-256 hash, which is a good reason to host them somewhere you control. See Security and Package Verification.

What happens if nobody ever logs in?

The run stops in the userland stage and stays there. The wait for a console user is a poll loop with no timeout, so a Mac left at the login window will sit with the dialog reading "Waiting for user to log in..." indefinitely, and the run will never reach its reporting or cleanup steps. If your Macs may never see an interactive login, put the work in setupassistant instead.

Do I need SwiftDialog?

No. BootstrapMate checks for /usr/local/bin/dialog at startup; if it is not there it logs that it is running in headless mode and every dialog call becomes a no-op. The run proceeds identically either way. You can also suppress the window explicitly with --no-dialog or --silent. Note that the dialog-related preferences — enableDialog, dialogIcon, blurScreen, and the title and message keys — are read from the profile but never reach the dialog; only the corresponding CLI flags do anything.

Can I put the manifest behind authentication?

Yes. A complete header value passed as --headers (or set as authorizationHeader in the profile) is sent on the manifest fetch and on every item download. Bear in mind that HTTP status codes are not inspected on downloads, so an authentication failure returns a body that gets written to the destination path and then fails the hash check — a hash mismatch is frequently an auth or URL problem. See Serving Manifests and Packages.

Does it clean up what it downloaded?

No. Every item is written to the absolute path in its file key and left there; the cache-cleaning routine in the source is never called. Choose file paths you are content to leave on the disk, or remove them yourself from a late manifest item.

What macOS versions does it run on?

The shipping bundle declares a deployment target of macOS 13.0, and the README says macOS 13.0 or later, but the Swift package manifest requires macOS 15 to build. Treat 13.0 as the floor for running an already-built package and 15 as the floor for building from source, and test on the oldest OS you actually deploy.

See also

Clone this wiki locally