How to make a LaunchDaemon run an app at login

catalinadaemonslaunchdpliststartup

I have an internally-maintained macOS app for my macOS Catalina-based enterprise environment which:

  • Must run as root (it accesses a privileged device API)
  • Runs in the background without a UI (via LSUIElement key set to true in its Info.plist)
  • Must run for every user who logs into a workstation without manual configuration (e.g. no manual Login Item configuration)

Creating a LaunchDaemon to launch the app seemed to be the right way to accomplish this. However, the app process is found to be hanging after first user login, presumably because it tries to start too early before app/window-supporting libraries are available (I thought setting LSUIElement to true would avoid this, but I guess not. If there's another Info.plist setting for the app I should use to accomplish this, I'm all ears). There are no errors related to the process or launchd service observed in system.log.

I've observed after login that if I kill the hanging process and the LaunchDaemon restarts it, it then works fine. So, all I think I need is a way to setup my LaunchDaemon plist so that the LaunchDaemon only launches the app on user login like a LaunchAgent, (which I can't use because LaunchAgents can't run as root). Does anyone have a tried and true plist configuration which accomplishes this? Maybe via setting a certain WatchPath for the LaunchDaemon?

My current plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>my.app</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Library/mydaemons/myapp.app/Contents/MacOS/myapp</string>
    </array>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>

Best Answer

@Wowfunhappy's suggestion to use a Login Hook got me the exact behavior I wanted, even if it wasn't done via a LaunchDaemon as planned. The solution:

A script (myscript.sh) to launch my app (must be executable via chmod a+x)

#!/bin/bash

//launch app in background (otherwise login hangs)
/path/to/myapp.app/Contents/MacOS/myapp &

And specifying that script as a login hook via the command:

sudo defaults write com.apple.loginwindow LoginHook /path/to/myscript.sh

My app now runs as root as needed on every user login