Skip to content

Installation

Rod Christiansen edited this page Oct 8, 2026 · 2 revisions

Installation

What the package puts on disk, what the install scripts do, and how the agent is scheduled. Everything here is what build.sh writes into the pkg's preinstall and postinstall.

Requirements

  • macOS 14 (Sonoma) or later — the Swift package declares .macOS(.v14).
  • Root. managedreportsrunner exits immediately with ERROR: You must run this as root! for any other uid.
  • osquery. The client uses whatever official osquery the Mac already has (see Architecture for how it is found and checked). The postinstall downloads and installs osquery 5.21.0 from the upstream GitHub release only when it finds no osquery at /usr/local/bin/osqueryi, at /opt/osquery/lib/osquery.app/Contents/MacOS/osqueryd, or as any osquery.app under /opt. It never installs over an existing copy, because Installer would relocate the new bundle on top of the other tool's. Collection still works without osquery, since every module has a shell fallback, but with less detail.

File layout

Path Contents
/Applications/Utilities/Managed Reports Runner.app The app bundle (bundle id com.github.reportmate); all three binaries live in Contents/MacOS/
.../Contents/MacOS/managedreportsrunner CLI collector
.../Contents/MacOS/ReportMateApp SwiftUI GUI
.../Contents/MacOS/ReportMateHelper Privileged XPC helper
.../Contents/Library/LaunchDaemons/ Master copies of the daemon plists
.../Contents/Resources/module-schedules.json Declarative description of the schedules below
.../Contents/Resources/profiles/ReportMate-FullDiskAccess.mobileconfig PPPC profile template
.../Contents/Resources/Assets.car, ReportMate.icns App icon
/usr/local/reportmate/managedreportsrunner sh wrapper that execs the bundle's CLI
/usr/local/reportmate/reportmate-appusage Application usage watcher
/usr/local/reportmate/macadmins_extension.ext Bundled macadmins osquery extension
/usr/local/bin/managedreportsrunner Symlink to the bundle's CLI
/etc/paths.d/reportmate Adds /usr/local/reportmate to the default PATH
/Library/Managed Reports/logs/ All logs
/Library/Managed Reports/cache/ Per-run payload cache and the storage-analysis cache
/Library/Managed Reports/appusage.sqlite Watcher database
/Library/Preferences/com.github.reportmate.plist System preferences
/Library/LaunchDaemons/com.github.reportmate.*.plist Installed daemons

The pkg identifier is com.github.reportmate. A component plist marks the app bundle non-relocatable, so macOS Installer cannot move it somewhere else and leave the daemons pointing at nothing.

What preinstall does

  1. Boots out and removes the legacy com.github.reportmate daemon and the boot, hourly, fourhourly, daily, allmodules and appusage daemons. The installs and helper daemons are replaced by the postinstall.
  2. Kills any running managedreportsrunner and reportmate-appusage.
  3. Removes app bundles left at older locations, and any ReportMate-N.localized duplicates macOS Installer created on past upgrades.

What postinstall does

  1. Creates /Library/Managed Reports/logs and deletes the unrotated log files older releases left there.
  2. Installs osquery if none is present (see Requirements).
  3. Re-registers the app bundle with LaunchServices.
  4. Removes the retired com.github.reportmate, ...boot and ...12hourly daemons, then copies the seven current plists into /Library/LaunchDaemons, runs launchctl enable system/<label> (clearing any recorded manual disable, which survives a plist reinstall and would otherwise make bootstrap fail forever), and bootstraps each one.
  5. Creates the /usr/local/bin symlink and the paths.d entry.
  6. Installs the Munki postflight integration if /usr/local/munki exists.
  7. Writes preference defaults, but only for keys that do not already exist, so an MDM profile or config package is never overwritten on upgrade.
  8. Starts, in one backgrounded shell, a --hello check-in so the device appears in the dashboard within seconds, then one --force --storage-mode quick collection of every enabled module, so a Mac shelved straight after enrolment still reports identity and its logged-in user. Quick storage keeps that first run short; the daily deep job does the full walk.

LaunchDaemons

