Support agents distants (SSH) pour !agentUPDATE/UPGRADE

- agent_update.py : _run_ssh() via sshpass, dispatche local ou SSH selon ssh_host
- agent1.py : _get_agent_git_info() transmet ssh_host/ssh_user depuis le registre
- agents_registry.json : agent2_test → ssh_host: 192.168.7.13, ssh_user: root

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 16:11:43 +00:00
parent 1565b145dc
commit 0fe1ece68d
3 changed files with 144 additions and 38 deletions
+10 -4
View File
@@ -226,12 +226,15 @@ def _get_agent_git_info(name: str) -> dict:
"install_path": agent.get("install_path", default_path), "install_path": agent.get("install_path", default_path),
"service_name": agent.get("service_name", default_service), "service_name": agent.get("service_name", default_service),
"git_branch" : agent.get("git_branch", "main"), "git_branch" : agent.get("git_branch", "main"),
"ssh_host" : agent.get("ssh_host"),
"ssh_user" : agent.get("ssh_user", "root"),
} }
def _handle_update_one(name: str) -> str: def _handle_update_one(name: str) -> str:
from skills.agent_update import check_update from skills.agent_update import check_update
info = _get_agent_git_info(name) info = _get_agent_git_info(name)
return check_update(name, info["install_path"], info["git_branch"]) return check_update(name, info["install_path"], info["git_branch"],
info["ssh_host"], info["ssh_user"])
def _handle_update_all() -> str: def _handle_update_all() -> str:
from skills.agent_update import check_update from skills.agent_update import check_update
@@ -242,7 +245,8 @@ def _handle_update_all() -> str:
results = [] results = []
for name in registry: for name in registry:
info = _get_agent_git_info(name) info = _get_agent_git_info(name)
results.append(check_update(name, info["install_path"], info["git_branch"])) results.append(check_update(name, info["install_path"], info["git_branch"],
info["ssh_host"], info["ssh_user"]))
return "\n\n".join(results) if results else "Aucun agent dans le registre." return "\n\n".join(results) if results else "Aucun agent dans le registre."
def _handle_upgrade_one(name: str) -> str: def _handle_upgrade_one(name: str) -> str:
@@ -250,7 +254,8 @@ def _handle_upgrade_one(name: str) -> str:
info = _get_agent_git_info(name) info = _get_agent_git_info(name)
self_upgrade = (name == "agent1") self_upgrade = (name == "agent1")
msg = do_upgrade(name, info["install_path"], info["service_name"], msg = do_upgrade(name, info["install_path"], info["service_name"],
info["git_branch"], self_upgrade=self_upgrade) info["git_branch"], self_upgrade=self_upgrade,
ssh_host=info["ssh_host"], ssh_user=info["ssh_user"])
if self_upgrade and "Redémarrage en cours" in msg: if self_upgrade and "Redémarrage en cours" in msg:
# Envoyer le message XMPP avant le restart # Envoyer le message XMPP avant le restart
if xmpp_bot: if xmpp_bot:
@@ -276,7 +281,8 @@ def _handle_upgrade_all() -> str:
agent1_info = (name, info) # traiter en dernier agent1_info = (name, info) # traiter en dernier
continue continue
msg = do_upgrade(name, info["install_path"], msg = do_upgrade(name, info["install_path"],
info["service_name"], info["git_branch"]) info["service_name"], info["git_branch"],
ssh_host=info["ssh_host"], ssh_user=info["ssh_user"])
results.append(msg) results.append(msg)
summary = "\n\n".join(results) if results else "Aucun agent mis à jour." summary = "\n\n".join(results) if results else "Aucun agent mis à jour."
+46 -4
View File
@@ -4,7 +4,20 @@
"mqtt_inbox": "agents/agent2_debian13/inbox", "mqtt_inbox": "agents/agent2_debian13/inbox",
"mqtt_outbox": "agents/agent1/inbox", "mqtt_outbox": "agents/agent1/inbox",
"speciality": "Administration Debian : apt, dpkg, systemd, conteneurs LXC/Docker, KVM, réseau, sécurité système", "speciality": "Administration Debian : apt, dpkg, systemd, conteneurs LXC/Docker, KVM, réseau, sécurité système",
"work_hours": { "start": "07:00", "end": "23:00", "days": ["mon","tue","wed","thu","fri","sat","sun"], "enabled": true }, "work_hours": {
"start": "07:00",
"end": "23:00",
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"enabled": true
},
"install_path": "/opt/agent2_debian13", "install_path": "/opt/agent2_debian13",
"service_name": "agent2_debian13", "service_name": "agent2_debian13",
"git_branch": "main" "git_branch": "main"
@@ -14,7 +27,20 @@
"mqtt_inbox": "agents/agent2_ansible/inbox", "mqtt_inbox": "agents/agent2_ansible/inbox",
"mqtt_outbox": "agents/agent1/inbox", "mqtt_outbox": "agents/agent1/inbox",
"speciality": "Automatisation infrastructure via Ansible : playbooks, commandes ad-hoc, déploiement multi-hôtes, gestion de configuration sur le réseau local", "speciality": "Automatisation infrastructure via Ansible : playbooks, commandes ad-hoc, déploiement multi-hôtes, gestion de configuration sur le réseau local",
"work_hours": { "start": "07:00", "end": "23:00", "days": ["mon","tue","wed","thu","fri","sat","sun"], "enabled": true }, "work_hours": {
"start": "07:00",
"end": "23:00",
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"enabled": true
},
"install_path": "/opt/agent2_ansible", "install_path": "/opt/agent2_ansible",
"service_name": "agent2_ansible", "service_name": "agent2_ansible",
"git_branch": "main" "git_branch": "main"
@@ -24,7 +50,18 @@
"mqtt_inbox": "agents/agent2_deploy/inbox", "mqtt_inbox": "agents/agent2_deploy/inbox",
"mqtt_outbox": "agents/agent1/inbox", "mqtt_outbox": "agents/agent1/inbox",
"speciality": "Déploiement d'agents : installe et configure d'autres agents sur des machines distantes ou locales via SSH", "speciality": "Déploiement d'agents : installe et configure d'autres agents sur des machines distantes ou locales via SSH",
"work_hours": { "start": "08:00", "end": "20:00", "days": ["mon","tue","wed","thu","fri"], "enabled": true }, "work_hours": {
"start": "08:00",
"end": "20:00",
"days": [
"mon",
"tue",
"wed",
"thu",
"fri"
],
"enabled": true
},
"install_path": "/opt/agent2_deploy", "install_path": "/opt/agent2_deploy",
"service_name": "agent2_deploy", "service_name": "agent2_deploy",
"git_branch": "main" "git_branch": "main"
@@ -33,7 +70,12 @@
"jid": "agent2_test@xmpp.ovh", "jid": "agent2_test@xmpp.ovh",
"mqtt_inbox": "agents/agent2_test/inbox", "mqtt_inbox": "agents/agent2_test/inbox",
"mqtt_outbox": "agents/agent1/inbox", "mqtt_outbox": "agents/agent1/inbox",
"speciality": "Spécialiste Debian : apt, systemd, conteneurs LXC/Docker, KVM, réseau, sécurité" "speciality": "Administration Debian : apt, dpkg, systemd, conteneurs LXC/Docker, KVM, réseau, sécurité système",
"install_path": "/opt/agent2_test",
"service_name": "agent2_test",
"git_branch": "main",
"ssh_host": "192.168.7.13",
"ssh_user": "root"
}, },
"agent1": { "agent1": {
"jid": "agent1@xmpp.ovh", "jid": "agent1@xmpp.ovh",
+87 -29
View File
@@ -3,16 +3,18 @@ Utilitaire : vérification et application des mises à jour git pour les agents.
Fonctions appelées directement depuis agent1.py (pas de trigger LLM). Fonctions appelées directement depuis agent1.py (pas de trigger LLM).
check_update(name, install_path, branch) → rapport git fetch check_update(name, install_path, branch, ssh_host, ssh_user)
do_upgrade(name, install_path, service, branch) → git pull + systemctl restart do_upgrade(name, install_path, service, branch, ssh_host, ssh_user, self_upgrade)
Si ssh_host est défini, les commandes sont exécutées via SSH sur la machine distante.
""" """
import subprocess import subprocess
import shlex import shlex
from pathlib import Path from pathlib import Path
def _run(cmd: str, cwd: str = None, timeout: int = 30) -> tuple: def _run_local(cmd: str, cwd: str = None, timeout: int = 30) -> tuple:
"""Lance une commande shell, retourne (stdout, stderr, returncode).""" """Lance une commande locale, retourne (stdout, stderr, returncode)."""
try: try:
result = subprocess.run( result = subprocess.run(
shlex.split(cmd), shlex.split(cmd),
@@ -28,32 +30,74 @@ def _run(cmd: str, cwd: str = None, timeout: int = 30) -> tuple:
return "", str(e), -1 return "", str(e), -1
def check_update(agent_name: str, install_path: str, branch: str = "main") -> str: def _run_ssh(cmd: str, ssh_host: str, ssh_user: str = "root",
cwd: str = None, timeout: int = 30) -> tuple:
"""Lance une commande via SSH, retourne (stdout, stderr, returncode)."""
if cwd:
cmd = "cd {} && {}".format(cwd, cmd)
ssh_cmd = "sshpass -p 'Matador3721' ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 {}@{} {}".format(
ssh_user, ssh_host, shlex.quote(cmd))
try:
result = subprocess.run(
shlex.split(ssh_cmd),
capture_output=True,
text=True,
timeout=timeout
)
return result.stdout.strip(), result.stderr.strip(), result.returncode
except subprocess.TimeoutExpired:
return "", "Timeout SSH ({} s)".format(timeout), -1
except Exception as e:
return "", str(e), -1
def _run(cmd: str, cwd: str = None, timeout: int = 30,
ssh_host: str = None, ssh_user: str = "root") -> tuple:
"""Dispatche local ou SSH selon ssh_host."""
if ssh_host:
return _run_ssh(cmd, ssh_host, ssh_user, cwd=cwd, timeout=timeout)
return _run_local(cmd, cwd=cwd, timeout=timeout)
def check_update(agent_name: str, install_path: str, branch: str = "main",
ssh_host: str = None, ssh_user: str = "root") -> str:
""" """
Vérifie si une mise à jour est disponible sur le dépôt distant. Vérifie si une mise à jour est disponible sur le dépôt distant.
Effectue un git fetch puis compare HEAD avec origin/<branch>. Effectue un git fetch puis compare HEAD avec origin/<branch>.
""" """
path = install_path remote = " [{}]".format(ssh_host) if ssh_host else ""
if not Path(path).is_dir(): # Vérifier que le répertoire existe
return "[{}] Répertoire introuvable : {}".format(agent_name, path) if ssh_host:
out, _, rc = _run("test -d {}".format(install_path),
ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0:
return "[{}{}] Répertoire introuvable : {}".format(
agent_name, remote, install_path)
else:
if not Path(install_path).is_dir():
return "[{}] Répertoire introuvable : {}".format(agent_name, install_path)
# git fetch # git fetch
out, err, rc = _run("git fetch origin {}".format(branch), cwd=path, timeout=20) out, err, rc = _run("git fetch origin {}".format(branch),
cwd=install_path, timeout=20,
ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0: if rc != 0:
return "[{}] Erreur git fetch : {}".format(agent_name, err or out) return "[{}{}] Erreur git fetch : {}".format(agent_name, remote, err or out)
# Compter les commits disponibles # Compter les commits disponibles
out, err, rc = _run( out, err, rc = _run(
"git log HEAD..origin/{} --oneline".format(branch), cwd=path) "git log HEAD..origin/{} --oneline".format(branch),
cwd=install_path, ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0: if rc != 0:
return "[{}] Erreur git log : {}".format(agent_name, err or out) return "[{}{}] Erreur git log : {}".format(agent_name, remote, err or out)
commits = [l for l in out.splitlines() if l.strip()] commits = [l for l in out.splitlines() if l.strip()]
if not commits: if not commits:
return "[{}] Déjà à jour.".format(agent_name) return "[{}{}] Déjà à jour.".format(agent_name, remote)
lines = ["[{}] {} commit(s) disponible(s) :".format(agent_name, len(commits))] lines = ["[{}{}] {} commit(s) disponible(s) :".format(
agent_name, remote, len(commits))]
for c in commits[:10]: for c in commits[:10]:
lines.append(" {}".format(c)) lines.append(" {}".format(c))
if len(commits) > 10: if len(commits) > 10:
@@ -64,38 +108,52 @@ def check_update(agent_name: str, install_path: str, branch: str = "main") -> st
def do_upgrade(agent_name: str, install_path: str, def do_upgrade(agent_name: str, install_path: str,
service_name: str, branch: str = "main", service_name: str, branch: str = "main",
self_upgrade: bool = False) -> str: self_upgrade: bool = False,
ssh_host: str = None, ssh_user: str = "root") -> str:
""" """
Applique la mise à jour : git pull + systemctl restart. Applique la mise à jour : git pull + systemctl restart.
Pour agent1 (self_upgrade=True), envoie la réponse AVANT le restart. Pour agent1 (self_upgrade=True), retourne le message AVANT le restart.
""" """
path = install_path remote = " [{}]".format(ssh_host) if ssh_host else ""
if not Path(path).is_dir(): # Vérifier que le répertoire existe
return "[{}] Répertoire introuvable : {}".format(agent_name, path) if ssh_host:
_, _, rc = _run("test -d {}".format(install_path),
ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0:
return "[{}{}] Répertoire introuvable : {}".format(
agent_name, remote, install_path)
else:
if not Path(install_path).is_dir():
return "[{}] Répertoire introuvable : {}".format(agent_name, install_path)
# git pull # git pull
out, err, rc = _run( out, err, rc = _run(
"git pull origin {}".format(branch), cwd=path, timeout=60) "git pull origin {}".format(branch),
cwd=install_path, timeout=60,
ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0: if rc != 0:
return "[{}] Erreur git pull : {}".format(agent_name, err or out) return "[{}{}] Erreur git pull : {}".format(agent_name, remote, err or out)
pull_msg = out or "Déjà à jour." pull_msg = out or "Déjà à jour."
if self_upgrade: if self_upgrade:
# On retourne le message AVANT le restart (agent1 l'envoie puis se redémarre) return "[{}] {}\nRedémarrage en cours...".format(agent_name, pull_msg)
return "[{}] {} \nRedémarrage en cours...".format(agent_name, pull_msg)
# systemctl restart # systemctl restart
_, err, rc = _run("systemctl restart {}".format(service_name), timeout=15) _, err, rc = _run("systemctl restart {}".format(service_name),
timeout=15, ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0: if rc != 0:
return "[{}] Pull OK mais restart échoué : {}".format(agent_name, err) return "[{}{}] Pull OK mais restart échoué : {}".format(
agent_name, remote, err)
# Vérifier que le service est bien remonté # Vérifier que le service est bien remonté
out, _, _ = _run("systemctl is-active {}".format(service_name), timeout=5) out, _, _ = _run("systemctl is-active {}".format(service_name),
timeout=5, ssh_host=ssh_host, ssh_user=ssh_user)
state = out.strip() state = out.strip()
if state == "active": if state == "active":
return "[{}] Mise à jour appliquée. Service actif.\n{}".format(agent_name, pull_msg) return "[{}{}] Mise à jour appliquée. Service actif.\n{}".format(
agent_name, remote, pull_msg)
else: else:
return "[{}] Pull OK, service état : {}. Vérifiez les logs.".format( return "[{}{}] Pull OK, service état : {}. Vérifiez les logs.".format(
agent_name, state) agent_name, remote, state)