{
"$type": "site.standard.document",
"canonicalUrl": "https://devlog.croft.click/2026/05/11/faol-lettabot-launchd",
"description": "Running lettabot as a macOS launchd agent for 24/7 availability. Sops secrets, path issues, and the sandbox workaround.",
"path": "/2026/05/11/faol-lettabot-launchd",
"publishedAt": "2026-05-11T17:10:00.000Z",
"site": "at://did:plc:ofrbh253gwicbkc5nktqepol/site.standard.publication/3mlen2qhzrt2s",
"tags": [
"infra",
"ai"
],
"textContent": "The setup\n\nFaol needs to be always-on. Telegram messages, Bluesky mentions, scheduled posts — can't have the agent dying when the terminal closes.\n\nSolution: launchd agent at ~/Library/LaunchAgents/com.faol.lettabot.plist. KeepAlive: true, RunAtLoad: true, logs to /tmp/faol-lettabot-*.log.\n\nThe sandbox problem\n\nFirst attempt put everything on /Volumes/Storage/ — the external drive where the git repos live. launchd agents don't get Full Disk Access by default, so the agent couldn't read the config files or write logs.\n\nFix: clone lettabot to ~/.config/lettabot/repo/ and run from there. The home directory is accessible to launchd. Config and secrets stay on /Volumes/Storage/ — the start script reads them at runtime.\n\nSecrets flow\n\nAll secrets are sops-encrypted with age:\n\nThe start script decrypts to ~/.config/ as fallback, or uses inline sops decryption if the age key is available. Either way, nothing plaintext in git.\n\nBluesky channel\n\nThe tricky bit: lettabot's Bluesky channel needs appPassword in the yaml, but yaml doesn't support env var expansion. The env-only path only works if there's no bluesky section in the yaml at all.\n\nFix: put the app password directly in lettabot.yaml under channels.bluesky.appPassword. The yaml is on the external volume, not in git, so it's not a leak risk.\n\nCurrent state\n\nExit code 0. Telegram and Bluesky channels running. Jetstream connected. Notifications polling every 60s. Session subprocess ready.",
"title": "Faol infrastructure — lettabot on launchd"
}