Introduction

Building a Windows agent is relatively straightforward—MSI installer, Windows Service, done. macOS is a different beast entirely. Apple’s security requirements mean every piece of software needs to be signed, notarized, and packaged in very specific ways. This is the story of how we built, signed, and ship the InventoryOS macOS agent.

Why macOS Is Different

If you’ve shipped on Windows before, the macOS story will feel unfamiliar. Apple tightened the screws with Gatekeeper in Catalina, and it’s only gotten stricter since. Here’s what you’re up against:

The bottom line: you can’t just compile a binary and zip it up. You need certificates, signing, notarization, and a proper .pkg installer. Here’s how we do it.

The Apple Developer Program

First, you need an Apple Developer account. That’s $99/year. No way around it.

From there, you generate two key certificates in the Certificates, Identifiers & Profiles section:

We store these certificates securely in CI/CD as encrypted secrets in GitHub Actions. Never commit them or leave them on a developer machine. If a cert leaks, Apple can revoke it, and you’re back to square one.

Code Signing the Agent

The InventoryOS agent is a background daemon written to run as a LaunchDaemon. It has no UI. It lives in /Library/Application Support/InventoryOS/ and starts at boot. Signing it is straightforward once you know the flags.

Conceptually, you run codesign on the binary with your Developer ID Application certificate. The important bits:

After signing, verify with:

codesign --verify --verbose /path/to/InventoryOS-Agent

If that passes, you’re good. If not, you’ve got an unsigned nested binary or a broken entitlements setup.

Notarization

Notarization is Apple’s automated security check. They scan your binary for malware, check the signature, and attach a “ticket” that tells Gatekeeper the app is safe. Users never see this unless something goes wrong.

We submit via notarytool (which replaced altool in Xcode 14+). You upload the signed .pkg, wait for Apple’s response (usually 2–5 minutes, sometimes longer under load), and then staple the notarization ticket to the package so it works offline:

xcrun notarytool submit InventoryOS-Agent.pkg --wait
xcrun stapler staple InventoryOS-Agent.pkg

What happens when notarization fails? Common culprits:

Apple returns a JSON payload with the exact issue. Fix it, re-sign, resubmit. Rinse and repeat until it passes.

Packaging as a .pkg

macOS uses .pkg installers for enterprise deployment, not .dmg. DMGs are fine for manual drag-to-Applications installs, but Jamf, Munki, and MDM tools expect a proper package.

We use two tools:

The postinstall script does the heavy lifting: it registers the LaunchDaemon plist, starts the agent, and sets the enrollment key (injected at build time per-org). The agent then phones home and registers with the InventoryOS backend.

Finally, sign the .pkg with the Developer ID Installer certificate. That signature is what Gatekeeper checks when a user (or MDM) runs the installer.

Distribution

We distribute the macOS agent in three ways:

Lessons Learned

After a few iterations, here’s what we wish we’d known from day one:

Conclusion

macOS code signing and notarization is painful the first time, but once your pipeline is solid, it’s seamless. The InventoryOS macOS agent ships as a signed, notarized .pkg that installs cleanly on any Mac from Monterey onwards. No Gatekeeper warnings, no “unidentified developer” popups—just a clean, silent install.