* feat: Add opt-in support for proxy CA cert installation * fix: Code review fixes * fix: Code review fixes * fix: Code review fixes * fix: Code review fixes * fix: Code review fixes * docs: Add limitation for MacOS MDM script
PMG MDM Scripts (macOS)
Deploy and remove PMG on macOS fleets through an MDM (Jamf, Mosyle, Kandji, Intune).
| File | Purpose |
|---|---|
pmg_setup_install_macos.sh |
Install the binary and configure every user (config, aliases, shims, optional cloud sync) |
pmg_uninstall_macos.sh |
Remove per-user state, Keychain credentials, and the binary |
lib_macos.sh |
Shared helpers. Deploy it alongside the other two. |
config.yml (optional) |
When present in the package, the install script deploys it as the machine-wide globally managed config |
The install and uninstall scripts source ./lib_macos.sh from their own directory, so ship all three together. Zip the mdm/ folder as the MDM payload. Add a config.yml to the folder to deploy a globally managed config (see below).
Execution model
PMG writes two kinds of state, and the scripts handle each:
- Machine scope: the
pmgbinary (/usr/local/binor Homebrew). Needs root. - User scope: config (
~/Library/Application Support/safedep/pmg), aliases (~/.pmg.rcand shell rc edits), PATH shims (~/.pmg/bin), and login Keychain credentials. Runs as the user. Keychain also needs the user's GUI session.
The scripts detect how the MDM invoked them:
- As root (typical MDM): the script installs or removes the binary machine-wide, then runs the per-user steps for every local human account (UID ≥ 500 with a home under
/Users), each in its own context viasudo -u. Keychain steps run in the logged-in user's session vialaunchctl asuser. - As the logged-in user (an MDM "run as current user" payload, or a person running it by hand): the per-user steps cover that user, and machine-scope steps elevate with
sudo.
Homebrew can't run as root, so the script runs brew commands as the owner of the Homebrew install.
Install
# Binary + per-user setup, no cloud sync
sudo ./pmg_setup_install_macos.sh
# Also enable SafeDep Cloud sync (stores credentials in the logged-in user's Keychain)
sudo SAFEDEP_API_KEY=... SAFEDEP_TENANT_ID=... ./pmg_setup_install_macos.sh
The install script:
- Installs or updates
pmg(Homebrew if present, otherwise the GitHub release tarball with SHA-256 verification). - If the package includes a
config.yml, installs it as the globally managed config (see below). - Runs
pmg setup installfor each target user to create aliases and shims (and a per-user config, unless a globally managed config is active). - With
SAFEDEP_API_KEYandSAFEDEP_TENANT_IDset, enables cloud sync and stores credentials in the logged-in user's Keychain. It skips users who aren't logged in, since there's no session to write to. Runpmg cloud loginin their session once they log in.
Uninstall
sudo ./pmg_uninstall_macos.sh
For each target user, the uninstall script:
- Runs
pmg setup removeto strip shell aliases and PATH shims. - Deletes the config directory, cache directory,
~/.pmg,~/.pmg.rc, and~/.local/bin/pmg. - Runs
pmg cloud logoutto clear Keychain credentials for the logged-in user. Other users' credentials clear on their next login.
It then removes the machine-wide binary via brew uninstall, or by deleting /usr/local/bin/pmg and /opt/homebrew/bin/pmg. It also removes the globally managed config if present (set PMG_KEEP_GLOBAL_CONFIG=1 to keep it).
Globally managed config
Include a config.yml next to the scripts to centrally manage PMG configuration. When that file is present at /Library/Application Support/safedep/pmg/config.yml, PMG treats it as authoritative and ignores every user's own config. pmg config set and pmg config edit refuse, and the file is root-owned (0644), so it is not user-writable.
- By default the global config is an overridable baseline: users can still override its values at runtime with
PMG_*env vars and CLI flags. Setglobal_lockdown: truein the bundledconfig.ymlto forbid those overrides. See Globally Managed Configuration for the full behaviour. - The file can be partial. Keys it does not set fall back to PMG's built-in defaults, not to user values.
- To enable cloud sync, set
cloud.enabled: truein the bundledconfig.yml. The install script skips the per-userpmg config set(a managed config refuses it) but still stores each logged-in user's credentials in the Keychain. - Install copies the bundled
config.ymlto the global path before configuring users, so each user's setup skips writing a per-user config. - Re-deploying the package overwrites the global config, keeping it in sync with the package.
- Uninstall removes the global config whenever it is present, regardless of whether the uninstall package ships a
config.yml. SetPMG_KEEP_GLOBAL_CONFIG=1to keep it.
Only the config file is global. Per-user runtime state (logs, cloud sync database, sandbox profiles) stays under each user's ~/Library.
Environment variables
| Variable | Effect |
|---|---|
SAFEDEP_API_KEY |
SafeDep Cloud API key (install only; with the tenant ID, enables cloud sync) |
SAFEDEP_TENANT_ID |
SafeDep Cloud tenant ID (install only) |
PMG_CONFIG_DIR |
Override the config directory location (uninstall cleanup honors it) |
PMG_CACHE_DIR |
Override the cache directory location (uninstall cleanup honors it) |
PMG_KEEP_GLOBAL_CONFIG |
Uninstall only: when set, keep the globally managed config instead of removing it |
Jamf example
Upload the mdm/ folder as a script payload, or a package that drops the three files together, then invoke the entry script. Jamf runs scripts as root, which covers fleet-wide, multi-user deployment:
#!/bin/sh
cd "$(dirname "$0")"
SAFEDEP_API_KEY="$4" SAFEDEP_TENANT_ID="$5" ./pmg_setup_install_macos.sh
$4 and $5 are Jamf script parameters. Adjust them to your configuration.
Limitations
- Installing PMG's MITM CA into the trust store (
pmg setup cert install) is not supported via MDM on macOS. Adding a trusted root to the login keychain requires interactive authorization from the user's GUI session, which an MDM deployment cannot supply. Have each user runpmg setup cert installin their own session when they need it (only needed for tools that ignore the proxy's CA environment variables, such as Go on macOS). - The scripts can't read or write the Keychain for a user who isn't logged in, since no session exists to reach. They report and skip those users; configure them in their session when they log in. After an uninstall, their credentials clear on next login.
- Machine-scope steps under a non-root invocation need
sudo. Without passwordless sudo in a non-interactive context, they fail with an error instead of hanging. - macOS only. The scripts exit on other platforms.
Development
# From this directory
shellcheck -x lib_macos.sh pmg_setup_install_macos.sh pmg_uninstall_macos.sh