Keeping fjx supervise alive
fjx supervise start <project-dirs...> is a long-running daemon. It drains in-flight ticks on SIGTERM and reloads each project's fjx.json on SIGHUP (see supervise). For unattended operation it needs a process supervisor that restarts it on crash and on host reboot.
Three concrete recipes follow. Pick the one that matches the host. Replace /srv/fjx/projects/{a,b} with the actual project directories and fjx with the absolute path to the binary (which fjx).
systemd (Linux)
User-level unit — runs as the invoking user without root. Drop into ~/.config/systemd/user/fjx-supervise.service:
[Unit]
Description=fjx supervisor
After=network-online.target docker.service
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/fjx supervise start /srv/fjx/projects/a /srv/fjx/projects/b
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
KillSignal=SIGTERM
TimeoutStopSec=120s
Environment=FJX_SUPERVISE_SOCK=%t/fjx-supervise.sock
[Install]
WantedBy=default.target
Enable and start:
systemctl --user daemon-reload
systemctl --user enable --now fjx-supervise.service
loginctl enable-linger $USER # survive logout
journalctl --user -u fjx-supervise -f
systemctl --user reload fjx-supervise triggers SIGHUP — the daemon re-reads each project's fjx.json without restarting (see fjx.json). TimeoutStopSec=120s gives in-flight ticks room to drain before systemd escalates to SIGKILL; raise it if --tick-timeout is higher.
launchd (macOS)
User agent — drop into ~/Library/LaunchAgents/net.tfks.fjx-supervise.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>net.tfks.fjx-supervise</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/fjx</string>
<string>supervise</string>
<string>start</string>
<string>/Users/me/fjx/projects/a</string>
<string>/Users/me/fjx/projects/b</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key>
<dict><key>SuccessfulExit</key><false/></dict>
<key>StandardOutPath</key><string>/Users/me/Library/Logs/fjx-supervise.log</string>
<key>StandardErrorPath</key><string>/Users/me/Library/Logs/fjx-supervise.log</string>
</dict>
</plist>
Load and inspect:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/net.tfks.fjx-supervise.plist
launchctl print gui/$(id -u)/net.tfks.fjx-supervise
tail -f ~/Library/Logs/fjx-supervise.log
launchd has no "reload" verb. Send SIGHUP directly to pick up fjx.json changes:
kill -HUP $(launchctl print gui/$(id -u)/net.tfks.fjx-supervise | awk '/pid =/ {print $3}')
Or use the control socket: fjx supervise reload.
tmux (development, ad-hoc hosts)
When systemd or launchd isn't available — a shared dev VM, a remote box accessed only over SSH — a detached tmux session is the lowest-ceremony option. It does not survive a host reboot; pair it with an @reboot cron entry or just re-launch by hand.
tmux new-session -d -s fjx-supervise \
'fjx supervise start /srv/fjx/projects/a /srv/fjx/projects/b 2>&1 | tee -a ~/fjx-supervise.log'
tmux attach -t fjx-supervise # watch it
tmux send-keys -t fjx-supervise C-c # graceful stop (SIGINT drains like SIGTERM)
For auto-restart on crash, wrap the command in a while true loop with a short sleep — but at that point systemd/launchd is the better tool.
Control socket
The daemon exposes a Unix socket for the status, pause, resume, kill, and reload subcommands. Default path is $XDG_RUNTIME_DIR/fjx-supervise-$USER.sock, overridable with FJX_SUPERVISE_SOCK or --socket. Pin the same path in the service file and in operator shell sessions so fjx supervise status finds the running daemon.
Verifying
After bringing the supervisor up, sanity-check from another shell:
fjx supervise status # lists in-flight ticks; empty list is fine on a cold start
fjx supervise reload # round-trip the control socket
If status errors with "connection refused", the daemon isn't running or FJX_SUPERVISE_SOCK doesn't match.