The six collection and watcher daemons write launchd's stdout and stderr capture to /Library/Managed Reports/logs/launchd-<label>.log, run Nice 10 with ProcessType Background, and set AssociatedBundleIdentifiers to com.github.reportmate. The collection daemons also set AbandonProcessGroup.

Label Trigger Command
com.github.reportmate.hourly StartInterval 3600 --run-modules security,network,management,hardware --storage-mode quick
com.github.reportmate.fourhourly StartInterval 14400 --run-modules applications,inventory,system,identity,peripherals
com.github.reportmate.daily StartCalendarInterval 09:00 --run-modules hardware --storage-mode deep
com.github.reportmate.allmodules StartCalendarInterval 04:00 no module flags, so every enabled module
com.github.reportmate.installs none; kickstarted on demand --run-modules installs --force
com.github.reportmate.appusage RunAtLoad + KeepAlive /usr/local/reportmate/reportmate-appusage
com.github.reportmate.helper MachServices com.github.reportmate.helper; launchd starts it when the GUI connects .../Contents/MacOS/ReportMateHelper

The helper is installed as a system LaunchDaemon by the package, so no user ever has to approve a login item for the GUI to work. See Managed Reports Runner App.

The two calendar jobs are wrapped in /bin/sh -c "sleep $(jot -r 1 0 N); exec …" — jot picks a random offset up to 3540 s for the daily job and 7200 s for the all-modules job. launchd has no native trigger jitter, and a fixed calendar time fires at the same instant on every Mac in the fleet, which would put the whole estate on the API in one minute. The interval-driven jobs need no jitter: their clock starts at daemon load, which is boot time, and that is already spread.

The installs daemon deliberately has no schedule. See below.

Munki integration

Munki honours exactly one /usr/local/munki/postflight. If Munki is present, the postinstall replaces it with a wrapper that runs every executable in /usr/local/munki/postflight.d/ in name order, passing Munki's runtype through, and logs each one to /Library/Managed Reports/logs/reportmate-postflight.log. An existing postflight is moved into postflight.d/ first, under a name that reflects what it appeared to belong to (munkireport.sh, sal.sh, or original.sh), so an incumbent reporting tool keeps working.

ReportMate's own entry is postflight.d/reportmate.sh. It does not run the collection itself: Munki allows a postflight only 60 seconds, and Munki's daemons do not set AbandonProcessGroup, so launchd kills the whole process group the moment the run ends and a child started here cannot outlive it. Instead it calls:

launchctl kickstart -k system/com.github.reportmate.installs

That hands the collection to launchd, which owns it independently of the Munki run. If the kickstart fails, the script falls back to running the collection inline. This postflight is the only scheduled path for the installs module outside the nightly all-modules run.

The wrapper skips non-executables, dotfiles, and any file that contains its own header string — a copy of the wrapper inside the directory it orchestrates would recurse forever.

Privacy permissions

Sources/Resources/profiles/ReportMate-FullDiskAccess.mobileconfig is a PPPC payload (com.apple.TCC.configuration-profile-policy) granting SystemPolicyAllFiles and SystemPolicySysAdminFiles to three identities:

Identifier Why
com.github.reportmate.managedreportsrunner Reads /Library, MDM enrolment data and configuration profiles
io.osquery.agent The managed_policies table reads TCC-protected /Library/Managed Preferences/ plists; without the grant osqueryi hangs in a TCC check with no UI to resolve and the management module never completes
macadmins_extension Runs as its own process with its own TCC identity; without an independent grant it hangs in a sandbox check and osqueryi blocks waiting on its socket

The osquery and macadmins requirements are pinned to those projects' own Team IDs. The ReportMate entries carry a TEAMID0000 placeholder; replace it with your Team ID before deploying, or the requirement will never match your signed build.

When you build with build.sh --sign, the bundled extension is re-signed with your own identity (notarization requires a hardened runtime the upstream binary lacks), and a signed runner only loads an extension carrying its own Team ID. In that case the extension's code requirement in the profile must name your Team ID too, not the upstream one.

Uninstalling the Munki hook

Remove ReportMate's script and, if you want the original tool back in the postflight slot directly, move it out of postflight.d/.

sudo rm /usr/local/munki/postflight.d/reportmate.sh

Leaving the wrapper in place is harmless and keeps the slot shareable for other tools.

Clone this wiki locally