I abandoned Mac development back around 10.4 when I departed Apple and am playing catch-up, trying to figure out how to register a privileged helper tool that can execute commands as root in the new world order. I am developing on 13.1 and since some of these APIs debuted in 13, I'm wondering if that's ultimately the root of my problem.
Starting off with the example code provided here:
Following all build/run instructions in the README to the letter, I've not been successful in getting any part of it to work as documented. When I invoke the register
command the test app briefly appears in System Settings for me to enable, but once I slide the switch over, it disappears. Subsequent attempts to invoke the register
command are met only with the error message:
`Unable to register Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted}
The app does not re-appear in System Settings on these subsequent invocations. When I invoke the status
command the result mysteriously equates to SMAppService.Status.notFound
.
The plist is in the right place with the right name and it is using the BundleProgram
key exactly as supplied in the sample code project. The executable is also in the right place at Contents/Resources/SampleLaunchAgent
relative to the app root.
The error messaging here is extremely disappointing and I'm not seeing any way for me to dig any further without access to the underlying Objective-C (which the Swift header docs reference almost exclusively, making it fairly clear that this was a... Swift... Port... [Pun intended]).
I have noticed that launchd is changing my status to 78
This is EX_CONFIG
. In this context it usually means that there’s something broken in your launchd
property list. A common example of this is that the path to the executable is incorrect.
I only update the OS when a machine dies
Fair enough. But it does limit my ability to help you, because it’s not easy for me to test things on such an old OS release. I have macOS 14, 15, and 26 (RC) VMs lying around. I can set up a macOS 13 VM, but it’s hard to do because I’m travelling right now.
Which is actually relevant here. I believe you can virtualise macOS 15 on top of macOS 13, and that’s a good option in this case, and also a good thing to have handy anyway. After all, your users are going to run your product on macOS 15, so you wanna make sure it works there.
Anyway, lemme pass along some snippets from a working test project I have here in my office. First, here’s the embedded launchd
property list:
% plutil -p XPCDaemonsAndAgents2.app/Contents/Library/LaunchDaemons/com.example.apple-samplecode.XPCDaemonsAndAgents2.DaemonSM.plist
{
"BundleProgram" => "Contents/MacOS/DaemonSM"
"Label" => "com.example.apple-samplecode.XPCDaemonsAndAgents2.DaemonSM"
"MachServices" => {
"com.example.apple-samplecode.XPCDaemonsAndAgents2.DaemonSM" => 1
}
}
Here’s where the daemon’s executable lives:
% file XPCDaemonsAndAgents2.app/Contents/MacOS/DaemonSM
…
XPCDaemonsAndAgents2.app/Contents/MacOS/DaemonSM (for architecture x86_64): Mach-O 64-bit executable x86_64
XPCDaemonsAndAgents2.app/Contents/MacOS/DaemonSM (for architecture arm64): Mach-O 64-bit executable arm64
Note that this path matches the BundleProgram
property.
Here’s how I set up the listener in that daemon:
let xpcListener = NSXPCListener(machServiceName: "com.example.apple-samplecode.XPCDaemonsAndAgents2.DaemonSM")
Note that the machServiceName
parameter matches the MachServices
property.
Here’s how I register the daemon:
let service = SMAppService.daemon(plistName: "com.example.apple-samplecode.XPCDaemonsAndAgents2.DaemonSM.plist")
try service.register()
Note that the plistName
paremeter matches the name of the launchd
property list file.
And here’s how I set up the XPC connection:
let connection = NSXPCConnection(machServiceName: "com.example.apple-samplecode.XPCDaemonsAndAgents2.DaemonSM", options: [.privileged])
Again, the machServiceName
parameter matches the MachServices
property.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"