Skip to content

Instantly share code, notes, and snippets.

@masklinn
Last active September 24, 2024 12:23
Show Gist options
  • Save masklinn/a532dfe55bdeab3d60ab8e46ccc38a68 to your computer and use it in GitHub Desktop.
Save masklinn/a532dfe55bdeab3d60ab8e46ccc38a68 to your computer and use it in GitHub Desktop.
launchctl/launchd cheat sheet

I've never had great understanding of launchctl but the deprecation of the old commands with launchctl 2 (10.10) has been terrible as all resources only cover the old commands, and documentation for Apple utilities is generally disgracefully bad, with launchctl not dissembling.

Mad props to https://babodee.wordpress.com/2016/04/09/launchctl-2-0-syntax/ which contains most details

domains

Internally, launchd has several domains, but launchctl 1 would only ask for service names, inferring the domain based on context. This made for straightforward commands (once you've loaded a plist you just use the service name it specifies) but had rough edge cases or odd behaviours.

Launchctl 2 separates service names, domain targets and service targets:

  • a service target is <domain-target>/<service-name> and used to point to most services
  • a service name is what is specified in the plist file
  • a domain target is a namespace for services, each namespace has specific behaviours associated:
    • system is privileged (runs services as root) and requires root for interaction
    • user/<uid> runs as that user, but does not require that the user be logged in
    • gui/<uid> runs as that user, but is only active when the user is logged in at the GUI

important commands

bootstrap <domain-target> <paths...>

The paths can be plist files, XPC bundles, or directories of them. Each plist or bundle is loaded into the specified domain.

bootout <service-target> | <domain-target> <paths...>

Unloads the specified service(s).

WARNING

The paths are actually optional, you can unload an entire domain, which you probably should not do.

enable <service-target> | disable <service-target>

Marks the service as runnable (or not), allows overriding a Disabled plist key. Behaviour is a bit odd and I haven't looked much into it, you may need to bootstrap a service (it will complain that the service is disabled), enable it, then bootstrap it again.

kickstart <service-target>

Starts a service. -k will kill then restart existing instances.

Supposedly -p will print the service's pid (even if it's already started), doesn't seem to work for me.

print <service-target>

Dumps the service's definition, properties & metadata.

WARNING

The output of print is not officially structured, do not rely either on the format or on the information.

print <domain-target>

Prints the domain's metadata, including but not limited to all services in the domain.

kill <signame | signum> <service-target>

Sends a signal to a service's process (without having to look it up manually).

@theKosh
Copy link

theKosh commented Jan 10, 2022

For those who came from Google.
All this has been tested on macOS 10.15.7 Catalina.

Bootstrap & bootout

The bootstrap and bootout commands requires <domain name> <path/to/service.plist>.

bootout a service

$ sudo launchctl list | grep -i team || echo none
5336	0	com.teamviewer.service
$ sudo launchctl bootout system /Library/LaunchDaemons/com.teamviewer.teamviewer_service.plist
/Library/LaunchDaemons/com.teamviewer.teamviewer_service.plist: Operation now in progress
$ sudo launchctl list | grep -i team || echo none
none

bootstrap a service

$ sudo launchctl list | grep -i team || echo none
none
$ sudo launchctl bootstrap system /Library/LaunchDaemons/com.teamviewer.teamviewer_service.plist
$ sudo launchctl list | grep -i team || echo none
5710 0 com.teamviewer.service

Kill

The kill command requires <signal code> <domain name>/<service name>.

$ sudo launchctl list | grep -i team || echo none
5808 0 com.teamviewer.service
$ sudo launchctl kill 9 system/com.teamviewer.service

But it won't help if the service has the KeepAlive key set to true, like TeamViewer. launchd just restart the service.

$ sudo launchctl list | grep -i team || echo none
5831 -9 com.teamviewer.service
$
$ sudo more /Library/LaunchDaemons/com.teamviewer.teamviewer_service.plist
...
<key>KeepAlive</key>
<true/>
...

@rgaufman
Copy link

I have a strange problem. I am logged in through the GUI with a mouse/keyboard (not remote), however launchctl bootstrap gui/501 throws a permission error while user/501 works just fine! - Any ideas what could cause something like this? - How does launchctl check that I am logged in at the GUI?

@theKosh
Copy link

theKosh commented Dec 23, 2022

@rgaufman Which service is being launched? They share a lot of resources, but not all of them.

launchctl man page

Note: GUI domains and user domains share many resources. For the purposes of the Mach boot-strap name lookups, they are "flat", so they share the same set of registered names. But they still have discrete sets of services. So when printing the user domain's contents, you may see many Mach bootstrap name registrations from services that exist in the GUI domain for that user, but you will not see the services themselves in that list.

@rgaufman
Copy link

@theKosh Any, I seem to have lost the ability to launch any service in ~/Library/LaunchAgents - they all launch fine with user/501 but fail to launch with gui/501, the error I get is:

2022-12-23 12:11:07.908091 (gui/501 [100003]) <Notice>: entering bootstrap mode
2022-12-23 12:11:07.908359 (gui/501/homebrew.mxcl.memcached) <Error>: Caller specified a plist with bad ownership/permissions: path = /Volumes/Data/home/hackeron/Library/LaunchAgents/homebrew.mxcl.memcached.plist, caller = launchctl[14134]
2022-12-23 12:11:07.908376 (gui/501 [100003]) <Notice>: Bootstrap by launchctl[14134] for /Volumes/Data/home/hackeron/Library/LaunchAgents/homebrew.mxcl.memcached.plist failed (122: Path had bad ownership/permissions)
2022-12-23 12:11:07.908386 (gui/501 [100003]) <Notice>: exiting bootstrap mode

However the permissions seem fine and they launch without issues using user/501:

$ LaunchAgents  /bin/ls -ltra *.plist
-rw-r--r--@ 1 hackeron  staff   994 17 Nov 20:56 [email protected]
-rw-r--r--@ 1 hackeron  staff   743 22 Dec 20:01 homebrew.mxcl.mosquitto.plist
-rw-r--r--@ 1 hackeron  staff   986 22 Dec 20:03 homebrew.mxcl.mongodb-community.plist
-rw-r--r--@ 1 hackeron  staff   880 23 Dec 12:05 homebrew.mxcl.redis.plist
-rw-r--r--@ 1 hackeron  staff   726 23 Dec 13:11 homebrew.mxcl.memcached.plist
-rw-r--r--@ 1 hackeron  staff   929 23 Dec 13:54 [email protected]
-rw-r--r--@ 1 hackeron  staff  1329 23 Dec 14:03 actions.runner.tetherit-tetherbox.Roman-Mac-Mini-M1.plist
-rw-r--r--@ 1 hackeron  staff  1436 23 Dec 14:03 actions.runner.tetherit-tetherx-ansible.Roman-Mac-Mini-M1.plist
-rw-r--r--@ 1 hackeron  staff  1396 23 Dec 14:03 actions.runner.tetherit-tetherx.Roman-Mac-Mini-M1.plist

I recently updated to 13.1 and moved my home directory from /Users/hackeron to /Volumes/Data/home/hackeron as the internal SSD is 256GB, not sure if this is related to these changes and not quite sure what to try next. Any guesses?

@m1n9o
Copy link

m1n9o commented Jan 9, 2023

ec2-user ~ % sudo launchctl bootout system /System/Library/LaunchDaemons/com.apple.apsd.plist
/System/Library/LaunchDaemons/com.apple.apsd.plist: Operation not permitted while System Integrity Protection is engaged
Boot-out failed: 150: Operation not permitted while System Integrity Protection is engaged

Is that necessary to disable SIP first?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment