Turn the worker binary into a fleet CLI over the same control protocol that
drives a GUI editor — any node, daemon or window, is reachable the same way.
- Subcommands: `serve` (the daemon, as before), `fleet` (every node on this
machine with a one-line state summary), `status` (one node's full snapshot),
`submit` (a coding task to a node's on-device agent), and `ctl` (the raw
control method escape hatch). All are thin clients over
joule_core::control::{run_client, run_client_tcp, list_peers} — no new wire.
- launchd install script generates a user agent in the GUI (Aqua) domain via
`launchctl bootstrap gui/$(id -u)`. The GUI domain (not nohup-over-SSH) is
required so the worker can initiate outbound local-network connections for
the federation transport — a macOS process-responsibility constraint we
root-caused (see lockstep-quic/MACOS_LAUNCH_CONTEXT.md). Generated plist
passes plutil -lint.
Live-verified: serve a daemon, then ctl/status/spawn_session/submit/fleet.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
123 lines
3.7 KiB
Shell
Executable file
123 lines
3.7 KiB
Shell
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# Install joule-coded as a launchd *user agent* in the GUI (Aqua) domain.
|
|
#
|
|
# Why the GUI domain and not `nohup … &`: on macOS a process detached from a
|
|
# login session is denied permission to *initiate* outbound local-network
|
|
# connections (it can still bind/accept). The headless worker's federation
|
|
# transport (Phase 3) needs to dial peers, so it must run session-attached or as
|
|
# a launchd agent in the GUI domain. `launchctl bootstrap gui/$(id -u)` gives it
|
|
# that context; `nohup`-over-SSH does not (we root-caused this — see
|
|
# open-standards lockstep-quic/MACOS_LAUNCH_CONTEXT.md).
|
|
#
|
|
# Usage:
|
|
# JOULE_CODE_ROOT=/path/to/repo \
|
|
# JOULECLAW_L3_MODEL=/path/to/model.gguf \ # optional; enables the L3 tier
|
|
# ./install-joule-coded.sh
|
|
#
|
|
# Override any of: JOULE_CODED_BIN, JOULE_CODE_ROOT, JOULE_CODE_SOCK,
|
|
# JOULECLAW_L3_MODEL, JOULE_CODE_GRANT_TIERS.
|
|
|
|
set -euo pipefail
|
|
|
|
LABEL="dev.openie.joule-coded"
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
# Resolve the binary: an explicit override, else the repo's release/debug build.
|
|
REPO_ROOT="$(cd "$HERE/../../.." && pwd)"
|
|
BIN="${JOULE_CODED_BIN:-}"
|
|
if [ -z "$BIN" ]; then
|
|
for candidate in "$REPO_ROOT/target/release/joule-coded" "$REPO_ROOT/target/debug/joule-coded"; do
|
|
[ -x "$candidate" ] && BIN="$candidate" && break
|
|
done
|
|
fi
|
|
if [ -z "$BIN" ] || [ ! -x "$BIN" ]; then
|
|
echo "error: joule-coded binary not found; build it (cargo build -p joule_coded) or set JOULE_CODED_BIN" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ROOT="${JOULE_CODE_ROOT:-$PWD}"
|
|
SOCK="${JOULE_CODE_SOCK:-$HOME/.joule/joule-coded.sock}"
|
|
TIERS="${JOULE_CODE_GRANT_TIERS:-l0,l1,l2,l3}"
|
|
MODEL="${JOULECLAW_L3_MODEL:-}"
|
|
DEST="$HOME/Library/LaunchAgents/$LABEL.plist"
|
|
|
|
mkdir -p "$HOME/logs" "$(dirname "$SOCK")" "$HOME/Library/LaunchAgents"
|
|
|
|
# The L3 model is optional: included only when a path is given.
|
|
MODEL_ARGS=""
|
|
if [ -n "$MODEL" ]; then
|
|
MODEL_ARGS=" <string>--model</string>
|
|
<string>$MODEL</string>
|
|
"
|
|
fi
|
|
|
|
cat > "$DEST" <<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>$LABEL</string>
|
|
|
|
<key>ProgramArguments</key>
|
|
<array>
|
|
<string>$BIN</string>
|
|
<string>serve</string>
|
|
<string>--root</string>
|
|
<string>$ROOT</string>
|
|
<string>--sock</string>
|
|
<string>$SOCK</string>
|
|
<string>--tiers</string>
|
|
<string>$TIERS</string>
|
|
$MODEL_ARGS </array>
|
|
|
|
<key>WorkingDirectory</key>
|
|
<string>$ROOT</string>
|
|
|
|
<key>EnvironmentVariables</key>
|
|
<dict>
|
|
<key>PATH</key>
|
|
<string>/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
<key>HOME</key>
|
|
<string>$HOME</string>
|
|
<key>RUST_LOG</key>
|
|
<string>info</string>
|
|
</dict>
|
|
|
|
<key>RunAtLoad</key>
|
|
<true/>
|
|
|
|
<key>KeepAlive</key>
|
|
<dict>
|
|
<key>SuccessfulExit</key>
|
|
<false/>
|
|
<key>Crashed</key>
|
|
<true/>
|
|
</dict>
|
|
|
|
<key>ThrottleInterval</key>
|
|
<integer>10</integer>
|
|
|
|
<key>StandardOutPath</key>
|
|
<string>$HOME/logs/joule-coded.out.log</string>
|
|
|
|
<key>StandardErrorPath</key>
|
|
<string>$HOME/logs/joule-coded.err.log</string>
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
|
|
# (Re)load into the GUI domain.
|
|
DOMAIN="gui/$(id -u)"
|
|
launchctl bootout "$DOMAIN/$LABEL" 2>/dev/null || true
|
|
launchctl bootstrap "$DOMAIN" "$DEST"
|
|
launchctl kickstart -k "$DOMAIN/$LABEL"
|
|
|
|
echo "installed $LABEL"
|
|
echo " binary : $BIN"
|
|
echo " root : $ROOT"
|
|
echo " sock : $SOCK"
|
|
echo " model : ${MODEL:-<none — L3 disabled>}"
|
|
echo "drive it: joule-coded fleet | joule-coded status --sock $SOCK"
|
|
echo "uninstall: launchctl bootout $DOMAIN/$LABEL && rm $DEST"
|