Creating this post so I can refer to it later when I want to do some launchctl
related thing. This tool is used to control launchd
which is the equivalent of systemd
and other service managers on the macOS.
To view services
1 |
sudo launchctl list |
Important to specify sudo
else you only get your own services. Notice the difference in output between these two:
1 2 3 4 5 |
$ sudo launchctl list | grep ssh - 0 com.openssh.sshd $ launchctl list | grep ssh 3521 0 com.openssh.ssh-agent |
To view the details of a particular service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$ sudo launchctl list com.openssh.sshd { "Wait" = false; "Sockets" = { "Listeners" = ( file-descriptor-object; file-descriptor-object; ); }; "LimitLoadToSessionType" = "System"; "StandardErrorPath" = "/dev/null"; "Label" = "com.openssh.sshd"; "inetdCompatibility" = true; "OnDemand" = true; "LastExitStatus" = 0; "Program" = "/usr/libexec/sshd-keygen-wrapper"; "ProgramArguments" = ( "/usr/sbin/sshd"; "-i"; ); }; |
Starting and stopping services
1 2 3 |
sudo launchctl stop com.openssh.sshd sudo launchctl start com.openssh.sshd |
You stop and start to restart.
Types of services
There’s two types of services as far as launchd
is concerned.
- Agents are services run for the logged in user (the output of my
launchctl list
command above without asudo
). They obviously require someone to be logged in to run.- These are stored in
~/Library/LaunchAgents
(empty on my system) and/Library/LaunchAgents
(on my systemssh-agent
is the only one I recognize, but there’s a whole bunch more) and/System/Library/LaunchAgents
(on my system I have iStat Menu, Karabiner, Citrix WorkSpace, etc.). - It would appear that
/System/Library/LaunchAgents
have agents which have a GUI presence while/Library/LaunchAgents
are GUI-less?
- These are stored in
- Daemons are services run by the system either as the root user or any other username specified in the service definition. They don’t require anyone to be logged in.
- These are stored in
/System/Library/LaunchDaemons
(the ones provided by Apple) and/Library/LaunchDaemons
(the ones from 3rd parties; on my system I have Karabiner, iStat Menu, Docker – presumeably the user agents talk to these).
- These are stored in
Service Definitions
Service Definitions are stored as .plist
files in the above directories. There doesn’t seem to be easy way of finding the .plist
file of a service. The closest I could find was do a launchctl dumpstate
and grep for the service name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
sudo launchctl dumpstate | grep ssh 0 - com.openssh.sshd "com.openssh.sshd" => false com.openssh.sshd = { path = /System/Library/LaunchDaemons/ssh.plist program = /usr/libexec/sshd-keygen-wrapper /usr/sbin/sshd XPC_SERVICE_NAME => com.openssh.sshd service = com.openssh.sshd 0 = "ssh" 1 = "sftp-ssh" "SockServiceName" => "ssh" service name = ssh 3521 - com.openssh.ssh-agent com.openssh.ssh-agent = { path = /System/Library/LaunchAgents/com.openssh.ssh-agent.plist program = /usr/bin/ssh-agent /usr/bin/ssh-agent XPC_SERVICE_NAME => com.openssh.ssh-agent |
That is a bit of a hit and miss I suppose.
A better way is to use the print
subcommand:
1 |
sudo launchctl print system/com.openssh.sshd |
The argument to be this has to be system
or user
(depending on whether it’s a system service or user service) followed by the service name, with a /
in between.
Thus for the SSH agent, the above command would be:
1 |
sudo launchctl print user/com.openssh.ssh-agent |
Loading (Installing), Unloading (Uninstalling), Enabling, Disabling services
It’s obvious what they do. I hadn’t realized you need to load/ install and enable separately. Thought it would be just one step.
Loading & Uninstalling require the path to the .plist
file. The other two require the service to be called with a system/
or user/
prefix as above.
1 2 3 4 5 6 7 8 9 10 11 |
# load sudo launchctl load /System/Library/LaunchDaemons/ssh.plist # enable sudo launchctl enable system/com.openssh.sshd # disable sudo launchctl disable system/com.openssh.sshd # unload sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist |
You can also install/ load a service and give it a kick to start (maybe you installed and the service is set to disabled so you’d like to do a one-time start):
1 |
launchctl kickstart system/com.openssh.sshd |
The kickstart
subcommand can take -p
as a switch to print the PID or -k
to kill any currently running instance before starting the new one.
If you want to reload a service after its .plist
file is changed you have to unload
and load
it. Simply enabling/ disabling or starting/ stopping won’t help. (It’s like systemctl daemon-reload
).
Disabled Services
You can find disabled services with the print-disabled
subcommand. This requires you to specify system
to show the status for system services; or user/<uid>
for the UID (User ID) in question (there’s other options too but I am going to skip that for now, check launchctl(1)).
Examples:
1 2 3 4 5 6 7 |
$ sudo launchctl print-disabled system disabled services = { "org.pqrs.karabiner.karabiner_grabber" => false "com.apple.CSCSupportd" => true "com.apple.ftpd" => true "com.openssh.sshd" => false } |
Here Karabiner is not disabled as its value is false
. The others are actually disabled. Bit confusing, eh?
Another one:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# find my UID $ id -u 501 $ $ sudo launchctl print-disabled user/501 disabled services = { "com.docker.helper" => false "com.bjango.istatmenus.agent" => false "com.pointum.hazeover.launcher" => false "com.apple.Siri.agent" => true "com.citrix.ReceiverHelper" => false "org.pqrs.karabiner.agent.karabiner_grabber" => false "io.tailscale.ipn.macos.login-item-helper" => false "com.apple.FolderActionsDispatcher" => true "org.pqrs.karabiner.agent.karabiner_observer" => false } login item associations = { "com.pointum.hazeover.launcher" => "com.pointum.hazeover" "version.com.docker.helper" => "48173" "com.docker.helper" => "com.docker.docker" "version.io.tailscale.ipn.macos.login-item-helper" => "101.0.5" "com.robinlu.Tooth-Fairy-Helper" => "com.robinlu.mac.Tooth-Fairy" "version.com.robinlu.Tooth-Fairy-Helper" => "56" "io.tailscale.ipn.macos.login-item-helper" => "io.tailscale.ipn.macos" "version.com.pointum.hazeover.launcher" => "980" } |
That gives login associations too. Not sure what that is.
An interesting aside on disabled services. Here’s what my SSH .plist
has:
1 2 3 4 5 6 7 8 9 10 11 |
<?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>Disabled</key> <true/> <key>Label</key> <string>com.openssh.sshd</string> ... </dict> </plist> |
Whoa, it’s disabled? But it’s not, coz I can access SSH.
Turns out this is ignored? From the manpage, under the load
section we have the following:
1 2 3 |
-w Overrides the Disabled key and sets it to false or true for the load and unload subcommands respectively. In previous versions, this option would modify the configuration file. Now the state of the Disabled key is stored elsewhere on- disk in a location that may not be directly manipulated by any process other than launchd. -F Force the loading or unloading of the plist. Ignore the Disabled key. |
Hmm, so 1) you’d do load -w
to load/ install a .plist
file and have it not be disabled even though the file might say so, and 2) more importantly the Disabled
key in the .plist
file is irrelevant as the actual state is stored in some other place. Weird.
Basically, stick with the print-disabled
sub-command rather than the .plist
file.
Elsewhere
For when this blog post isn’t enough for me: