Pult Presence Docs
Pult AgentMDM Deployment

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, and hdiutil, 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 .pkg or the .dmg works.
  • 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 payload

Then 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-mount

If 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 ./scripts

Save 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 0

Make the script executable:

chmod +x ./scripts/postinstall

What the postinstall does

The script runs as root during package installation. It:

  1. Detects the currently logged-in console user.
  2. Writes the bootstrap token to that user's ~/Library/Application Support/com.pult.agent/.
  3. 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.pkg

The 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:

  1. /Applications/Pult Agent.app exists.
  2. ~/Library/Application Support/com.pult.agent/BOOTSTRAP_TOKEN is briefly created and then automatically removed (the agent transfers the token to the Keychain and deletes the file).
  3. The Pult Agent tray icon appears in your menu bar within a few seconds.
  4. 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.mdm

The 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-credentials

Next Steps

Upload the wrapper .pkg to your MDM:

Also configure Managed Login Items so the agent auto-starts at login and users can't disable it.

Troubleshooting

IssueSolution
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 .dmgRemove the quarantine flag: xattr -dr com.apple.quarantine <file>.dmg.
Token file not consumed after installConfirm 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 barConfirm 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 DashboardCheck 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

On this page