Skip to content

MDM Rollout

Every MDM rollout has the same three steps:

  1. Install the binary — push install-mdm-template.sh (macOS + Linux) or install-mdm-template-win.ps1 (Windows) via your MDM tool. Get it from the PointFive app or your PointFive contact.
  2. Drop the enrollment manifest — at the system path so every user on the machine inherits the binding. Generate the manifest in the PointFive app.
  3. Verifytokenshift doctor reports back.

Paths used below:

  • macOS / Linux system manifest: /etc/tokenshift/enrollment.json
  • Windows system manifest: %PROGRAMDATA%\tokenshift\enrollment.json

See Enrollment for what’s in the manifest and why the per-user path takes precedence when both exist.

Two policies, scoped to the same smart group:

  1. Install — push install-mdm-template.sh as a script payload (get it from the PointFive app or your PointFive contact). Paste your API key into the marked block before uploading.
  2. Configure — push the enrollment manifest as a file to /etc/tokenshift/enrollment.json (mode 0644, owned by root).

Run tokenshift doctor as a self-service “verify” policy so users (and your support team) can confirm the binding without admin help.

JumpCloud Commands work well for both steps:

  1. Push a shell command that installs the binary user-owned — get install-mdm-template.sh (macOS + Linux) from the PointFive app (or your PointFive contact) and set TENANT_ID at the top to your tenant UUID before pushing (the script aborts while the YOUR-TENANT-UUID-HERE placeholder is unedited). Paste your API key into the marked block at the top of the same script, replacing PASTE-YOUR-API-KEY-HERE and keeping the quotes:

    Terminal window
    ######## PASTE YOUR API KEY FROM THE TOKENSHIFT CONSOLE HERE ########
    export TOKENSHIFT_BEARER_TOKEN="${TOKENSHIFT_BEARER_TOKEN:-PASTE-YOUR-API-KEY-HERE}"
    #####################################################################

    Tenant UUID and API key are then both in the Command body, and that body is the only thing to keep current. The script aborts at exit 30 if either placeholder is left unedited, or if the key is not a well-formed tenant API key (at least 82 characters, p5ts_ prefix, letters, digits and underscores only).

    It installs every eligible user on the machine, each into their own ~/.tokenshift/bin as that user, and wires PATH via ~/.tokenshift/env — nothing under /usr/local/bin. A plain curl | tar -C /usr/local/bin leaves a root-owned binary that can’t self-update.

    Note what pasting costs on JumpCloud specifically: the key sits in cleartext in the saved Command, readable by anyone with console access to it, and it has to be re-edited in every Command carrying the script whenever the key rotates. JumpCloud can hold it as a Custom Variable with the Secret Variable toggle instead — encrypted at rest, substituted at runtime — and the installer honours an environment-supplied key ahead of the pasted one, so export TOKENSHIFT_BEARER_TOKEN={{tokenshift_bearer_token}} above the script still works. If your console has broad read access or you rotate keys often, that is the safer option.

  2. Push the manifest contents via a second Command that writes /etc/tokenshift/enrollment.json with tee.

Verify with a third Command that runs tokenshift doctor and captures output.

macOS-side: push install-mdm-template.sh as a shell script assigned to a device group. Intune for Mac runs scripts as root, so writing /etc/tokenshift/enrollment.json is straightforward — either inline in the same script or via a separate configuration profile.

Windows-side: push install-mdm-template-win.ps1 as a platform script (Devices → Scripts), assigned in user context — the installer is per-user and needs no admin. Run it in the 64-bit PowerShell host; the script relaunches itself 64-bit if Intune starts it under 32-bit, so either setting works. Set TOKENSHIFT_TENANT_ID and TOKENSHIFT_BEARER_TOKEN in the environment, or paste the key into the marked block before uploading. The enrollment manifest goes to %PROGRAMDATA%\tokenshift\enrollment.json via a separate device-context script or a configuration profile.

A playbook does the same steps natively — no install-mdm-template.sh invocation needed: create the user-owned ~/.tokenshift/bin store, download and unpack the tarball into it, wire PATH via a /etc/profile.d drop-in, drop the enrollment manifest to /etc/tokenshift/enrollment.json, then run tokenshift doctor to verify. A ready-to-adapt playbook is available via the PointFive app or your PointFive contact.

A root-triggered run of install-mdm-template.sh (MDM push, Ansible, or sudo) installs every eligible user on the machine, each into their own ~/.tokenshift and each enrolled as themselves. There is no “primary user” to resolve. An account is eligible when all of these hold, and is skipped with a logged reason otherwise:

  • the account resolves, and its UID is in the human range — /etc/login.defs UID_MIN/UID_MAX on Linux (default 1000–60000), ≥501 on macOS
  • it has a real login shell (not nologin / false)
  • its home directory, read from the account record, exists and is owned by it

