Repository navigation
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.
- macOS 14 (Sonoma) or later — the Swift package declares
.macOS(.v14). - Root.
managedreportsrunnerexits immediately withERROR: 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 anyosquery.appunder/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.
| 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.
- Boots out and removes the legacy
com.github.reportmatedaemon and theboot,hourly,fourhourly,daily,allmodulesandappusagedaemons. Theinstallsandhelperdaemons are replaced by the postinstall. - Kills any running
managedreportsrunnerandreportmate-appusage. - Removes app bundles left at older locations, and any
ReportMate-N.localizedduplicates macOS Installer created on past upgrades.
- Creates
/Library/Managed Reports/logsand deletes the unrotated log files older releases left there. - Installs osquery if none is present (see Requirements).
- Re-registers the app bundle with LaunchServices.
- Removes the retired
com.github.reportmate,...bootand...12hourlydaemons, then copies the seven current plists into/Library/LaunchDaemons, runslaunchctl enable system/<label>(clearing any recorded manual disable, which survives a plist reinstall and would otherwise make bootstrap fail forever), and bootstraps each one. - Creates the
/usr/local/binsymlink and thepaths.dentry. - Installs the Munki postflight integration if
/usr/local/munkiexists. - Writes preference defaults, but only for keys that do not already exist, so an MDM profile or config package is never overwritten on upgrade.
- Starts, in one backgrounded shell, a
--hellocheck-in so the device appears in the dashboard within seconds, then one--force --storage-mode quickcollection 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.
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 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.
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.
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.
MIT licensed. — reportmate.app