Enroll a VM in MDM
This guide shows you how to enroll a Pomme VM in a mobile device management
(MDM) server with an enrollment profile. The pomme mdm command works from any
VM state: it creates the VM if it doesn’t exist, finishes any incomplete work,
prepares only the security changes that enrollment needs, enrolls, and then
restores the VM.
pomme mdm uses Pomme’s profile-based enrollment flow. It doesn’t implement
Apple User Enrollment or Automated Device Enrollment.
Before you begin
Section titled “Before you begin”- Get an MDM enrollment profile (a
.mobileconfigfile) from your MDM server and save it on the host. - If the VM doesn’t exist yet, decide how to create it. Creating from a template avoids restoring macOS again. For details, see Use templates.
- End any active terminal sessions in the VM. Recovery security workflows don’t start while a terminal session is active.
- If the VM has an owner account, make its credentials available. For details, see Provide owner authorization.
Preview the enrollment plan
Section titled “Preview the enrollment plan”Before you change anything, see what Pomme would do:
pomme mdm VM_NAME --profile PROFILE_PATH --dry-runReplace the following:
VM_NAME: the name of the VM to enroll.PROFILE_PATH: the host path to the enrollment profile.
The dry run reports the detected VM state, the planned steps, and any blockers or warnings, without changing the VM.
Enroll a VM
Section titled “Enroll a VM”To enroll a VM, run the following command:
pomme mdm VM_NAME --profile PROFILE_PATHReplace the following:
VM_NAME: the name of the VM to enroll.PROFILE_PATH: the host path to the enrollment profile.
Pomme works out what’s missing from the VM’s retained state and runs only those steps, in order, under one exclusive lease on the VM:
- Creates the VM if it doesn’t exist.
- Resumes an incomplete creation, which installs and verifies the guest agent.
- Finishes a retained
pomme siporpomme amfioperation, using that operation’s own action and final state. - Enrolls the VM: boots normal macOS, verifies the pinned guest agent, reads the System Integrity Protection (SIP) and Apple Mobile File Integrity (AMFI) settings, turns off only what enrollment needs, installs the profile, and verifies the result.
By default, Pomme then restores the original SIP and AMFI settings and the VM’s original run state.
If the VM has no owner account, the SIP step creates the pomme owner. Pomme
asks you to confirm first; to confirm without a prompt, add --force.
Create the VM as part of enrollment
Section titled “Create the VM as part of enrollment”If the VM doesn’t exist, add creation flags to the pomme mdm command. Pomme
ignores these flags when the VM already exists and reports a
creationOptionsIgnored warning.
For example, to clone a new VM from a template and enroll it in one command, run the following:
pomme mdm VM_NAME --profile PROFILE_PATH \ --from-template TEMPLATE_NAME --memory 4GB --forceReplace the following:
VM_NAME: the name of the VM to create and enroll.PROFILE_PATH: the host path to the enrollment profile.TEMPLATE_NAME: the name of an installed template.
You can use one of the following sources to create the VM:
--from-template TEMPLATE_NAME: clone an installed template. The template supplies the disk size.--version VERSIONor--latest: install a macOS version or build.--restore-image PATH: install from a local IPSW file.
You can also set --memory (default 8GB), --disk-size (default 60GB),
and --boot, which accepts normal (the default) or none.
Choose the enrollment mode
Section titled “Choose the enrollment mode”Pomme supports the following enrollment modes:
| Mode | Result |
|---|---|
supervised |
Supervised, user-approved enrollment. This is the default. |
unapproved |
Enrollment without user approval or supervision. |
To request enrollment without approval, add --enrollment-mode unapproved:
pomme mdm VM_NAME --profile PROFILE_PATH --enrollment-mode unapprovedTo upgrade an existing unapproved enrollment to supervised, run pomme mdm
again with the same profile and the default mode. Pomme reuses a matching
enrollment and avoids security changes when the request is already satisfied.
Pomme rejects the following requests:
- A profile or server that conflicts with the installed enrollment.
- A downgrade from an approved or supervised enrollment to
unapproved.
Keep SIP and AMFI off after enrollment
Section titled “Keep SIP and AMFI off after enrollment”By default (--final-security restore), Pomme turns SIP and AMFI back on if
enrollment turned them off. To leave the settings that enrollment turned off
switched off, add --final-security disabled:
pomme mdm VM_NAME --profile PROFILE_PATH --final-security disabledSettings that enrollment didn’t change stay as they were. To turn the settings back on later, restore AMFI first and then SIP:
pomme amfi enable VM_NAMEpomme sip enable VM_NAMEUnderstand the server trust check
Section titled “Understand the server trust check”Before Pomme touches the VM, the host checks the MDM server named in the profile:
-
If the host reaches the server and neither Apple’s root certificates nor the certificate payloads in the profile validate it, a command that could still install the profile stops with the following error:
Neither Apple's roots nor the profile's certificates validate the MDM server. Fix the profile, or pass --skip-server-preflight if the guest already trusts it.If the guest already trusts the server, add
--skip-server-preflightto skip this host check. -
If the host can’t reach the server, Pomme only warns. The guest checks the server again before it enrolls.
If only the certificates in the profile validate the server, Pomme first
installs those certificates as a separate configuration profile with the
identifier com.github.weswhet.pomme.mdm-trust.PROFILE_UUID, where
PROFILE_UUID is the UUID of your enrollment profile. This lets a server with
a private certificate authority work without manual trust setup.
The trust profile stays installed after enrollment. To remove it when you no longer need it, run the following command in the guest:
pomme exec VM_NAME -- /usr/bin/profiles remove \ -identifier com.github.weswhet.pomme.mdm-trust.PROFILE_UUIDReplace the following:
VM_NAME: the name of the VM.PROFILE_UUID: the UUID of the enrollment profile.
Set the guest staging path
Section titled “Set the guest staging path”Pomme copies the profile into the guest before it installs it. To choose the
temporary guest location, add --guest-path with an absolute guest path:
pomme mdm VM_NAME --profile PROFILE_PATH --guest-path GUEST_PATHReplace GUEST_PATH with an absolute path in the guest.
Resume after a failure
Section titled “Resume after a failure”Each step keeps its own journal. If a step fails, Pomme exits with a nonzero status and keeps the current SIP and AMFI settings, the VM’s run state, the staged files, and the journals. It doesn’t poll for enrollment or clean up automatically.
To resume, repeat the same command with the same profile, --enrollment-mode,
and --final-security values. If you change any of them while an enrollment is
unfinished, Pomme stops with the following error:
An unfinished MDM enrollment uses a different profile, --enrollment-mode, or --final-security. Repeat it with its original values.After you rebuild and install Pomme, you can repeat the same command to inspect and continue retained work. Each enrollment attempt stages its helper from the current host command-line tool, so a fix to guest enrollment doesn’t require replacing the persistent guest agent.
Some states can’t be resumed safely. In these cases, Pomme stops before any change and names the blocker. The following are examples:
- First-boot provisioning was dispatched without a receipt. Recreate the VM.
- The VM’s pinned guest agent lacks the capabilities that MDM requires. Recreate the VM with a current Pomme build.
- An enrollment outcome is unknown. Pomme doesn’t repeat the installation without evidence.
Read structured output
Section titled “Read structured output”With --format json, the result includes the following fields:
| Field | Description |
|---|---|
steps |
Each step that ran, with its status. |
result.readiness |
The detected state, the planned steps, blockers, and warnings. |
result.serverTrust |
The result of the MDM server trust check. |
result.failureStage |
The guest stage that failed, when the enrollment helper reports it. |
result.diagnostics |
Elapsed times, numeric status codes, and Keychain status flags from the helper. Diagnostics never include profile contents or credentials. |
result.failureStatePreserved |
On failure, whether Pomme kept the failure state for inspection. |
result.retryAllowed |
On failure, whether repeating the command can continue the work. |
To include more detailed enrollment diagnostics, add --debug:
pomme mdm VM_NAME --profile PROFILE_PATH --debug --format jsonFor the general output format, see Structured output.
Downgrade considerations
Section titled “Downgrade considerations”Pomme records MDM enrollment in a schema 6 journal. Pomme reads older schema 3
through 5 journals with --final-security restore and rewrites them as schema
6 on their next update. An older Pomme build can’t read a schema 6 journal, so
finish retained MDM work before you downgrade Pomme.