Exclude accounts with a space- or comma-separated denylist. Each entry is a shell glob matched against the username — svc-* excludes every service account, a literal name excludes just that one:

Terminal window
sudo TOKENSHIFT_EXCLUDE_USERS="svc-*,contractor" bash ./install-mdm-template.sh

A single run installs at most 10 users, overridable with TOKENSHIFT_MAX_USERS. Beyond the cap the first 10 still install and the run exits non-zero: an endpoint with that many eligible accounts is a shared machine rather than the single-owner workstation this flow is built for, and it should surface for a human decision instead of installing silently.

The run exits non-zero if no user was eligible, if the cap was exceeded, or if an installed user hit a fatal step (binary placement, telemetry, enroll). A skipped account is not a failure, and hook-wiring or doctor warnings never fail the run.

Windows works the other way round. install-mdm-template-win.ps1 installs the account that runs it and nothing else — no eligibility scan, no TOKENSHIFT_EXCLUDE_USERS, no TOKENSHIFT_MAX_USERS, and no root step to reclaim. Cover a shared machine by assigning the script in user context so each user’s own session runs it. Service SIDs (S-1-5-18/-19/-20) are refused.

  • The installer wires every supported and detected agent on the machine.
  • Detection is per user: an agent is detected once its own config directory exists.
  • An agent that is not installed yet is never wired — there is no way to wire ahead.
  • Wiring is additive and idempotent. Re-running tokenshift install picks up an agent that appeared since the last run and leaves the already-wired ones untouched.
  • So there is nothing to wire “per agent” — when a developer adopts a new agent, re-run the same install.

When nothing is detected, install prints the per-agent detection status and still exits 0, so a fleet push is never aborted by a machine that has no agent yet.

The run result is the exit code and stdout — Jamf policy logs, Intune script output and Ansible all capture both, so nothing extra is needed to see whether an install succeeded.

codemeaning
0every gated-in user installed
10 / 30 / 40 / 45preflight / config / download / verify
20no eligible user on the device
25more eligible users than the cap; the first N installed
60a leftover /usr/local/bin/tokenshift survived removal
125at least one installed user hit a fatal step

Windows shares 10 / 30 / 40 / 45 for preflight / config / download / verify, and adds 50 for binary placement, 80 for telemetry, 90 for enroll, and 115 for the closing self-update round-trip. The multi-user codes (20, 25, 60, 125) never appear — one run installs one user, so a per-user step failure is the run’s own exit code rather than rolling up into 125 the way the sh arm does.

For inventory — asking a device days later whether it is installed, outside a run — each installed user carries the complete run log at ~/.tokenshift/install-<stamp>.log and a single-line verdict at ~/.tokenshift/install-status. There is deliberately no device-wide file: an inventory script already runs as root and can read any home, and a shared path in a world-writable directory could be pre-created by a local user to report a device as compliant when nothing was installed.

On Windows the same pair lands in %USERPROFILE%\.tokenshift.

A Jamf extension attribute, Intune custom attribute, or Workspace ONE sensor reads them with:

#!/bin/sh
# newest per-user verdict on this device; absent means not installed
v=$(for h in /Users/*/ /home/*/; do cat "$h/.tokenshift/install-status" 2>/dev/null; done \
| grep . | tail -1)
echo "<result>${v:-not-installed}</result>"

The Windows equivalent, for an Intune custom attribute script:

Terminal window
$v = Get-ChildItem $env:SystemDrive\Users -Directory -ErrorAction SilentlyContinue |
ForEach-Object { Get-Content "$($_.FullName)\.tokenshift\install-status" -ErrorAction SilentlyContinue } |
Select-Object -Last 1
if ($v) { $v } else { 'not-installed' }

Each verdict ends with sig=verified|skipped|rejected|unchecked, on every OS — so the same query also reports whether the artifact’s signature was checked on that endpoint. See Installation & distribution → Verification at install time.

The manifest is read once at session start and cached for the lifetime of the process. Pushing a new manifest takes effect on the next agent session (or the next tokenshift invocation). No restart required for already-running shells beyond the agent itself.

Terminal window
# remove the binary store and PATH wiring (run as the user)
rm -rf "$HOME/.tokenshift/bin"
rm -f "$HOME/.tokenshift/env" /usr/local/bin/tokenshift
sudo rm -f /etc/profile.d/tokenshift.sh # if the Ansible playbook was used
# remove the system manifest
rm /etc/tokenshift/enrollment.json

On Windows, run it as the user:

Terminal window
tokenshift uninstall --purge

tokenshift uninstall --purge removes hooks and per-user state but never touches the system-deployed manifest — /etc/tokenshift/enrollment.json or %PROGRAMDATA%\tokenshift\enrollment.json stays your MDM’s responsibility.