Contents

How I use launchd to automate my routine task?

Introduction

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?

Wikipedia  defines launchd as “a unified, open-source service management framework for starting, stopping and managing daemons, applications, processes, and scripts.1

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.

Steps overview

  1. Familiarize with the set of commands required to perform a routine task or tasks.
  2. Create a shell script that executes the commands mentioned in first step.
  3. Write a plist file that incorporates the script and necessary configurations to set up the job for launchd.

My example

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.

My plist file

<?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 plist file:

  1. Label: A unique label that identifies the job to launchd, similar to a process name.
  2. ProgramArguments: This is where you specify the program to run and any arguments passed to it – where you store your script.
  3. RunAtLoad: This key specifies whether the job should be run when the job is loaded.
  4. 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.

Enable the plist file

The 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.

https://res.cloudinary.com/dn5fmt3xj/image/upload/f_auto,q_auto/v1/OB_Assets/ob/oc77qrehfpbt9ozebe2b

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.

Conclusion

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.