herdr phase 0 follow-up: persistent daemon via systemd/launchd
Phase 0 started Herdr via nohup — died on reboot / logout. Replaces
that with proper service management:
- deploy/fleet/herdr-persistence/herdr.service — systemd user unit
(Linux). Restart=on-failure with an 8-in-24h burst cap so a
broken binary doesn't hot-loop. Requires linger enabled so the
user's systemd manager runs without a login session; install.sh
does that via loginctl.
- deploy/fleet/herdr-persistence/dev.herdr.plist — launchd
LaunchAgent (macOS). ProgramArguments + PATH templated so the
install script substitutes actual paths at deploy time.
- deploy/fleet/herdr-persistence/install.sh — idempotent installer
that autodetects OS, drops the unit/plist in the right place,
enables + starts, prints status.
Rolled to tank + architect + morpheus (systemd) + smith + macbook
(launchd). All 5 nodes confirmed `status: running` post-install.
Phase 0 gap closed: Herdr now survives node reboots and logouts,
which is the prerequisite for the fleet_herdr dispatch path to be
reliable across mission_orchestrator restarts.
This commit is contained in:
@@ -0,0 +1,53 @@
|
|||||||
|
<?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">
|
||||||
|
<!--
|
||||||
|
Launchd user agent for Herdr's headless server. Install as:
|
||||||
|
cp dev.herdr.plist ~/Library/LaunchAgents/
|
||||||
|
launchctl load -w ~/Library/LaunchAgents/dev.herdr.plist
|
||||||
|
|
||||||
|
Reads $HOME/.local/bin/herdr — fleet_herdr and the node daemon's
|
||||||
|
PtyTarget::Command resolver both expect that path.
|
||||||
|
-->
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>dev.herdr.server</string>
|
||||||
|
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>__HERDR_BIN__</string>
|
||||||
|
<string>server</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<dict>
|
||||||
|
<key>SuccessfulExit</key>
|
||||||
|
<false/>
|
||||||
|
<key>Crashed</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<!-- 5 second throttle so a bad binary doesn't hot-loop. -->
|
||||||
|
<key>ThrottleInterval</key>
|
||||||
|
<integer>5</integer>
|
||||||
|
|
||||||
|
<!-- Herdr writes its own logs at ~/.config/herdr/herdr-server.log;
|
||||||
|
launchd captures anything on stdout/stderr as a safety net. -->
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>/tmp/herdr-launchd.out.log</string>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/tmp/herdr-launchd.err.log</string>
|
||||||
|
|
||||||
|
<!-- Herdr's install script writes to $HOME/.local/bin; make sure
|
||||||
|
the daemon's PATH includes it so the binary is resolvable. -->
|
||||||
|
<key>EnvironmentVariables</key>
|
||||||
|
<dict>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>__HOME__/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Herdr headless server (terminal multiplexer for coding agents)
|
||||||
|
Documentation=https://herdr.dev/docs/
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
# Reads $HOME/.local/bin/herdr; the resolver in fleet_herdr expects this path.
|
||||||
|
ExecStart=%h/.local/bin/herdr server
|
||||||
|
# Herdr's socket lives under ~/.config/herdr/herdr.sock — the daemon
|
||||||
|
# creates its own state dir, no need to pre-provision anything.
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
# 24 hours between restart bursts so a crashing binary doesn't hot-loop.
|
||||||
|
StartLimitIntervalSec=86400
|
||||||
|
StartLimitBurst=8
|
||||||
|
# Herdr owns its own logs (~/.config/herdr/herdr-server.log); systemd
|
||||||
|
# journal captures anything that escapes.
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
# WantedBy=default.target for user units — no wants for multi-user.target.
|
||||||
|
WantedBy=default.target
|
||||||
Executable
+61
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Idempotent install of a persistent Herdr daemon for the current user.
|
||||||
|
# Autodetects Linux (systemd --user) vs macOS (launchd LaunchAgent).
|
||||||
|
#
|
||||||
|
# Usage (locally):
|
||||||
|
# ./install.sh
|
||||||
|
# Usage (remote):
|
||||||
|
# scp -r deploy/fleet/herdr-persistence <host>:/tmp/
|
||||||
|
# ssh <host> 'bash -lc "/tmp/herdr-persistence/install.sh"'
|
||||||
|
#
|
||||||
|
# Preconditions: `herdr` is installed at $HOME/.local/bin/herdr
|
||||||
|
# (via `curl -fsSL https://herdr.dev/install.sh | sh`).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
here="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
herdr="$HOME/.local/bin/herdr"
|
||||||
|
|
||||||
|
if [ ! -x "$herdr" ]; then
|
||||||
|
echo "herdr binary not found at $herdr — run the herdr install script first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
os="$(uname -s)"
|
||||||
|
case "$os" in
|
||||||
|
Linux)
|
||||||
|
unit_dir="$HOME/.config/systemd/user"
|
||||||
|
unit="$unit_dir/herdr.service"
|
||||||
|
mkdir -p "$unit_dir"
|
||||||
|
cp "$here/herdr.service" "$unit"
|
||||||
|
# Enable lingering so the user's systemd manager runs without a
|
||||||
|
# login session — otherwise the daemon dies at logout.
|
||||||
|
if command -v loginctl >/dev/null 2>&1; then
|
||||||
|
sudo loginctl enable-linger "$USER" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable --now herdr.service
|
||||||
|
systemctl --user status --no-pager herdr.service | head -8
|
||||||
|
;;
|
||||||
|
Darwin)
|
||||||
|
plist_dir="$HOME/Library/LaunchAgents"
|
||||||
|
plist="$plist_dir/dev.herdr.server.plist"
|
||||||
|
mkdir -p "$plist_dir"
|
||||||
|
sed \
|
||||||
|
-e "s|__HERDR_BIN__|$herdr|g" \
|
||||||
|
-e "s|__HOME__|$HOME|g" \
|
||||||
|
"$here/dev.herdr.plist" > "$plist"
|
||||||
|
# Unload first so a re-install replaces cleanly.
|
||||||
|
launchctl unload "$plist" 2>/dev/null || true
|
||||||
|
launchctl load -w "$plist"
|
||||||
|
sleep 1
|
||||||
|
launchctl list | grep dev.herdr.server || echo "(agent not in list yet)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unsupported OS: $os" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "herdr daemon persistence installed. Verify with: herdr status server"
|
||||||
Reference in New Issue
Block a user