* fix: Reject overriding managed flags * fix: Lockdown overrides when global config present * fix: Opt-in lock-down enforcement for global config * fix: Code review fixes * fix: Code review fixes * fix: Code review fixes
6.4 KiB
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
- 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