# PMG MDM Scripts (macOS)

Deploy and remove [PMG](https://github.com/safedep/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 `pmg` binary (`/usr/local/bin` or Homebrew). Needs root.
- **User scope**: config (`~/Library/Application Support/safedep/pmg`), aliases (`~/.pmg.rc` and 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 via `sudo -u`. Keychain steps run in the logged-in user's session via `launchctl 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

```sh
# 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:

1. Installs or updates `pmg` (Homebrew if present, otherwise the GitHub release tarball with SHA-256 verification).
2. If the package includes a `config.yml`, installs it as the globally managed config (see below).
3. Runs `pmg setup install` for each target user to create aliases and shims (and a per-user config, unless a globally managed config is active).
4. With `SAFEDEP_API_KEY` and `SAFEDEP_TENANT_ID` set, 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. Run `pmg cloud login` in their session once they log in.

## Uninstall

```sh
sudo ./pmg_uninstall_macos.sh
```

For each target user, the uninstall script:

1. Runs `pmg setup remove` to strip shell aliases and PATH shims.
2. Deletes the config directory, cache directory, `~/.pmg`, `~/.pmg.rc`, and `~/.local/bin/pmg`.
3. Runs `pmg cloud logout` to 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. Set `global_lockdown: true` in the bundled `config.yml` to forbid those overrides. See [Globally Managed Configuration](../../docs/config.md#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: true` in the bundled `config.yml`. The install script skips the per-user `pmg config set` (a managed config refuses it) but still stores each logged-in user's credentials in the Keychain.
- Install copies the bundled `config.yml` to 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`. Set `PMG_KEEP_GLOBAL_CONFIG=1` to 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:

```sh
#!/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 run `pmg setup cert install` in 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

```sh
# From this directory
shellcheck -x lib_macos.sh pmg_setup_install_macos.sh pmg_uninstall_macos.sh
```
