Skip to content

launchd / systemd unit

The daemon is meant to run as a service: starts on login, restarts on failure, logs centrally. Pick the unit file for your OS, drop it in, load it, done.

launchd is macOS’s service manager. User agents (run as you, not root) live in ~/Library/LaunchAgents/. They start when you log in and stop when you log out — perfect for a personal backup daemon.

~/Library/LaunchAgents/com.unclez.quay.daemon.plist
<?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.unclez.quay.daemon</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/quay</string>
<string>daemon</string>
</array>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>ThrottleInterval</key> <integer>10</integer>
<key>EnvironmentVariables</key>
<dict>
<key>RUST_LOG</key>
<string>quay=info</string>
</dict>
<key>StandardOutPath</key> <string>/tmp/quay-daemon.log</string>
<key>StandardErrorPath</key> <string>/tmp/quay-daemon.err</string>
</dict>
</plist>
Terminal window
launchctl load -w ~/Library/LaunchAgents/com.unclez.quay.daemon.plist
Terminal window
launchctl list | grep quay
# com.unclez.quay.daemon - ...
tail -f /tmp/quay-daemon.log
Terminal window
# Restart (reads any plist changes)
launchctl kickstart -k gui/$(id -u)/com.unclez.quay.daemon
# Stop temporarily
launchctl unload ~/Library/LaunchAgents/com.unclez.quay.daemon.plist

”But I want it to run when I’m not logged in”

Section titled “”But I want it to run when I’m not logged in””

That’s a system-level daemon (/Library/LaunchDaemons/, runs as root). Don’t do that for backups — the daemon needs your user’s ~/Library/Application Support/com.unclez.quay/ config files, which won’t exist for root. Stay with the user-agent pattern.

If the host needs to run truly headless (a Mac mini that nobody logs into), enable Auto-login in System Settings → Users & Groups so the user agent loads on boot.

Same idea: a per-user systemd service that survives reboots and restarts on failure.

~/.config/systemd/user/quay-daemon.service
[Unit]
Description=Quay scheduled-backup daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/quay daemon
Environment=RUST_LOG=quay=info
Restart=on-failure
RestartSec=10
[Install]
WantedBy=default.target
Terminal window
systemctl --user daemon-reload
systemctl --user enable --now quay-daemon
Terminal window
systemctl --user status quay-daemon
journalctl --user -u quay-daemon -f

By default a user systemd unit stops when you log out. Enable lingering to keep it running in the background:

Terminal window
sudo loginctl enable-linger $(whoami)

Now quay-daemon runs 24/7 even if you’re not logged in.

Terminal window
systemctl --user restart quay-daemon
systemctl --user stop quay-daemon
systemctl --user disable quay-daemon

Windows isn’t covered yet — v0.1 focuses on the Unix-style service managers. For now use Task Scheduler with quay schedule run-once <id> per schedule, or run quay daemon in a long-lived PowerShell window. v0.2 will ship a Windows Service wrapper.

The daemon logs a heartbeat every tick. If you want explicit liveness monitoring, the log file’s mtime moves on every fire and the process PID is in the launchd / systemd status.

A simple “is it alive” alert:

Terminal window
# macOS — alert if last log line is more than 70min old
test "$(stat -f %m /tmp/quay-daemon.log)" -gt $(($(date +%s) - 4200)) || \
echo "Quay daemon log is stale" | mail -s "alert" you@example.com
# Linux — equivalent via journalctl
journalctl --user -u quay-daemon --since "1 hour ago" --no-pager | tail -1

For a real production setup pair the unit with your existing log/ metric pipeline (Datadog, Grafana Agent, etc.) tailing the journal.