Build the macOS MDM Package
Build a single Pult Agent .pkg with an embedded postinstall script that provisions the bootstrap token, ready to upload to any macOS MDM.
This guide describes how to build a single .pkg artifact that you upload to your MDM. The
resulting package combines the signed Pult Agent.app with a postinstall script that writes the
bootstrap token onto each enrolled device. It is the macOS analogue of the
Intune deployment package used on Windows.
The wrapper .pkg is unsigned, but the Pult Agent.app it contains is signed and notarized by
Pult. MDMs install packages as root via installer, which bypasses Gatekeeper, so an unsigned
wrapper is acceptable for MDM-only distribution. Don't redistribute the wrapper outside your MDM.
Terminology: This guide uses "bootstrap token" to refer to Pult's enrollment token. Apple uses the same term for an unrelated MDM-managed token tied to FileVault and SecureToken. The two are completely independent.
Prerequisites
- Access to a Mac. This guide uses
pkgutil,pkgbuild, andhdiutil, which are macOS-only tools. If no Mac is available, an in-browser builder is planned -- see MDM Deployment Overview. - The Pult Agent canonical artifact for the version you want to deploy. Either the
.pkgor the.dmgworks. - A bootstrap token generated in the Pult Dashboard.
Step 1: Extract Pult Agent.app
Set up a working directory:
mkdir -p ~/pult-mdm-build && cd ~/pult-mdm-build
mkdir -p payloadThen extract Pult Agent.app into ./payload/ using the section that matches your artifact.
Option A: From the .pkg
pkgutil --expand "Pult-Agent_0.2.9-beta1_universal.pkg" ./expanded
( cd ./payload && gunzip -dc ../expanded/*.pkg/Payload | cpio -i --quiet )Option B: From the .dmg
mkdir -p ./dmg-mount
hdiutil attach -nobrowse -noverify -noautoopen -readonly \
-mountpoint ./dmg-mount "Pult-Agent_0.2.9-beta1_universal.dmg"
cp -R "./dmg-mount/Pult Agent.app" ./payload/
hdiutil detach ./dmg-mountIf hdiutil attach fails or cp reports a quarantine error, remove the quarantine flag from the
download first:
xattr -dr com.apple.quarantine "Pult-Agent_0.2.9-beta1_universal.dmg"Verify the extraction succeeded -- this should print the agent version:
/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' \
"./payload/Pult Agent.app/Contents/Info.plist"Step 2: Create the Postinstall Script
Create the scripts directory:
mkdir -p ./scriptsSave the following as ./scripts/postinstall. Replace your-bootstrap-token-here with the token
you generated in the Pult Dashboard.
#!/bin/bash
set -euo pipefail
# Bootstrap token from the Pult Dashboard. Replace before building the package.
BOOTSTRAP_TOKEN="your-bootstrap-token-here"
# Where the postinstall drops the bootstrap token file. The Pult Agent watches
# this location and consumes the file on first launch.
# - "user": the console user's ~/Library/Application Support/com.pult.agent/.
# Compatible with all Pult Agent versions.
# - "system": /Library/Application Support/com.pult.agent/, with an ACL that
# lets the user-context agent delete the file after consumption.
# Reserved for future Pult Agent versions; keep "user" today.
# - "both": write to both locations during a transition window.
DROP_MODE="user"
SYSTEM_DIR="/Library/Application Support/com.pult.agent"
drop_user_context() {
local console_user
console_user="$(/usr/bin/stat -f%Su /dev/console)"
if [[ -z "${console_user}" || "${console_user}" == "root" ]]; then
echo "[user mode] no interactive console user; skipping user-context drop"
return 1
fi
local user_home
user_home="$(/usr/bin/dscl . -read "/Users/${console_user}" NFSHomeDirectory \
| /usr/bin/awk '{print $2}')"
local dir="${user_home}/Library/Application Support/com.pult.agent"
local file="${dir}/BOOTSTRAP_TOKEN"
/usr/bin/sudo -u "${console_user}" /bin/mkdir -p "${dir}"
/usr/bin/sudo -u "${console_user}" /usr/bin/tee "${file}" >/dev/null <<<"${BOOTSTRAP_TOKEN}"
/bin/chmod 0600 "${file}"
echo "[user mode] token written to ${file}"
}
drop_system_context() {
/bin/mkdir -p "${SYSTEM_DIR}"
/usr/sbin/chown root:wheel "${SYSTEM_DIR}"
/bin/chmod 0755 "${SYSTEM_DIR}"
/bin/chmod +a "group:everyone allow add_file,delete_child" "${SYSTEM_DIR}"
local file="${SYSTEM_DIR}/BOOTSTRAP_TOKEN"
/usr/bin/printf '%s' "${BOOTSTRAP_TOKEN}" > "${file}"
/bin/chmod 0644 "${file}"
echo "[system mode] token written to ${file}"
}
case "${DROP_MODE}" in
user) drop_user_context ;;
system) drop_system_context ;;
both)
drop_system_context
drop_user_context || true
;;
*)
echo "unknown DROP_MODE: ${DROP_MODE}"
exit 1
;;
esac
# Best-effort: launch the agent in the console user's context so the token is
# consumed immediately. If no user is logged in, the agent will pick up the
# token at next login.
#
# Note: `sudo -H` is required, not just `-u`. On recent macOS,
# `sudo -u <user> env` reports HOME=/var/root -- sudo switches the UID to the
# target user but preserves root's HOME across the switch. The Pult Agent
# reads HOME on startup to resolve its config dir, so without -H it tries to
# create `/var/root/Library/Application Support/...` and panics with
# "Permission denied". -H forces HOME to the target user's home.
console_user="$(/usr/bin/stat -f%Su /dev/console)"
if [[ -n "${console_user}" && "${console_user}" != "root" ]]; then
console_uid="$(/usr/bin/id -u "${console_user}")"
/bin/launchctl asuser "${console_uid}" \
/usr/bin/sudo -Hu "${console_user}" /usr/bin/open -a "Pult Agent" || true
fi
exit 0Make the script executable:
chmod +x ./scripts/postinstallWhat the postinstall does
The script runs as root during package installation. It:
- Detects the currently logged-in console user.
- Writes the bootstrap token to that user's
~/Library/Application Support/com.pult.agent/. - Launches the Pult Agent in the user's context if a user is logged in. The agent reads the token, transfers it to the user's Keychain, deletes the token file, and initiates enrollment.
If no user is logged in at install time, the postinstall logs a message and exits without staging the token. Most MDMs retry the install at the next user login or check-in.
Step 3: Build the Wrapper Package
Read the agent version from the bundle so the wrapper's version matches:
VERSION=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' \
"./payload/Pult Agent.app/Contents/Info.plist")
echo "Building wrapper for Pult Agent ${VERSION}"Build the package:
pkgbuild \
--root ./payload \
--scripts ./scripts \
--identifier com.pult.agent.mdm \
--version "${VERSION}" \
--install-location /Applications \
./pult-agent-mdm.pkgThe output pult-agent-mdm.pkg is the file you upload to your MDM.
Step 4: Verify Locally (Optional)
Test the package on a Mac before pushing it through your MDM. Either run the installer from the terminal:
sudo installer -pkg ./pult-agent-mdm.pkg -target /β¦or simply double-click pult-agent-mdm.pkg in Finder to launch the standard macOS Installer GUI.
Both paths run the postinstall script as root, identically to how an MDM would.
After install, confirm:
/Applications/Pult Agent.appexists.~/Library/Application Support/com.pult.agent/BOOTSTRAP_TOKENis briefly created and then automatically removed (the agent transfers the token to the Keychain and deletes the file).- The Pult Agent tray icon appears in your menu bar within a few seconds.
- In the Pult Dashboard at Settings β Presence β Device Authentication, a new Pending Device Request appears for this Mac.
To clean up before re-running:
sudo rm -rf "/Applications/Pult Agent.app"
rm -rf "${HOME}/Library/Application Support/com.pult.agent"
sudo pkgutil --forget com.pult.agent.mdmThe pkgutil --forget step removes the package receipt macOS keeps to track installed packages.
Without it, the system still considers the wrapper "installed" even after the app is gone, which can
prevent a clean re-install during testing.
To redo enrollment for an already-enrolled device, also reset the agent's stored credentials:
"/Applications/Pult Agent.app/Contents/MacOS/pult-agent" --reset-bootstrap-token
"/Applications/Pult Agent.app/Contents/MacOS/pult-agent" --cleanup-credentialsNext Steps
Upload the wrapper .pkg to your MDM:
- Jamf Pro
- For other macOS MDMs (Kandji, Mosyle, Workspace ONE, etc.), upload the wrapper as a standard package install. See MDM Deployment Overview.
Also configure Managed Login Items so the agent auto-starts at login and users can't disable it.
Troubleshooting
| Issue | Solution |
|---|---|
pkgutil --expand fails with "no such file" | Verify the path to the canonical .pkg and that the file isn't quarantined (xattr -dr com.apple.quarantine). |
hdiutil attach reports "resource busy" | A previous mount didn't detach. Run hdiutil detach ./dmg-mount (or whatever mountpoint you used) and try again. |
cp: Operation not permitted reading the .dmg | Remove the quarantine flag: xattr -dr com.apple.quarantine <file>.dmg. |
| Token file not consumed after install | Confirm a user is logged in. The agent reads the token only when running in user context. If installed at the login window, wait for the next login. |
| Agent doesn't appear in the menu bar | Confirm Pult Agent.app is at /Applications/Pult Agent.app. Launch manually with open -a "Pult Agent" to see if it starts. |
| Pending Device Request never appears in the Dashboard | Check the agent's logs in ~/Library/Logs/com.pult.agent/. Verify the bootstrap token hasn't expired in the Pult Dashboard. |
Last updated on May 3, 2026, 4:37 PM
Intune Deployment (Windows)
Deploy the Pult Agent on Windows via Microsoft Intune using PSAppDeployToolkit.
Jamf Pro Deployment (macOS)
Deploy the Pult Agent on macOS via Jamf Pro using a wrapper .pkg, an Extension Attribute for version detection, and a Smart Group-scoped Policy for continuous compliance.