Skip to content

Item Types

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

Item Types

Every item in a manifest carries a type, and the type decides what BootstrapMate does with the downloaded payload. There are three: package, rootscript and userscript. This page covers what each does, which fields matter for it, how success is judged, and the traps in each.

The values are matched exactly and are case-sensitive. Anything else logs Unknown item type: <type>, shows "Unknown type" in the dialog, and fails the item.

Which fields apply to which type

Field package rootscript userscript
file, hash, url, type Yes Yes Yes
name Yes Yes Yes
retries, retrywait Yes Yes Yes
skipIf Yes Yes Yes
packageid, version Yes No effect No effect
expectedTeamID, allowUnsigned Yes No effect No effect
donotwait No effect Yes Yes
followRedirects Accepted, no effect Accepted, no effect Accepted, no effect

package

An Apple installer package, installed at the system root.

The sequence is: check whether it is already installed, download, verify the signature, install. The already-installed check only happens when both packageid and version are set — BootstrapMate reads pkgutil --pkg-info-plist <packageid> and skips the item if the receipt's version is greater than or equal to version. A skip here counts as a success and is reported in the dialog as already installed.

Installation is /usr/sbin/installer -pkg <file> -target /. Success is installer exiting 0 — nothing else is checked. A package that installs cleanly but does nothing useful is a success as far as BootstrapMate is concerned.

Before installer runs, the package's signature is checked with pkgutil --check-signature. An untrusted or missing signature refuses the install unless allowUnsigned is set, and an expectedTeamID mismatch refuses it unconditionally. See Security and Package Verification.

Gotchas:

  • -target / is the only target. There is no way to install to a volume or a custom location, and file is the download destination, not an install destination.
  • Distribution packages that would normally prompt, require a choices file, or refuse headless install will fail here, because there is no interactive installer.
  • donotwait does nothing for packages. Every package install is waited on, with no timeout.
  • Downloads are read fully into memory before being written to file, so a very large package is fully resident in RAM during the transfer.

rootscript

An executable file run as root.

BootstrapMate downloads it, sets its permissions to 0755, and runs it directly — the file at file is the executable, so it needs a working shebang. There is no interpreter argument and no way to pass arguments to the script. The environment inherits the daemon's, with PATH set to /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin, and the working directory is set to the script's own directory.

Success is exit code 0. Any non-zero exit fails the item. Standard output is logged at INFO and standard error at WARN, each line prefixed [OUTPUT] <script name>: , which is how you find out what a script actually did.

Scripts are not signature-checked. The SHA-256 hash is the only integrity control on a script, which makes the manifest's own transport the thing you have to trust.

rootscript is also the only type preflight will use, and there its exit code means something different — see Stages.

userscript

Same schema fields as rootscript, and intended for per-user work in the userland stage.

A userscript runs as root, not as the logged-in user. It is executed through exactly the same code path as a rootscript: same permissions, same environment, same root daemon context. The code that would run a script as the console user via launchctl asuser exists but is never called. The only real difference between the two types today is the label, and that --userscript mode runs userland items of this type and nothing else.

Write these scripts accordingly. Anything that relies on being the user — defaults write without an explicit path, ~ expansion, per-user launchd domains, the user's keychain — will act on root, not on the person logging in. If you need to touch the console user's environment, look the user up inside the script and target them explicitly.

Success and output handling are identical to rootscript: exit 0 is success, output is captured to the log.

donotwait on either script type

With donotwait: true the script is launched and BootstrapMate continues immediately. The item is marked successful the moment the process starts; its exit code is never read, and its output is not captured to the log — a fire-and-forget script that fails leaves no trace in the run. The working directory is not set for these launches either.

This is the mechanism behind the handoff: because the daemon plist sets AbandonProcessGroup, a donotwait child keeps running after BootstrapMate exits and its LaunchDaemon is booted out. See Handoff to Munki.

See also

Clone this wiki locally