#!/bin/zsh # ============================================ # KA Guardian — déploiement complet depuis le laptop # ./deploy.sh runners → runners sur M3U96a M3U96b M4M64a M4M64b # ./deploy.sh orchestrators → ka2/ka4/ka6 sur M4M36 (remplace les anciens bots) # ./deploy.sh all # Idempotent. Le token partagé vit dans ~/.ka-guardian.env (laptop) et est # poussé sur chaque nœud. Les tunnels ngrok existants (www.kaX.bot) sont gardés. # ============================================ set -e ROOT="$(cd "$(dirname "$0")/.." && pwd)" RUNNER_NODES=(M3U96a M3U96b M4M64a M4M64b) ORCH_NODE=M4M36 PY=/opt/homebrew/bin/python3 # --- token partagé TOKEN_FILE="$HOME/.ka-guardian.env" if [[ ! -f $TOKEN_FILE ]]; then echo "KA_GUARDIAN_TOKEN=$(openssl rand -hex 24)" > "$TOKEN_FILE" echo "token généré → $TOKEN_FILE" fi TOKEN_LINE=$(grep KA_GUARDIAN_TOKEN "$TOKEN_FILE") deploy_courier() { # Courrier zsh sur TOUS les nœuds (orchestrateur + runners): expédie le # trafic LAN inter-nœuds via ssh (macOS 26 Local Network Privacy). for n in $RUNNER_NODES $ORCH_NODE; do echo "=== courrier → $n" scp -q "$ROOT/deploy/courier.sh" $n:ka-guardian-courier.sh ssh $n 'chmod +x ~/ka-guardian-courier.sh cat > ~/Library/LaunchAgents/com.ka.guardian-courier.plist < Labelcom.ka.guardian-courier ProgramArguments /bin/zsh /Users/simon-pierreboucher/ka-guardian-courier.sh RunAtLoadKeepAlive StandardOutPath/Users/simon-pierreboucher/ka-guardian-spool/courier.log StandardErrorPath/Users/simon-pierreboucher/ka-guardian-spool/courier.log PLIST mkdir -p ~/ka-guardian-spool launchctl unload ~/Library/LaunchAgents/com.ka.guardian-courier.plist 2>/dev/null || true launchctl load ~/Library/LaunchAgents/com.ka.guardian-courier.plist' \ && echo " ✓ $n courrier ok" || echo " ✗ $n courrier KO" done } deploy_runners() { for n in $RUNNER_NODES; do echo "=== runner → $n" ssh $n "mkdir -p ~/ka-guardian-runner/transcripts" scp -q "$ROOT/runner/runner.py" $n:ka-guardian-runner/runner.py ssh $n "printf '%s\nKA_GUARDIAN_NODE=%s\n' '$TOKEN_LINE' '$n' > ~/.ka-guardian.env cd ~/ka-guardian-runner [[ -d .venv ]] || $PY -m venv .venv ./.venv/bin/pip -q install 'fastapi>=0.110' 'uvicorn>=0.29' 'httpx>=0.27' 'pydantic>=2' >/dev/null cat > ~/Library/LaunchAgents/com.ka.guardian-runner.plist <<'PLIST' Labelcom.ka.guardian-runner ProgramArguments /Users/simon-pierreboucher/ka-guardian-runner/.venv/bin/python /Users/simon-pierreboucher/ka-guardian-runner/runner.py EnvironmentVariables PATH/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin RunAtLoadKeepAlive StandardOutPath/Users/simon-pierreboucher/ka-guardian-runner/runner.log StandardErrorPath/Users/simon-pierreboucher/ka-guardian-runner/runner.log PLIST launchctl unload ~/Library/LaunchAgents/com.ka.guardian-runner.plist 2>/dev/null || true launchctl load ~/Library/LaunchAgents/com.ka.guardian-runner.plist" sleep 2 ssh $n "curl -sf localhost:7791/health" && echo " ✓ $n runner ok" || echo " ✗ $n runner KO" done } deploy_orchestrators() { echo "=== orchestrateurs → $ORCH_NODE" ssh $ORCH_NODE "printf '%s\n' '$TOKEN_LINE' > ~/.ka-guardian.env; mkdir -p ~/cluster-projects/ka-guardian" cd "$ROOT" rsync -az --delete --exclude data --exclude .venv --exclude .git --exclude __pycache__ \ ./ $ORCH_NODE:cluster-projects/ka-guardian/ # vérification: le fichier déployé DOIT être identique (gotcha rsync silencieux) loc=$(md5 -q orchestrator/main.py); rem=$(ssh $ORCH_NODE 'md5 -q ~/cluster-projects/ka-guardian/orchestrator/main.py') [[ "$loc" == "$rem" ]] || { echo "✗ rsync n'a pas mis à jour main.py ($loc ≠ $rem)"; exit 1; } ssh $ORCH_NODE "cd ~/cluster-projects/ka-guardian PY=\$(command -v /opt/homebrew/bin/python3 || command -v /opt/homebrew/bin/python3.13) [[ -d .venv ]] || \$PY -m venv .venv ./.venv/bin/pip -q install 'fastapi>=0.110' 'uvicorn>=0.29' 'httpx>=0.27' >/dev/null mkdir -p data logs # retirer les anciens bots (web+bot), garder les tunnels ngrok for a in ka2 ka4 ka6; do launchctl unload ~/Library/LaunchAgents/com.\$a.web.plist 2>/dev/null || true launchctl unload ~/Library/LaunchAgents/com.\$a.bot.plist 2>/dev/null || true rm -f ~/Library/LaunchAgents/com.\$a.web.plist ~/Library/LaunchAgents/com.\$a.bot.plist done" for a in ka2:8799 ka4:8899 ka6:8999; do agent=${a%%:*}; port=${a##*:} ssh $ORCH_NODE "cat > ~/Library/LaunchAgents/com.$agent.guardian.plist < Labelcom.$agent.guardian ProgramArguments /Users/simon-pierreboucher/cluster-projects/ka-guardian/.venv/bin/python /Users/simon-pierreboucher/cluster-projects/ka-guardian/orchestrator/main.py EnvironmentVariables AGENT$agent KA_GUARDIAN_SELF_IP192.168.2.69 PATH/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin RunAtLoadKeepAlive StandardOutPath/Users/simon-pierreboucher/cluster-projects/ka-guardian/logs/$agent.log StandardErrorPath/Users/simon-pierreboucher/cluster-projects/ka-guardian/logs/$agent.log PLIST launchctl unload ~/Library/LaunchAgents/com.$agent.guardian.plist 2>/dev/null || true launchctl load ~/Library/LaunchAgents/com.$agent.guardian.plist" sleep 2 ssh $ORCH_NODE "curl -sf localhost:$port/health" && echo " ✓ $agent ok (:$port)" || echo " ✗ $agent KO" done } case "${1:-all}" in courier) deploy_courier ;; runners) deploy_runners ;; orchestrators) deploy_orchestrators ;; all) deploy_courier; deploy_runners; deploy_orchestrators ;; esac echo "terminé."