How to launch a User Agent on Demand

I am developing a screen share app and would like to launch an agent on demand whenever i receive a request to the port on which the app listens.

Based on the documentation available from Daemons & Agents , i designed a plist file which looks like this,

<?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>com.myapp.assist-loginserver</string>
        <key>LimitLoadToSessionType</key>
        <array>
          <string>LoginWindow</string>
          <string>Aqua</string>
        </array>
        <key>ProcessType</key>
        <string>Interactive</string>
        <key>ProgramArguments</key>
        <array>
            <string>/Applications/My\ App\ share.app/Contents/MacOS/My\ App\ Assist</string>
            <string>--args</string>
            <string>--launcher</string>
        </array>
        <key>Sockets</key>
        <dict>
            <key>Listeners</key>
            <dict>
                <key>SockServiceName</key>
                <string>50053</string>
            </dict>
        </dict>
 </dict>
</plist>

However when i pass a request to the listening port '50053' , i don't see the app being launched. From the terminal , if i provide the Program Arguments separated by space , i can see app launch . So i don't think there is a problem with Program Arguments.

When i look for active ports , i can see the port '50053' being monitored by launchd.

sh-3.2# lsof -i:50053
COMMAND PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
launchd   1 root   30u  IPv6 0xb9009595656d4fe7      0t0  TCP *:50053 (LISTEN)
launchd   1 root   43u  IPv4 0xb900959efe5cf82f      0t0  TCP *:50053 (LISTEN)

when i pass in a curl request to the port , i do see a TCP Establishment , but it doesn't launch the app. I would like the app to be launched and let the app handle the socket connection (50053) till it is alive

sh-3.2# lsof -i:50053
COMMAND   PID     USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
launchd     1     root   30u  IPv6 0xb9009595656d4fe7      0t0  TCP *:50053 (LISTEN)
launchd     1     root   43u  IPv4 0xb900959efe5cf82f      0t0  TCP *:50053 (LISTEN)
curl    15759 testuser    5u  IPv4 0xb900959efe5ca2ff      0t0  TCP localhost:50054->localhost:50053 (ESTABLISHED)

Any help on this will be much appreciated

Thanks, Abhilash

Note: I want to launch an agent on demand and not a daemon. The plist configuration is specified in Library/LaunchAgents/ directory

What you’re trying to do make no sense. If two GUI users are logged in and someone connects to port 50053, which agent should be started?

I am developing a screen share app

Screening sharing apps usually need two launchd jobs:

  • A daemon, to manage the network connection

  • An agent, which runs in each GUI context, which handles the GUI work

These typically communicate via XPC but other IPC mechanism work as well.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Eskimo, Thank you so much for adding your comments.

Our Application is quiet heavy weight and we don't want to run it always in the context of memory footprint. We would like to launch this on-demand.

The architecture we follow is same as what you outlined .

ScreenShare App server --->MAC System Daemon --->MAC Agent(On Demand)

Daemon manages network connections & would try to setup a session in current active console user on demand (LoginWindow/Aqua) by passing a traffic to port. The agent will be quiesced if it doesn't run as console user.

But , we aren't able to launch it on demand by monitoring the socketport.

Thanks, Abhilash

What I’d do in this situation is to sprinty our agent into two:

  • A main agent that does all the real work.

  • A lightweight agent that runs all the time in every GUI login context and maintain an IPC connection from your daemon.

The latter can then launch the former based on whatever demand criteria you come up with.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Eskimo,

I have couple of questions based on your response

  1. Why do we need a light weight agent that runs always on GUI Context ?Is it because daemon launching an agent is not a recommended practice ?

    The latter can then launch the former based on whatever demand criteria you come up with.

  2. How do you recommend we do the above ? I also need to pass few run-time arguments to the UI Agent .

Should this be a launchctl load command issued from the lightweight agent ? or an on-demand monitors such as port,file system etc. If it's latter can you please suggest on what config needs to be changed to get on demand working (Details on question) and how i can pass run-time arguments while launching such that i can minimize one more level of IPCs between Light Weight Agent <-> UI Agent

Thanks again for your response

--Abhilash

Why do we need a light weight agent that runs always on GUI Context? Is it because daemon launching an agent is not a recommended practice?

It’s not simply “not recommended” but rather “actively unsupported”. A daemon cannot safely launch an app because there’s no way to control the context in which the app runs [1]. I go into this in gory detail in Technote 2083 Daemons and Agents [2].

How do you recommend we do the above? I also need to pass few run-time arguments to the UI Agent.

Your lightweight agent should use an IPC mechanism, preferably XPC, to coordinate its work with the daemon. If your lightweight agent needs to pass arguments to your main agent, it can do so via any mechanism it likes. I would use XPC for this as well, but you can use command-line arguments if you want.

Should this be a launchctl load command issued from the lightweight agent?

Probably not. I’m being a bit loose with terminology here; sorry. Your lightweight agent is a launchd agent but your main agent is only an agent in the broadest sense. It doesn’t have to be a launchd agent and probably shouldn’t be. Rather, make it a simple program that you launch, either as a child process (using say NSTask) or as a standalone app (using NSWorkspace).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] This isn’t quite true on modern systems. On macOS 10.10 you can target a specific GUI login session using the modern launchctl commands but I still don’t recommend that path because… well… lots of reasons. First, you can’t targeting a pre-login session that way. Second, it gets you into the business of trying to discover and monitor GUI login session changes, and there’s no good way to do that from a daemon.

[2] This is super old, and some things are definitely out of date, but the core concepts still apply.

It doesn’t have to be a launchd agent and probably shouldn’t be. Rather, make it a simple program that you launch, either as a child process (using say NSTask) or as a standalone app (using NSWorkspace).

My app is based on NodeJS and probably doesn't have access to cocoa libraries to make use of NSTask or NSWorkspace. If i fork the standalone app using command line (open -a app.name ) , i don't think pre-login capture would work.

Any recommendations on this ?

If i fork the standalone app using command line (open -a app.name)

NSTask is a wrapper around posix_spawn which is more or less equivalent to fork/exec.

open is effectively a wrapper around NSWorkspace.

Any recommendations on this?

I only maintain expertise in Apple’s tools and APIs. If you need help translating my advice into your third-party environment, I recommend that you escalate that via its support channel.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Quinn, Thank you so much for your responses.

I should have been more clear on my question. From the architecture you suggested above where Light weight Agent launches the App using NSTask/NSWorkspace , i just wanted to confirm if the child process that's newly spawned from agent will have the same privileges as Launchd Agent & can capture the prelogin screen as well. Traditionally Launchd agents capture them , i am not sure if child process spawned from it has required privileges to capture screen.

Thanks, Abhilash

Supporting the pre-login environment is tricky. For this to work reliably the system must be able to find a path from the calling process to the responsible code that’s authorised for this privilege by the user. For your launchd there are two good ways to do this:

  • Install your agent using SMAppService.

  • Or set the AssociatedBundleIdentifiers property in your launchd property list.

Or both (-:

I believe that TCC will track this via the parent process relationship but, honestly, it’s hard to be sure without actually testing it.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

How to launch a User Agent on Demand
 
 
Q