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:
@@ -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."
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
+86
-28
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user