SMAppService re-register after app upgrade

I was experimenting with Service Management API and Xcode project from https://developer.apple.com/documentation/servicemanagement/updating-your-app-package-installer-to-use-the-new-service-management-api and faced some issues with the API.

I replaced agent with XPC service and tried to re-register it. Use case is a new app package installation with a newer service binary. In order to get the running service restarted with the new binary it's required to unregister old version and register new one. Otherwise the old version would be still running after app upgrade.

The problem is that register fails with "Operation not permitted" error after running unregister which seems to work fine.

Experiments with some delays (500ms) between unregister and register seem to help but it's a not a good solution to work around the problem.

I'm using open func unregister() async throws with description:

The completion handler will be invoked after the running process has been killed if successful or will be invoked whenever an error occurs. After the completion handler has been invoked it is safe to re-register the service.

Sample output with no 500ms sleep between unregister and register calls:

/Library/Application\ Support/YourDeveloperName/SMAppServiceSampleCode.app/Contents/MacOS/SMAppServiceSampleCode unregister && /Library/Application\ Support/YourDeveloperName/SMAppServiceSampleCode.app/Contents/MacOS/SMAppServiceSampleCode register Successfully unregistered LaunchDaemon(com.xpc.example.service.plist) Unable to register LaunchDaemon(com.xpc.example.service.plist): Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted}

In fact it doesn't seem to be safe to re-register. Any explanation would much appreciated!

=====================================================

Side issue #2: I tried to add a similar helper executable as in the original project with register/unregister and put it inside the same app bundle but at a different location like Contents/Helpers/ folder instead of Contents/MacOS. And it always fails with this error:

Error Domain=SMAppServiceErrorDomain Code=3 "Codesigning failure loading plist: com.okta.service.osquery code: -67028" UserInfo={NSLocalizedFailureReason=Codesigning failure loading plist: com.okta.service.osquery code: -67028}

When I moved the helper binary to Contents/MacOS/ folder along with the main app executable it starts working fine again. Other folders like Resources/XPCServices also don't work.

Is it a hard requirement for an executable to be located inside main Contents/MacOS folder in order to be able to call SMAppService register/unregister APIs? I haven't found any documentation regarding this requirement.


Thanks,

Pavel

I replaced agent with XPC service and tried to re-register it.

I’m confused by this. In the context of SMAppService, an agent is something you register using agent(plistName: String). But what do you mean by “XPC service”. Normally an XPC service is something you embed within your app, that is, what you get when you choose New > Target > XPC Service. However, such services don’t need to be registered. They are available to your app simply by this embedding.

The answer to this will inform my answer to “Side issue #2”, so I’m gonna defer answer that until I understand the above.

Oh, and I don’t see “Side issue #1”?

Share and Enjoy

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

Correct. I embedded an XPC Service and now it's bundled with the main app in XPCServices folder. So instead of agent(plistName: String) I init the service with daemon(plistName: String).

In order to run the service as a LaunchDaemon I need either (1) register it using SMAppService API (plist is in the app bundle at Contents/Library/LaunchDaemons/ folder) or (2) put the plist to /Library/LaunchDaemons/ and run launchctl load <plist>.

Approach (1) with SMAppService is more user friendly as it provides more visibility and user control by showing 'Background Item needs approval' notification and listing it in Login Items in Settings. So I was looking into migrating (2) launchctl approach to (1) SMAppService.

For the use case of application upgrade (new .pkg file released) we need to re-launch currently running LaunchDaemon from postinstall script. Doing it by invoking launchctl unload and then launchctl load works seamless for end users (for initial installation users are not even aware that a service has been also installed since they were not presented with any visuals).

Using (1) SMAppService it can be done by calling unregister and then register from postinstall script invoking a helper binary within the same app bundle (during initial installation users are presented with above mentioned notification which is great). Unfortunately calling register alone doesn't re-register the service and the old process from old package continues running. In order to kill currently running process it's required to unregister the service first. But unfortunately

let service = SMAppService.daemon(plistName: "com.xpc.example.service.plist")
try await service.unregister()
// try await Task.sleep(nanoseconds: 500_000_000)
try await service.register()

requires experimentally discovered 500ms delay between unregister and register as mentioned in the original post. (Trying another unregister with completion handler doesn't make any difference). Otherwise register throws

Unable to register LaunchDaemon(com.xpc.example.service.plist): Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted}

(+ problem #2 with strict requirements of where the helper binary must reside inside application bundle).

I embedded an XPC Service and … I init the service with daemon(plistName: String).

You are badly ‘crossing the streams’ here. An XPC service is not a daemon, and trying to install one as such is not going to end well.

The correct path forward depends on whether you’re sandboxed or not. Is your app sandboxed?

Share and Enjoy

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

No, the app is not sandboxed and the service is declared as a MachService in plist.

SMAppService re-register after app upgrade
 
 
Q