A native macOS application that provides end users with a self-service portal for Fleet. It integrates with Fleet's orbit agent to give users direct access to device management features in a native window instead of a browser.
It also embeds the Fleet Platform SSO (PSSO) extension (FleetPSSOExtension.appex), which implements Apple's Platform Single Sign-On v2 + Password Mode so Fleet can create a Mac's local account and keep its password in sync with the user's IdP credentials. See Platform SSO extension below.
Heads up — two things named "Fleet Desktop": Fleet's agent already ships a tray/menu-bar component called Fleet Desktop (bundle ID
com.fleetdm.desktop, built fromorbit/cmd/desktop). This is a separate, standalone native app (bundle IDcom.fleetdm.fleet-desktop) distributed as its own.pkg. They use different bundle IDs and can coexist. Learn more.
- Native macOS app built with Swift and AppKit
- Universal binary supporting Apple Silicon (arm64) and Intel (x86_64)
- Self-service portal embedded in a native window via WKWebView
- Embedded Platform SSO extension for IdP-based local account creation and password sync
- Automatic token refresh handles hourly token rotation transparently
- Loading screen with Fleet logo while the portal loads
- File download support for
.mobileconfigprofiles and other files served by Fleet - MDM enrollment in a sheet over the device page, so end users can always get back to it
- Dark/light mode respects the user's system appearance
fleet://URL scheme for deep linking to Self-service, Policies, triggering refetches, and Update/Install all- Works with or without MDM — reads the Fleet URL from the fleetd configuration profile, or from the orbit launchd plist on Macs that aren't MDM-enrolled
- Code signed and notarized for secure distribution via
.pkginstaller
- macOS 13.0 (Ventura) or later for the app; the PSSO extension requires macOS 14.0+ (the password-sync feature targets macOS 26+)
- Fleet's orbit agent installed and enrolled. MDM enrollment is not required — see Configuration Sources for where the Fleet URL is read from
- The orbit identifier file must exist at
/opt/orbit/identifier
The signed, notarized .pkg is produced by CI (see CI/CD) and uploaded as a workflow artifact. To deploy:
- Via Fleet (Software): upload the
.pkgto Fleet as a software installer. Fleet Desktop will appear in the software catalog for deployment. - Manually: double-click the
.pkgand follow the installer.
The app is placed in /Applications with root:admin ownership and 755 permissions. On upgrades, the installer gracefully quits Fleet Desktop before installing and automatically relaunches it afterward.
Installing the app into /Applications is also what registers the bundled FleetPSSOExtension.appex with the system so it becomes selectable by a com.apple.extensiblesso configuration profile.
- Reads the Fleet URL from the fleetd configuration profile, or from the orbit launchd plist on Macs that aren't MDM-enrolled (see Configuration Sources)
- Reads the device token from
/opt/orbit/identifier(managed by orbit, rotates hourly) - Opens the self-service portal at
{FleetURL}/device/{token}/self-servicein an embedded browser window
The device token in /opt/orbit/identifier rotates every hour. Fleet Desktop handles this automatically:
- A background timer checks the identifier file every 60 seconds (and keeps the Dock badge current even when the window is closed)
- On HTTP 401/403 errors or error page detection, the app immediately checks for a new token and retries (up to 3 attempts with 5-second delays)
- Token refreshes are invisible to the user — the page silently reloads with the new token
Turn on MDM on the device page opens Fleet's enrollment page ({server}/enroll) in a sheet over the device page instead of replacing it:
- Close (or Esc) dismisses the sheet; the device page stays loaded underneath
- The sheet tightens the page's spacing and sizes itself to the content so every step is visible without scrolling. In a short window it extends below the window, staying on screen
- When end-user authentication is required, the IdP sign-in runs inside the sheet
- After the enrollment profile downloads, the sheet stays open so the remaining steps (installing the profile in System Settings) stay visible
When Fleet serves downloadable content (e.g., MDM enrollment profiles):
.mobileconfigfiles are downloaded and automatically opened for installation. Outside the enrollment sheet, the app then returns to the device page- All other file types (
.pkg,.dmg,.zip, etc.) are saved to~/Downloads
- App Transport Security (ATS) is enforced for the in-app WebView — the embedded portal requires HTTPS
- External links are restricted to
https,http, andmailtoschemes - The transparency link's redirect is resolved by the app and only an absolute destination is opened in the default browser, so the device token never reaches the browser
- During an SSO flow, navigations to a known authenticator app scheme (
com-okta-authenticator, used by Okta Verify for FastPass) are handed to that app; any other custom scheme ends the flow - Device tokens are percent-encoded and not exposed in error messages
- Downloaded files are only auto-opened if they are
.mobileconfigprofiles - The WebView uses a non-persistent data store (no cookies or cache persist between sessions)
- Mutable state is protected by a serial dispatch queue for thread safety
FleetPSSOExtension.appex is an Apple com.apple.AppSSO.idp-extension that implements Platform SSO v2 in Password Mode. The Fleet server provides the IdP endpoints; the extension registers the device's keys with Fleet and proxies password sign-in / key exchange through it.
The extension is bundled inside the app at Fleet Desktop.app/Contents/PlugIns/FleetPSSOExtension.appex and ships in the same .pkg.
The extension derives all of its endpoints from a single BaseURL value supplied in the ExtensionData dictionary of a com.apple.extensiblesso configuration profile (see fleet-sso-extension-example.mobileconfig):
<key>BaseURL</key> <string>https://fleet.example.com</string>From that it derives, under /api/mdm/apple/psso/:
POST /nonce— single-use nonces for token requestsPOST /registration— device key registrationPOST /token— password login / key request / key exchangeGET /jwks— Fleet's PSSO public keys
The Fleet server also serves an Apple App Site Association file at https://<hostname>/.well-known/apple-app-site-association containing an authsrv entry naming the extension's <TeamID>.<BundleID> — i.e. 8VBZ3948LU.com.fleetdm.fleet-desktop.pssoextension.
Because the same generic, CI-built extension must work against any Fleet server, the associated domain is not baked into the binary. Instead:
- The entitlement
com.apple.developer.associated-domainsships as an empty array, withcom.apple.developer.associated-domains.mdm-managedset totrue. - The actual
authsrv:domain (the configured Fleet server) is delivered at runtime by an MDM AssociatedDomains payload targeting the extension's bundle ID.
Both the host app and the extension carry restricted (Apple-managed) entitlements. These are not freely assertable — codesign only honors them when a Developer ID provisioning profile that grants them is embedded in the bundle (see Signing secrets).
| Bundle | Entitlement | Value |
|---|---|---|
| App + extension | com.apple.developer.associated-domains |
empty array (must exist) |
| App + extension | com.apple.developer.associated-domains.mdm-managed |
true |
| Extension only | com.apple.security.app-sandbox |
true |
| Extension only | com.apple.security.network.client |
true |
The host app is deliberately not sandboxed — it reads /opt/orbit/identifier and fleetd's configuration plists outside any container. App extensions are always sandboxed.
apps/fleet-desktop-macos/
├── FleetDesktop/
│ ├── FleetDesktopApp.swift # App delegate, main menu, entry point
│ ├── FleetService.swift # Config reading, token management, refresh timer
│ ├── BrowserWindow.swift # WKWebView window, loading overlay, downloads
│ ├── EnrollmentSheet.swift # Sheet that shows Fleet's MDM enrollment page
│ ├── Info.plist # App bundle metadata
│ ├── FleetDesktop.entitlements # Host-app entitlements (managed associated domains)
│ ├── AppIcon.icns # App icon
│ └── fleet-logo.png # Fleet logo for loading screen
├── FleetPSSOExtension/
│ ├── AuthenticationViewController.swift # Principal class (SSO request handler)
│ ├── AuthenticationViewController+PSSO.swift # Registration handler
│ ├── AuthenticationViewController+Shared.swift # Payload / key-ID / config helpers
│ ├── AuthenticationViewController+Networking.swift # URLSession against Fleet
│ ├── AuthenticationViewController+UserRegistration.swift # Interactive user registration (sign-in form → Fleet /token)
│ ├── PSSOLoginRequest.swift # Device-signed login JWS + encrypted password assertion
│ ├── RegistrationFormView.swift # Native username/password form
│ ├── Info.plist # appex metadata (NSExtension dict)
│ └── FleetPSSOExtension.entitlements # Extension entitlements
├── fleet-sso-extension-example.mobileconfig # Example com.apple.extensiblesso profile
├── build.sh # Compiles the universal app + appex
└── build-pkg.sh # Creates the .pkg installer
The CI workflow lives at .github/workflows/fleet-desktop-macos-build.yml.
The PSSO extension is built as a Foundation app extension with swiftc: there is no main(); the entry point is NSExtensionMain and the principal class is loaded from the appex Info.plist. swiftc's -module-name must match the module prefix in NSExtensionPrincipalClass (FleetPSSOExtension).
# Build the app (with the embedded extension)
./build.sh
# Run
open "build/Fleet Desktop.app"
# Build the (unsigned) .pkg installer
./build-pkg.shTo test end to end locally (a dev-signed app/extension against your local Fleet server), build.sh accepts TEAM_ID, APP_BUNDLE_ID, EXT_BUNDLE_ID, SIGNING_IDENTITY, APP_PROFILE, and EXT_PROFILE, and build-pkg.sh accepts APP_BUNDLE_ID and INSTALLER_SIGNING_IDENTITY — together they build and sign the app + .pkg under a non-production Fleet dev team. See the Local development: Apple Platform SSO guide for the full walkthrough (signing assets, AASA override, tunnel, profile, and logs).
| Variable | Default | Description |
|---|---|---|
ORBIT_ROOT_DIR |
/opt/orbit |
Override the orbit directory (changes where the identifier file is read from) |
| File | Key | Purpose |
|---|---|---|
/Library/Managed Preferences/com.fleetdm.fleetd.config.plist |
FleetURL |
Fleet server URL delivered via the fleetd MDM configuration profile. Checked first. |
/Library/LaunchDaemons/com.fleetdm.orbit.plist |
EnvironmentVariables.ORBIT_FLEET_URL |
Fleet server URL baked in by fleetctl package --fleet-url. Used when the managed preference is absent (Macs that aren't MDM-enrolled). |
/opt/orbit/identifier |
— | Device authentication token (rotates hourly) |
A scheme-less value such as fleet.example.com:8080 is treated as HTTPS, matching orbit. If neither plist provides a URL, the app displays an error and quits.
Note: A Mac enrolled with
--use-system-configwhose configuration profile was later removed keeps the URL only in/opt/orbit/fleet_url.txt, which only root can read, so the app can't use it. Re-deliver the profile or reinstall fleetd with--fleet-url.
Fleet Desktop registers the fleet:// URL scheme, allowing other tools and scripts to open specific pages:
| URL | Action |
|---|---|
fleet://self-service |
Opens the Self-service tab |
fleet://software |
Opens the Software tab |
fleet://policies |
Opens the Policies tab |
fleet://refetch |
Triggers a device refetch and opens the app |
fleet://update_all |
Opens Self-service and clicks "Update all" |
fleet://install_all |
Opens Self-service and clicks "Install all" |
fleet://install_all?category_id=## |
Opens Self-service filtered to a category and clicks "Install all" |
fleet://anything-else |
Brings the app to the foreground |
Both _ and - separators are accepted (e.g. fleet://install_all and fleet://install-all are equivalent).
Example usage from a script or terminal:
open fleet://self-service
open fleet://refetch
open fleet://install_all
open "fleet://install_all?category_id=5".github/workflows/fleet-desktop-macos-build.yml runs on pull requests touching apps/fleet-desktop-macos/**, on push to main, and via manual dispatch. It:
- Compiles a universal binary (arm64 + x86_64) for the app and the extension, and assembles the
.appexinside the.app - Embeds the Developer ID provisioning profiles into the app and extension bundles
- Code signs inside-out — the extension first, then the host app — each with its own entitlements
- Packages into a
.pkginstaller with a custom distribution XML - Signs the
.pkgwith Fleet's Developer ID Installer certificate - Notarizes with Apple and staples the ticket
- Uploads the signed
.pkgas a workflow artifact (retained for 30 days)
The workflow always signs and notarizes. Runs without access to the signing secrets — fork pull requests, or any run before the provisioning profiles have been added — fail rather than producing an unsigned artifact.
The workflow reuses the Developer ID certificate secrets already used by Fleet's other macOS build workflows, plus two new provisioning-profile secrets required for the extension's restricted entitlements:
| Secret | Purpose |
|---|---|
APPLE_APPLICATION_CERTIFICATE / ..._PASSWORD |
Developer ID Application certificate (.p12, base64) + password |
APPLE_INSTALLER_CERTIFICATE / ..._PASSWORD |
Developer ID Installer certificate (.p12, base64) + password |
APPLE_USERNAME / APPLE_PASSWORD |
Apple ID + app-specific password for notarization |
APPLE_TEAM_ID |
Apple Developer Team ID |
KEYCHAIN_PASSWORD |
Temporary CI keychain password |
APPLE_FLEET_DESKTOP_APP_PROFILE_B64 |
base64 of the Developer ID provisioning profile for com.fleetdm.fleet-desktop |
APPLE_PSSO_EXT_PROFILE_B64 |
base64 of the Developer ID provisioning profile for com.fleetdm.fleet-desktop.pssoextension |
The Developer ID certificate identities (SHA-1) are pinned in the workflow env block, matching the identities used by Fleet's orbit and fleetd-base builds.
The com.apple.developer.associated-domains* entitlements are Apple-managed: codesign will not honor them without a Developer ID provisioning profile that grants them. Profiles are not committed — they are team/cert-bound build inputs that expire, so they're stored as the base64 secrets above (the same pattern as the .p12 certs).
Under Fleet's Apple Developer team (8VBZ3948LU, the team that owns the pinned Developer ID certificates):
- Register two App IDs:
com.fleetdm.fleet-desktop(host app)com.fleetdm.fleet-desktop.pssoextension(extension)
- Enable the Associated Domains and MDM Managed Associated Domains capabilities on both App IDs.
- Create a Developer ID provisioning profile (distribution, platform macOS) for each App ID. Select the same Developer ID Application certificate that CI signs with — SHA-1
604D877399AAEB7630A78B84F288E2D28A2EDE42(the identity pinned in the workflow). Fleet has more than one "Developer ID Application: Fleet Device Management Inc" certificate; a profile generated against the wrong one will sign and notarize successfully but get SIGKILLed by AMFI at launch, because AMFI requires the signing cert to appear in the profile'sDeveloperCertificates. TheVerify profiles authorize the signing certificateworkflow step guards against this. - base64-encode each downloaded
.provisionprofileand store them asAPPLE_FLEET_DESKTOP_APP_PROFILE_B64andAPPLE_PSSO_EXT_PROFILE_B64:base64 -i FleetDesktop_DeveloperID.provisionprofile | pbcopy # → APPLE_FLEET_DESKTOP_APP_PROFILE_B64 base64 -i FleetPSSOExtension_DeveloperID.provisionprofile | pbcopy # → APPLE_PSSO_EXT_PROFILE_B64
Re-encode and update the secrets when a profile expires or the signing certificate is rotated. To inspect a profile — its entitlements and, crucially, the certs it authorizes — dump it with security cms -D -i <profile>.provisionprofile; the DeveloperCertificates array must contain the CI signing cert above.
.github/workflows/release-fleet-desktop-macos.yml publishes a tagged, signed, notarized build to https://download.fleetdm.com/fleet-desktop-macos/v<version>/. Releases are immutable — a version that already exists on download.fleetdm.com cannot be overwritten. No GitHub Release is created; the git tag is the release marker.
- Bump
CFBundleShortVersionString(andCFBundleVersion) inFleetDesktop/Info.plistand merge tomain. - Tag the commit and push the tag:
git tag fleet-desktop-macos-v<version> git push origin fleet-desktop-macos-v<version>
- In the Actions tab, run Release Fleet Desktop (macOS), selecting the tag in the "Use workflow from" dropdown.
The workflow fails before building if the selected ref isn't a fleet-desktop-macos-v* tag on main, if the tag version doesn't match Info.plist, or if that version is already uploaded. It builds via the CI workflow above, uploads the pkg plus a meta.json (version, fleet_desktop_pkg_sha256, fleet_desktop_pkg_url), then downloads the pkg back from the public URL and verifies its SHA256 before succeeding. The checksum and URLs are written to the run summary.
The testing input uploads to download-testing.fleetdm.com instead of production — use it for the first run after changing the workflow.
Licensed under the MIT Expat license via the repository root LICENSE.