spb/ka-guardian
Public
1#!/bin/zsh2# ============================================3# KA Guardian — déploiement complet depuis le laptop4# ./deploy.sh runners → runners sur M3U96a M3U96b M4M64a M4M64b5# ./deploy.sh orchestrators → ka2/ka4/ka6 sur M4M36 (remplace les anciens bots)6# ./deploy.sh all7# Idempotent. Le token partagé vit dans ~/.ka-guardian.env (laptop) et est8# poussé sur chaque nœud. Les tunnels ngrok existants (www.kaX.bot) sont gardés.9# ============================================10set -e11ROOT="$(cd "$(dirname "$0")/.." && pwd)"12RUNNER_NODES=(M3U96a M3U96b M4M64a M4M64b)13ORCH_NODE=M4M3614PY=/opt/homebrew/bin/python31516# --- token partagé17TOKEN_FILE="$HOME/.ka-guardian.env"18if [[ ! -f $TOKEN_FILE ]]; then19 echo "KA_GUARDIAN_TOKEN=$(openssl rand -hex 24)" > "$TOKEN_FILE"20 echo "token généré → $TOKEN_FILE"21fi22TOKEN_LINE=$(grep KA_GUARDIAN_TOKEN "$TOKEN_FILE")2324deploy_courier() {25 # Courrier zsh sur TOUS les nœuds (orchestrateur + runners): expédie le26 # trafic LAN inter-nœuds via ssh (macOS 26 Local Network Privacy).27 for n in $RUNNER_NODES $ORCH_NODE; do28 echo "=== courrier → $n"29 scp -q "$ROOT/deploy/courier.sh" $n:ka-guardian-courier.sh30 ssh $n 'chmod +x ~/ka-guardian-courier.sh31 cat > ~/Library/LaunchAgents/com.ka.guardian-courier.plist <<PLIST32<?xml version="1.0" encoding="UTF-8"?>33<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">34<plist version="1.0"><dict>35 <key>Label</key><string>com.ka.guardian-courier</string>36 <key>ProgramArguments</key><array>37 <string>/bin/zsh</string>38 <string>/Users/simon-pierreboucher/ka-guardian-courier.sh</string>39 </array>40 <key>RunAtLoad</key><true/><key>KeepAlive</key><true/>41 <key>StandardOutPath</key><string>/Users/simon-pierreboucher/ka-guardian-spool/courier.log</string>42 <key>StandardErrorPath</key><string>/Users/simon-pierreboucher/ka-guardian-spool/courier.log</string>43</dict></plist>44PLIST45 mkdir -p ~/ka-guardian-spool46 launchctl unload ~/Library/LaunchAgents/com.ka.guardian-courier.plist 2>/dev/null || true47 launchctl load ~/Library/LaunchAgents/com.ka.guardian-courier.plist' \48 && echo " ✓ $n courrier ok" || echo " ✗ $n courrier KO"49 done50}5152deploy_runners() {53 for n in $RUNNER_NODES; do54 echo "=== runner → $n"55 # GOTCHA: redémarrer un runner TUE la mission claude en cours (cause de la56 # mission fantôme du 2026-08-23). On refuse si le runner est occupé.57 busy=$(ssh $n "curl -s -m 5 localhost:7791/health 2>/dev/null" | grep -o '"busy":true' || true)58 if [[ -n $busy ]]; then59 echo " ⏸ $n occupé (mission en cours) — runner NON redéployé, relance plus tard"60 continue61 fi62 ssh $n "mkdir -p ~/ka-guardian-runner/transcripts"63 scp -q "$ROOT/runner/runner.py" $n:ka-guardian-runner/runner.py64 ssh $n "printf '%s\nKA_GUARDIAN_NODE=%s\n' '$TOKEN_LINE' '$n' > ~/.ka-guardian.env65 cd ~/ka-guardian-runner66 [[ -d .venv ]] || $PY -m venv .venv67 ./.venv/bin/pip -q install 'fastapi>=0.110' 'uvicorn>=0.29' 'httpx>=0.27' 'pydantic>=2' >/dev/null68 cat > ~/Library/LaunchAgents/com.ka.guardian-runner.plist <<'PLIST'69<?xml version=\"1.0\" encoding=\"UTF-8\"?>70<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">71<plist version=\"1.0\"><dict>72 <key>Label</key><string>com.ka.guardian-runner</string>73 <key>ProgramArguments</key><array>74 <string>/Users/simon-pierreboucher/ka-guardian-runner/.venv/bin/python</string>75 <string>/Users/simon-pierreboucher/ka-guardian-runner/runner.py</string>76 </array>77 <key>EnvironmentVariables</key><dict>78 <key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>79 </dict>80 <key>RunAtLoad</key><true/><key>KeepAlive</key><true/>81 <key>StandardOutPath</key><string>/Users/simon-pierreboucher/ka-guardian-runner/runner.log</string>82 <key>StandardErrorPath</key><string>/Users/simon-pierreboucher/ka-guardian-runner/runner.log</string>83</dict></plist>84PLIST85 launchctl unload ~/Library/LaunchAgents/com.ka.guardian-runner.plist 2>/dev/null || true86 launchctl load ~/Library/LaunchAgents/com.ka.guardian-runner.plist"87 sleep 288 ssh $n "curl -sf localhost:7791/health" && echo " ✓ $n runner ok" || echo " ✗ $n runner KO"89 done90}9192deploy_orchestrators() {93 echo "=== orchestrateurs → $ORCH_NODE"94 ssh $ORCH_NODE "printf '%s\n' '$TOKEN_LINE' > ~/.ka-guardian.env; mkdir -p ~/cluster-projects/ka-guardian"95 cd "$ROOT"96 # tar-over-ssh: déterministe (gotcha: rsync silencieusement inopérant depuis97 # ce contexte de script — jamais élucidé, contourné définitivement).98 tar czf - --exclude data --exclude .venv --exclude .git --exclude '__pycache__' --exclude logs . \99 | ssh $ORCH_NODE 'tar xzf - -C ~/cluster-projects/ka-guardian/'100 loc=$(md5 -q orchestrator/main.py); rem=$(ssh $ORCH_NODE 'md5 -q ~/cluster-projects/ka-guardian/orchestrator/main.py')101 [[ "$loc" == "$rem" ]] || { echo "✗ transfert raté: main.py ($loc ≠ $rem)"; exit 1; }102 ssh $ORCH_NODE "cd ~/cluster-projects/ka-guardian103 PY=\$(command -v /opt/homebrew/bin/python3 || command -v /opt/homebrew/bin/python3.13)104 [[ -d .venv ]] || \$PY -m venv .venv105 ./.venv/bin/pip -q install 'fastapi>=0.110' 'uvicorn>=0.29' 'httpx>=0.27' >/dev/null106 mkdir -p data logs107 # retirer les anciens bots (web+bot), garder les tunnels ngrok108 for a in ka2 ka4 ka6; do109 launchctl unload ~/Library/LaunchAgents/com.\$a.web.plist 2>/dev/null || true110 launchctl unload ~/Library/LaunchAgents/com.\$a.bot.plist 2>/dev/null || true111 rm -f ~/Library/LaunchAgents/com.\$a.web.plist ~/Library/LaunchAgents/com.\$a.bot.plist112 done"113 for a in ka2:8799 ka4:8899 ka6:8999; do114 agent=${a%%:*}; port=${a##*:}115 ssh $ORCH_NODE "cat > ~/Library/LaunchAgents/com.$agent.guardian.plist <<PLIST116<?xml version=\"1.0\" encoding=\"UTF-8\"?>117<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">118<plist version=\"1.0\"><dict>119 <key>Label</key><string>com.$agent.guardian</string>120 <key>ProgramArguments</key><array>121 <string>/Users/simon-pierreboucher/cluster-projects/ka-guardian/.venv/bin/python</string>122 <string>/Users/simon-pierreboucher/cluster-projects/ka-guardian/orchestrator/main.py</string>123 </array>124 <key>EnvironmentVariables</key><dict>125 <key>AGENT</key><string>$agent</string>126 <key>KA_GUARDIAN_SELF_IP</key><string>192.168.2.69</string>127 <key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>128 </dict>129 <key>RunAtLoad</key><true/><key>KeepAlive</key><true/>130 <key>StandardOutPath</key><string>/Users/simon-pierreboucher/cluster-projects/ka-guardian/logs/$agent.log</string>131 <key>StandardErrorPath</key><string>/Users/simon-pierreboucher/cluster-projects/ka-guardian/logs/$agent.log</string>132</dict></plist>133PLIST134 launchctl unload ~/Library/LaunchAgents/com.$agent.guardian.plist 2>/dev/null || true135 launchctl load ~/Library/LaunchAgents/com.$agent.guardian.plist"136 sleep 2137 ssh $ORCH_NODE "curl -sf localhost:$port/health" && echo " ✓ $agent ok (:$port)" || echo " ✗ $agent KO"138 done139}140141deploy_pousseur() {142 # Pousseur launchd (binaires Apple) : pousse les commits en attente des repos143 # Ka du nœud — les missions Claude ne peuvent pas pousser elles-mêmes144 # (macOS 26 Local Network Privacy bloque les descendants du runner python).145 typeset -A REPOS146 REPOS[M3U96a]='~/apps/vrai-prix147~/apps/sorti-ka148~/apps/job-ka'149 REPOS[M3U96b]='~/apps/groupe-ka150~/apps/lou-ka151/opt/api-ka152~/apps/crea-ka153~/apps/resto-ka'154 REPOS[M4M64a]='~/apps/immo-ka155~/fabri-ka'156 REPOS[M4M64b]='~/auto-ka157~/apps/food-ka'158 for n in $RUNNER_NODES; do159 echo "=== pousseur → $n"160 scp -q "$ROOT/deploy/pousseur.sh" $n:ka-pousseur.sh161 ssh $n "chmod +x ~/ka-pousseur.sh162 cat > ~/.ka-pousseur-repos <<'REPOS'163${REPOS[$n]}164REPOS165 cat > ~/Library/LaunchAgents/com.ka.pousseur.plist <<'PLIST'166<?xml version=\"1.0\" encoding=\"UTF-8\"?>167<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">168<plist version=\"1.0\"><dict>169 <key>Label</key><string>com.ka.pousseur</string>170 <key>ProgramArguments</key><array>171 <string>/bin/zsh</string>172 <string>/Users/simon-pierreboucher/ka-pousseur.sh</string>173 </array>174 <key>RunAtLoad</key><true/><key>KeepAlive</key><true/>175 <key>StandardOutPath</key><string>/Users/simon-pierreboucher/ka-guardian-spool/pousseur.log</string>176 <key>StandardErrorPath</key><string>/Users/simon-pierreboucher/ka-guardian-spool/pousseur.log</string>177</dict></plist>178PLIST179 mkdir -p ~/ka-guardian-spool180 launchctl unload ~/Library/LaunchAgents/com.ka.pousseur.plist 2>/dev/null || true181 launchctl load ~/Library/LaunchAgents/com.ka.pousseur.plist" \182 && echo " ✓ $n pousseur ok" || echo " ✗ $n pousseur KO"183 done184}185186case "${1:-all}" in187 courier) deploy_courier ;;188 runners) deploy_runners ;;189 orchestrators) deploy_orchestrators ;;190 pousseur) deploy_pousseur ;;191 all) deploy_courier; deploy_runners; deploy_orchestrators; deploy_pousseur ;;192esac193echo "terminé."194