- Used on macOS for managing agents and daemons and can be used to run scripts at specified intervals
- macOS's competitor to
cron
, along with other things
- macOS's competitor to
- Runs Daemons and Agents
A daemon is a program running in the background without requiring user input.
- Used to perform daily maintenance tasks or do something when an event occurs at the OS level, like when a device is connected.
- Runs on behalf of the
root
user of the machine
- Basically the same thing as a daemon, but runs on behalf of the logged-in user, not the
root
user
- Important Distinction: You don't interact with
launchd
directly- Use
launchctl
to load or unload daemons and agents instead
- Use
- Steps to set up an agent or daemon:
- Create a program that you want to run in the background
- Create a
.plist
file describing the job to run (See below for how to author one) - Store it in the relevant spot based on whether or not you're creating a daemon or an agent, and why type of agent or daemon that you want to include (see screenshot below)
- Use
launchctl
to load the job and set it to run:launchctl load PATH/TO-PLIST
- Note that you only have to run this once when you first author / install the service. Upon reboot / login all agents & daemons will be loaded and run according to the
.plist
running commands
- Note that you only have to run this once when you first author / install the service. Upon reboot / login all agents & daemons will be loaded and run according to the
- If you want to run a job regardless of its run conditions listed in the
.plist
file, you can run the following:launchctl start LABEL.OF.JOB
The majority of the learning that I needed in order to run my background job came from the first link in the Further Reading section below
- A
.plist
file is valid for loading intolaunchd
with just theLabel
andProgram
orProgramArguments
attributes, but the background job won't run, since it doesn't know when to invoke it - It's an
xml
document - Some key attributes to include:
Label
(required): The name of your job, should be unique and follow "reverse domain" naming conventionProgram
(required ifProgramArguments
isn't present): The path to the executable on your systemProgramArguments
(required ifProgram
isn't present): Array of string arguments including the path to your executable and any other arguments- All strings are concatenated together with spaces between them to make up the full command
ServiceDescription
: Human-readable description of your serviceWorking Directory
: Can set the working directory when your program runsRunAtLoad
: Should we run the job at boot time (for daemons) / login time (agents)?StartCalendarInterval
: Dictionary used to specifycron
-like running intervals, like "run this every day at 3:00 AM"- Available keys:
Month
Day
Weekday
Hour
Minute
- Important Note: omitted keys are interpreted as
*
- Available keys:
StartInterval
: Used to run the background job every n secondsStandardErrorPath
: Useful for indicating a specific log file location for when errors arise in your serviceEnvironmentVariables
: Allows you to set different environment variables that can be accessed as part of your program- Common entries here include setting your
PATH
when running different scripts.- Given that your service is running outside of the context of loading a terminal / shell session, your shims and other enhancements usually found in a
.bashrc
,.bash_profile
, or.zshrc
file won't be loaded; therefore you have to load those up here or your service might not run as expected
- Given that your service is running outside of the context of loading a terminal / shell session, your shims and other enhancements usually found in a
- Common entries here include setting your
This is an example .plist
file that I use to run a background job to sync my Obsidian-based Second Brain to the git
-based version of the repo, which then runs some formatting and other processes.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>dev.johnturner.ObsidianFoamReconciler</string>
<key>ServiceDescription</key>
<string>Regular sync between Obsidian Second Brain vault and my git-based Second Brain</string>
<key>Program</key>
<string>/Users/johnturner/second-brain/sync-from-obsidian.sh</string>
<key>WorkingDirectory</key>
<string>/Users/johnturner/second-brain</string>
<key>StandardErrorPath</key>
<string>/Users/johnturner/Desktop/reconciler-error.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>
/usr/local/opt/gettext/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
</string>
</dict>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>11</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>RunAtLoad</key>
<false />
</dict>
</plist>
@cmendez20 I believe that it is NOT a required attribute. If you check this documentation, and click on the "Configuration" tab at the top, you'll see at the bottom of the page where it describes the difference use cases for
StartCalendarInterval
vs.StartInterval
.