Use Service Management API to Exit/Restart App

Hello, My current app bundle structure is I have a sandboxed GUI and a unsandboxed launch agent that does the core logic of my app. Our pkg post install scripts handles bootstrapping the Launch Agent plists defined in /Library/Launch Agents. I have been tasked with creating a restart/exit button on the UI which terminates the Launch Agent (essentially bootout command in launchctl) and terminates the UI as well. I have attempted to follow the SMAppServcice.agent(plistName) and changed Program key to BundleProgram and changed the value to the relative path as in example provided in Apple Docs (old launch agent plist attached, and new bundle build phase style attached. I have been unable to register or unregister the launch agent via the UI, and in the initial case when trying to call unregister the launch agent got removed and i got "Operation not permitted" with error kSMErrorInvalidSignature seems like some code signature issue im not aware of. I wasnt even able to bootstrap the launch agent back until I found a script which reset such launchctl settings. My question is: is the sandboxed UI not able to do this (and why is this not documented in the dev docs I have no idea), and if so then how would I go about terminating both services and also being able to restart them? This seems like a common use case the UI should be able to handle as far as ownership of running/booting out its resources. ).

Answered by DTS Engineer in 827100022

You are crossing the streams here. If you install a launchd agent in the traditional way, then you have to manage it in the traditional way. If you want to manage an agent with SMAppService, you have to install it with SMAppService. So, you either need to change your installation setup to use SMAppService or you need to use alternative means to manage your agent.

If you decide to continue on the traditional installation path, lemme know and I can offer some advice on how to do this management from your sandboxed app.

Share and Enjoy

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

You are crossing the streams here. If you install a launchd agent in the traditional way, then you have to manage it in the traditional way. If you want to manage an agent with SMAppService, you have to install it with SMAppService. So, you either need to change your installation setup to use SMAppService or you need to use alternative means to manage your agent.

If you decide to continue on the traditional installation path, lemme know and I can offer some advice on how to do this management from your sandboxed app.

Share and Enjoy

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

I'm still facing an issue with registering the agents. I left the bootout of the launch agents in the preinstall script, thinking i would be in a clear state with respect to the registration status. But when i launch the UI and check the registration status of the launch agent its 1 (enabled). So I was able to succcessfully unregister it (status=0 notRegistered), and then i keep getting this error when trying to register: User Agent Registration Failed: Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted}

What could be happening here thats causing me not to be able to register the agent? Is the fact that I have two launch agent plists for the same agent is causing it (even though there will be at least a bootout of the agent required for existing users to implement this flow). Is this something to do with Should I "Code Sign on Copy" the plist files that represent the launch agent configuration? Starting to think SMAppService cannot fit my use case..

Note that the plists defined in /Library/LaunchAgents, and the one SMAppService references in the app bundle are different and that is expected (BundleProgram replace Program, and relative path for that)

Preinstall script commands:

CURRENT_UID=$(ls -ln /dev/console | awk '{print $3}')
    if [ -n "$CURRENT_UID" ]; then
        /bin/launchctl bootout gui/"$CURRENT_UID" "/Library/LaunchAgents/com.amazon.persist.user-service.plist" 2>/dev/null || true
        /bin/launchctl bootout gui/"$CURRENT_UID" "/Library/LaunchAgents/com.amazon.persist.privileged-service.plist" 2>/dev/null || true
    fi

You really need to stop trying to use both mechanisms for this. Each one works fine, but if you try to combine them you’ll just confuse yourself.

If I stick with the traditional installation path, how would you suggest managing from the sandboxed app?

Here’s how I’d do this:

  1. Have the agent publish a named XPC endpoint within an app group. That is, if your Team ID is TTT, use an app group ID like TTT.com.example.my-app.my-group and then set the XPC endpoint name to something like TTT.com.example.my-app.my-group.my-agent-service.

  2. Then add support for an uninstall command to that XPC endpoint.

  3. Sign the sandboxed app with the app group entitlement, listing the app group ID you chose in step 1. This allows it to access the XPC endpoint even though it’s sandboxed.

  4. Add code to the sandboxed app to connect to the agent’s XPC endpoint and invoke your uninstall command.

  5. If you want to prevent other programs from using this uninstall command, restrict the endpoint as described in this post.

Share and Enjoy

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

It’s better to reply as a reply, rather than in the comments; see Quinn’s Top Ten DevForums Tips for this and other titbits.

how would we reconcile that with migrating to the new format with SMAppService?

There’s some interesting titbits in the following official docs:

Honestly, I’m not sure I agree with all the advice in there, but that’s not surprising. As with any engineering problem, there are lots of different ways to achieve a goal, with various trade-offs between them.

However, I do recommend reading those docs in depth, if only to learn about the various options open to you. For example, I read through them today and it reminded me about the statusForLegacyPlist(at:) method.

Anyway, I think the answer here depends on whether your product needs an installer for other purposes:

  • If it does, then you can have your new installer follow the ideas in Updating your app package installer to use the new Service Management API.

  • If it doesn’t, the path forward is less clear. One option would be to change the label on your agent. That completely separates your new version from your old version, which gives you more options. Installing the new version wouldn’t require the user to uninstall the old version. You could then have the new version check for the old version using statusForLegacyPlist(at:) and offer to uninstall it, with the sandboxed app using XPC to request that the agent uninstall the old version on its behalf.

Share and Enjoy

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

Use Service Management API to Exit/Restart App
 
 
Q