1

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>
10
  • Why must it run as root?
    – Tetsujin
    Apr 14, 2021 at 15:24
  • @Tetsujin it accesses the privileged FSEvents API via the /dev/fsevents device
    – eckenrod
    Apr 14, 2021 at 15:26
  • 1
    Well, you're working against the way the system is set up, so you basically have a choice: (1) re-code your app so that it's not an app (in other words, as a proper background daemon) so that it can run without a login session, (2) run it as a LaunchAgent and do the hacky things you dislike, or (3) split the code into two segments, one that runs as a daemon and does the privileged stuff, and the other that runs as an agent and launches a modified app in user context. Apr 14, 2021 at 18:05
  • 1
    Could use a LoginHook... pretty sure they're still around. Apr 14, 2021 at 22:45
  • 1
    @Wowfunhappy: Yeah; they're deprecated, but I believe they still work. Apr 14, 2021 at 22:54

1 Answer 1

3

@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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .