Skip to content

LaunchAgents & LaunchDaemons

The template forge — where XML plists are stamped into existence with the precision of a bureaucracy that runs on cron

Sanctum manages a set of macOS LaunchAgents (user-level) and LaunchDaemons (root-level) that form the boot chain for the haus intelligence platform. Each plist is rendered from templates at ~/.sanctum/templates/launchagents/ by the generate-plists.sh script, which pulls values from instance.yaml and tokens from the macOS Keychain.

You might wonder why these aren’t written in a friendlier format. The answer is that Apple chose XML for process management configuration in 2005 and has been politely pretending that was fine ever since. The template system exists so you never have to touch raw plist XML. You’re welcome.

LaunchAgents are loaded at user login. The ordering below reflects the logical dependency chain — launchd does not guarantee ordering, but RunAtLoad: true ensures all agents start promptly after login.

In practice, everything starts within seconds of each other and sorts itself out. It’s less of a chain and more of a stampede in roughly the right direction.

Every Sanctum-managed user-level agent has its own paragraph in the LaunchAgent Catalog — grouped by role (Core Infrastructure, AI & Voice, Network & Tunnels, System & Maintenance), each with port, KeepAlive setting, purpose, and the specific 3 AM incident that earned the paragraph. Twenty-three plists is a lot to scroll past on the way to a config tweak, so the catalog gets its own room.

The labels all live in ~/Library/LaunchAgents/, all follow the com.sanctum.* namespace, all render from templates at ~/.sanctum/templates/launchagents/, and all load at user-session start via launchctl bootstrap gui/$(id -u). The convention is the only thing standing between the haus and twenty-three little XML islands all doing roughly the same thing slightly differently.

Everything above runs as your user. Everything below runs as root. There is exactly one daemon in this section, and it exists because of a number: 80. The lowest-numbered privilege escalation in the history of haus automation.

PropertyValue
Labelcom.sanctum.dench-proxy
PurposeReverse proxy from port 80 to port 1977 for the Holocron chat interface
Required Servicegateway
KeepAliveYes
RunAtLoadYes
Runs asroot

This is a LaunchDaemon (not a LaunchAgent) because binding to port 80 requires root privileges. It enables http://holocron/ access from the LAN without specifying a port.

Plist location: /Library/LaunchDaemons/com.sanctum.dench-proxy.plist

The entire reason this runs as root is so family members can type holocron into a browser instead of holocron:1977. Usability has a cost. That cost is sudo.


All plists are rendered from Mustache-style templates using values from instance.yaml and tokens from the macOS Keychain:

Terminal window
# Preview what would be generated (dry run)
~/.sanctum/generate-plists.sh --dry-run
# Generate and install all plists for enabled services
~/.sanctum/generate-plists.sh

The generator:

  1. Reads each template from ~/.sanctum/templates/launchagents/
  2. Checks if the corresponding service is enabled in instance.yaml
  3. Expands {{PLACEHOLDER}} tokens with config values
  4. Pulls secrets from the macOS Keychain using the configured keychain_account
  5. Writes the rendered plist to ~/Library/LaunchAgents/ (or /Library/LaunchDaemons/ for daemons)

Load or unload agents using launchctl:

Terminal window
# Load an agent
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.sanctum.watchdog.plist
# Unload an agent
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.sanctum.watchdog.plist
# Check if an agent is running
launchctl print gui/$(id -u)/com.sanctum.watchdog