How I use launchd to automate my routine task?
This blog discusses how I utilize
launchd to automatically schedule and execute tasks in the background, freeing me from the burden of daily routine tasks.
I have two daily routine tasks. The first task is to synchronize my running data from a running app to a web page that displays my running records. This involves performing a set of git operations every day after I finish running. The second task is to sync my Obsidian vault using git, which allows me to access the same progress on the vault across laptops or desktops.
What is launchd?
Daemons and Agents
A daemon is a program running in the background without requiring user input. A typical daemon might for instance perform daily maintenance tasks or scan a device for malware when it is connected. An agent is on behalf of the logged user while a daemon runs on behalf of the root user or any user you specify. Only agents have access to the macOS GUI.
Why I choose it
cron is a Linux utility for Linux with significant reputation, but
launchd is built into the MacOS.
- Familiarize with the set of commands required to perform a routine task or tasks.
- Create a shell script that executes the commands mentioned in first step.
- Write a
plistfile that incorporates the script and necessary configurations to set up the job for launchd.
My task and script
This is an open-source project called “running_page” available on GitHub. It allows runners to back up and display their running data on a webpage that can be deployed on your own website. I am tasked with executing scripts to obtain data from Garmin or other sports gear providers. This data is then processed, graphed, and used to create images. This includes daily data and an annual overview of aggregated data.
#!/bin/zsh cd /Users/hongji/<your dir> source /Users/hongji/code/personal/running_page/.venv/bin/activate # Sync Strava data python scripts/strava_sync.py <params > >> /users/hongji/<dir>/running_page.log # Generate total data <...> # py env deactivate # Git git add . >> /users/hongji/<dir>/running_page.log 2>&1 # Get current date commit_message=$(date "+%Y-%m-%d") git commit -m "$commit_message" >> /users/hongji/<dir>/running_page.log 2>&1 git push >> /users/hongji/code/scripts/running_page.log 2>&1
2>&1 indicates that any error messages generated by the command will be combined with the standard output and both will be sent to the same destination.
<?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>Label</key> <string>com.hongji.runningpage</string> <key>ProgramArguments</key> <array> <string>/Users/hongji/<dir>/.script.sh</string> </array> <key>RunAtLoad</key> <true/> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>21</integer> <key>Minute</key> <integer>0</integer> </dict> </dict> </plist>
Let’s break down the
- Label: A unique label that identifies the job to launchd, similar to a process name.
- ProgramArguments: This is where you specify the program to run and any arguments passed to it – where you store your script.
- RunAtLoad: This key specifies whether the job should be run when the job is loaded.
- StartCalendarInterval: This key schedules the job to run at a specific time. In my case, it is set to 21:00, which is after I typically finish running and sit back at my computer.
plist file locates in
~/Library/LaunchAgents/. When the job was created, just load it.
launchctl load com.hongji.runningpage.plist.
If you change the config you have to unload then load it again to apply the changes.
launchctl unload com.hongji.runningpage.plist launchctl load com.hongji.runningpage.plist
Pitfalls and challenges
Full Disk Access
Terminal application (Mine is Alacritty) doesn’t have “Full. Disk Access”, due to the privacy protections introduced in MacOS Catalina and later versions. Add Terminal (literally). Finally, add
zsh, then the script works.
What if your job needs to run but your machine is asleep?
According to official site1, if the system is asleep, the job will be started the next time the computer wakes up. If multiple intervals transpire before the computer is woken, those events will be coalesced into one event upon wake from sleep.
If you have some daily routine tasks require a set of commands, you can create a launchd job to automatically run them in background, just like while playing Stardew Valley I don’t have to water the crops on myself after acquiring sprinklers.