claire/scripts/deploy-agent.sh

68 lines
2.7 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
#
# Deploy the headless `claire agent` peer node to a Linux host (apricot|black).
# Runs FROM plum. Idempotent. Code + systemd unit + peer config (injects plum's
# sync_secret so the host can sync to plum).
#
# scripts/deploy-agent.sh apricot
#
# Requires: `remote-run` on PATH (~/Code/@scripts/session-tools), ssh access,
# uv + python3.12+ on the remote, and NTP-synced clocks (HMAC skew window 300s).
set -euo pipefail
HOST="${1:?usage: deploy-agent.sh <host>}"
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REMOTE_DIR="Code/@projects/@claire" # relative to remote $HOME
PLUM_TOML="${CLAIRE_TOML:-$HOME/.config/claire/claire.toml}"
say() { printf '\033[1;35m▸\033[0m %s\n' "$*"; }
# Plum's bind + sync_secret — the peer host signs sync requests to plum with it.
read -r PLUM_URL PLUM_SECRET <<EOF
$("$SRC/.venv/bin/python" - "$PLUM_TOML" <<'PY'
import sys, tomllib, pathlib
c = tomllib.loads(pathlib.Path(sys.argv[1]).read_text())
web = c.get("web", {})
host = web.get("host", "127.0.0.1")
if host in ("0.0.0.0", "::", ""):
host = "127.0.0.1"
print(f"http://{host}:{web.get('port', 8765)}", c.get("sync_secret", ""))
PY
)
EOF
[ -n "$PLUM_SECRET" ] || { echo "ERROR: plum has no sync_secret in $PLUM_TOML" >&2; exit 1; }
say "plum peer URL = $PLUM_URL (secret ${PLUM_SECRET:0:4}…)"
say "[$HOST] reachability + clock"
ssh -o ConnectTimeout=8 -o BatchMode=yes "$HOST" 'true' \
|| { echo "ERROR: cannot ssh $HOST" >&2; exit 1; }
ssh "$HOST" 'timedatectl show -p NTPSynchronized --value 2>/dev/null || echo unknown'
say "[$HOST] rsync source"
ssh "$HOST" "mkdir -p ~/$REMOTE_DIR"
rsync -az --delete \
--exclude='.venv/' --exclude='.git/' --exclude='__pycache__/' \
--exclude='*.pyc' --exclude='.pytest_cache/' --exclude='.ruff_cache/' \
--exclude='claire.toml' \
--exclude='src/claire/web/app/node_modules/' \
--exclude='src/claire/web/app/dist/' \
"$SRC/" "${HOST}:${REMOTE_DIR}/"
say "[$HOST] install (uv) + init"
remote-run "$HOST" "cd ~/$REMOTE_DIR && { [ -d .venv ] || uv venv; } && uv pip install -e . && .venv/bin/claire init"
say "[$HOST] configure peer (idempotent — points this host at plum)"
remote-run "$HOST" "cd ~/$REMOTE_DIR && .venv/bin/claire agent add-peer --url '$PLUM_URL' --secret '$PLUM_SECRET'"
say "[$HOST] install + enable systemd --user unit"
remote-run "$HOST" "
mkdir -p ~/.config/systemd/user
cp ~/$REMOTE_DIR/deployments/systemd/claire-agent.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now claire-agent.service
loginctl enable-linger \$(whoami) 2>/dev/null || true
sleep 2
systemctl --user --no-pager status claire-agent.service | head -5
"
say "[$HOST] done."