From ee6b671e2ee47dff53467f8b14887e7297ec832f Mon Sep 17 00:00:00 2001 From: root Date: Sat, 7 Mar 2026 21:24:34 +0000 Subject: [PATCH] deploy.py : choix local/distant interactif + agent_name partout agent2_deploy : MQTT Last Will + publish retain sur agents/status/{name} Co-Authored-By: Claude Sonnet 4.6 --- agent2_deploy.py | 12 ++++ deploy.py | 181 ++++++++++++++++++++++++----------------------- 2 files changed, 104 insertions(+), 89 deletions(-) diff --git a/agent2_deploy.py b/agent2_deploy.py index 9189859..21e15bd 100644 --- a/agent2_deploy.py +++ b/agent2_deploy.py @@ -42,10 +42,22 @@ def mqtt_publish(topic: str, message: str): def start_mqtt(): global _mqtt_pub + import json as _json + _status_topic = "agents/status/{}".format(MQTT_CLIENT) + _offline_payload = _json.dumps({"status": "offline", "agent": MQTT_CLIENT}) + _online_payload = _json.dumps({ + "status" : "online", + "agent" : MQTT_CLIENT, + "jid" : XMPP_JID, + "mqtt_inbox": cfg["mqtt_inbox"], + }) + _mqtt_pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=MQTT_CLIENT + "_pub") + _mqtt_pub.will_set(_status_topic, _offline_payload, retain=True) _mqtt_pub.connect(MQTT_HOST, MQTT_PORT) _mqtt_pub.loop_start() + _mqtt_pub.publish(_status_topic, _online_payload, retain=True) register_to_agent1() def register_to_agent1(): diff --git a/deploy.py b/deploy.py index ea877e8..c40e37b 100644 --- a/deploy.py +++ b/deploy.py @@ -2,20 +2,19 @@ # -*- coding: utf-8 -*- """ Script de déploiement CLI standalone. -Utilisable après un git clone, sans avoir besoin d'un agent XMPP actif. +Utilisable après un git clone, sans besoin d'un agent XMPP actif. Usage : - python3 deploy.py # déploiement interactif via SSH - python3 deploy.py --local # installe l'agent sur la machine locale + python3 deploy.py # déploiement interactif (local ou SSH) """ import sys -import argparse +import re import getpass import json +import subprocess from pathlib import Path -# Ajouter le répertoire courant au path pour importer deployer sys.path.insert(0, str(Path(__file__).parent)) from deployer import deploy_agent, load_catalog @@ -32,17 +31,14 @@ def c(color: str, text: str) -> str: return COLORS.get(color, "") + text + COLORS["reset"] -def print_header(): - print(c("bold", "\n=== Agent Deploy — Déploiement interactif ===\n")) - +# ── Helpers ─────────────────────────────────────────────────────────────── def choose_agent(catalog: dict) -> str: - print(c("cyan", "Agents disponibles :")) + print(c("cyan", "\nAgents disponibles :")) agents = list(catalog.items()) for i, (name, info) in enumerate(agents, 1): print(" {}. {} — {}".format(i, c("bold", name), info["description"])) print() - while True: choice = input("Choisissez un agent (numéro ou nom) : ").strip() if choice.isdigit(): @@ -54,15 +50,38 @@ def choose_agent(catalog: dict) -> str: print(c("red", "Choix invalide, réessayez.")) +def choose_name(agent_type: str) -> str: + print(c("cyan", "\nNom de l'agent :")) + print(" Ce nom sera utilisé dans vos commandes (ex: \"mets à jour trouducul\"),") + print(" comme identifiant MQTT et nom du service systemd.") + print(" Lettres, chiffres, tirets et underscores uniquement.") + while True: + name = input(" Nom [{}] : ".format(agent_type)).strip().lower() or agent_type + if re.match(r'^[a-z0-9_-]+$', name): + return name + print(c("red", " Nom invalide. Utilisez lettres, chiffres, tirets ou underscores.")) + + +def choose_mode() -> str: + print(c("cyan", "\nMode de déploiement :")) + print(" 1. Localement (sur cette machine)") + print(" 2. Machine distante via SSH") + while True: + choice = input(" Choix (1/2) : ").strip() + if choice == "1": + return "local" + if choice == "2": + return "ssh" + print(c("red", " Répondez 1 ou 2.")) + + def collect_ssh_info() -> dict: print(c("cyan", "\nInformations SSH :")) - host = input(" Adresse IP de la machine cible : ").strip() - user = input(" Nom d'utilisateur SSH : ").strip() - + host = input(" Adresse IP de la machine cible : ").strip() + user = input(" Nom d'utilisateur SSH : ").strip() print(" Authentification :") print(" 1. Mot de passe") print(" 2. Clé SSH") - while True: auth = input(" Choix (1/2) : ").strip() if auth == "1": @@ -75,96 +94,71 @@ def collect_ssh_info() -> dict: key_path = str(Path.home() / ".ssh" / "id_rsa") return {"host": host, "user": user, "auth": "key", "password": None, "key_path": key_path} - print(c("red", "Répondez 1 ou 2.")) + print(c("red", " Répondez 1 ou 2.")) -def collect_agent_config(agent_type: str, catalog: dict, host: str) -> dict: - print(c("cyan", "\nConfiguration de l'agent :")) - default_jid = "{}@xmpp.ovh".format(agent_type) +def collect_agent_config(agent_name: str, host: str) -> dict: + print(c("cyan", "\nConfiguration XMPP/MQTT :")) + default_jid = "{}@xmpp.ovh".format(agent_name) xmpp_jid = input(" JID XMPP [{}] : ".format(default_jid)).strip() or default_jid xmpp_pass = getpass.getpass(" Mot de passe XMPP : ") - mqtt_host = input(" Broker MQTT [{}] : ".format(host)).strip() or host + default_mqtt = host if host else "localhost" + mqtt_host = input(" Broker MQTT [{}] : ".format(default_mqtt)).strip() or default_mqtt return {"xmpp_jid": xmpp_jid, "xmpp_pass": xmpp_pass, "mqtt_host": mqtt_host} -def show_summary(agent_type: str, ssh: dict, agent_cfg: dict, catalog: dict): - info = catalog[agent_type] - print(c("cyan", "\nRécapitulatif :")) - print(" Agent : {}".format(c("bold", agent_type))) - print(" Repo : {}".format(info["repo_url"])) - print(" Machine : {}@{}".format(ssh["user"], ssh["host"])) - print(" Auth SSH : {}".format("clé SSH" if ssh["auth"] == "key" else "mot de passe")) - print(" XMPP JID : {}".format(agent_cfg["xmpp_jid"])) - print(" MQTT broker: {}".format(agent_cfg["mqtt_host"])) - print(" MQTT inbox : {}".format(info["mqtt_inbox"])) - print() - - -def deploy_local(agent_type: str, catalog: dict): - """ - Installe l'agent localement (sur la machine courante). - Utile quand on clone le repo directement sur la machine cible. - """ - import subprocess - from pathlib import Path +# ── Déploiement local ────────────────────────────────────────────────────── +def deploy_local(agent_type: str, agent_name: str, catalog: dict): info = catalog[agent_type] - install_path = info["install_path"] repo_url = info["repo_url"] - service_name = info["service_name"] main_script = info["main_script"] deps = info["dependencies"] + install_path = "/opt/{}".format(agent_name) + service_name = agent_name - print(c("cyan", "\nDéploiement local de {} dans {}...".format(agent_type, install_path))) + print(c("cyan", "\nDéploiement local de «{}» ({}) dans {}...".format( + agent_name, agent_type, install_path))) - def run(cmd, **kwargs): - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, **kwargs) + def run(cmd): + result = subprocess.run(cmd, shell=True, capture_output=True, text=True) if result.returncode != 0: print(c("red", " Erreur : {}".format(result.stderr.strip() or result.stdout.strip()))) sys.exit(1) return result.stdout.strip() - # Prérequis - print(" Installation des prérequis...") + print(" [1/5] Installation des prérequis...") run("apt-get install -y -qq python3 python3-pip python3-venv git 2>&1") - # Clone - print(" Clonage du dépôt...") + print(" [2/5] Clonage du dépôt...") if Path(install_path + "/.git").exists(): run("git -C {} pull".format(install_path)) else: run("git clone {} {}".format(repo_url, install_path)) - # Venv - print(" Création du venv...") + print(" [3/5] Création du venv et installation des dépendances...") run("python3 -m venv {}/venv".format(install_path)) run("{}/venv/bin/pip install -q {}".format(install_path, " ".join(deps))) - # Config - print(c("cyan", "\nConfiguration de l'agent :")) - default_jid = "{}@xmpp.ovh".format(agent_type) - xmpp_jid = input(" JID XMPP [{}] : ".format(default_jid)).strip() or default_jid - xmpp_pass = getpass.getpass(" Mot de passe XMPP : ") - mqtt_host = input(" Broker MQTT [localhost] : ").strip() or "localhost" - + print(" [4/5] Configuration...") + agent_cfg = collect_agent_config(agent_name, "localhost") config = { "ollama_url" : "http://192.168.7.119:11434/api/chat", "model" : "qwen3:8b", - "xmpp_jid" : xmpp_jid, - "xmpp_pass" : xmpp_pass, + "xmpp_jid" : agent_cfg["xmpp_jid"], + "xmpp_pass" : agent_cfg["xmpp_pass"], "admin_jid" : "sylvain@xmpp.ovh", "db_path" : "{}/memory.db".format(install_path), - "mqtt_host" : mqtt_host, + "mqtt_host" : agent_cfg["mqtt_host"], "mqtt_port" : 1883, - "mqtt_client_id": agent_type, - "mqtt_inbox" : info["mqtt_inbox"], - "mqtt_outbox" : info["mqtt_outbox"], + "mqtt_client_id": agent_name, + "mqtt_inbox" : "agents/{}/inbox".format(agent_name), + "mqtt_outbox" : "agents/agent1/inbox", } config_path = Path(install_path) / "config" / "config.json" config_path.write_text(json.dumps(config, indent=2, ensure_ascii=False)) - print(" config.json écrit.") - # Service systemd + print(" [5/5] Création et démarrage du service systemd...") service = """[Unit] Description=Agent {name} After=network.target mosquitto.service @@ -181,41 +175,49 @@ StandardError=journal [Install] WantedBy=multi-user.target -""".format(name=agent_type, path=install_path, script=main_script) +""".format(name=agent_name, path=install_path, script=main_script) Path("/etc/systemd/system/{}.service".format(service_name)).write_text(service) - run("systemctl daemon-reload && systemctl enable {s} && systemctl start {s}".format(s=service_name)) + run("systemctl daemon-reload && systemctl enable {s} && systemctl start {s}".format( + s=service_name)) - print(c("green", "\nDéploiement local terminé !")) - print(" Service : systemctl status {}".format(service_name)) - print(" Logs : journalctl -u {} -f".format(service_name)) + print(c("green", "\nDéploiement local de «{}» terminé !".format(agent_name))) + print(" XMPP JID : {}".format(agent_cfg["xmpp_jid"])) + print(" MQTT inbox : agents/{}/inbox".format(agent_name)) + print(" Service : systemctl status {}".format(service_name)) + print(" Logs : journalctl -u {} -f".format(service_name)) +# ── Main ─────────────────────────────────────────────────────────────────── + def main(): - parser = argparse.ArgumentParser(description="Déploiement interactif d'agents") - parser.add_argument("--local", metavar="AGENT", - help="Installer l'agent localement (sans SSH)") - args = parser.parse_args() - - print_header() + print(c("bold", "\n=== Agent Deploy — Déploiement interactif ===")) catalog = load_catalog() - # Mode local - if args.local: - agent_type = args.local if args.local in catalog else None - if not agent_type: - print(c("red", "Agent inconnu : {}".format(args.local))) - print("Agents disponibles : {}".format(", ".join(catalog.keys()))) - sys.exit(1) - deploy_local(agent_type, catalog) + agent_type = choose_agent(catalog) + agent_name = choose_name(agent_type) + mode = choose_mode() + + if mode == "local": + deploy_local(agent_type, agent_name, catalog) return - # Mode SSH interactif - agent_type = choose_agent(catalog) - ssh = collect_ssh_info() - agent_cfg = collect_agent_config(agent_type, catalog, ssh["host"]) + # Mode SSH + ssh = collect_ssh_info() + agent_cfg = collect_agent_config(agent_name, ssh["host"]) + + print(c("cyan", "\nRécapitulatif :")) + print(" Type : {}".format(agent_type)) + print(" Nom : {}".format(c("bold", agent_name))) + print(" Repo : {}".format(catalog[agent_type]["repo_url"])) + print(" Machine : {}@{}".format(ssh["user"], ssh["host"])) + print(" Auth SSH : {}".format("clé SSH" if ssh["auth"] == "key" else "mot de passe")) + print(" XMPP JID : {}".format(agent_cfg["xmpp_jid"])) + print(" MQTT broker: {}".format(agent_cfg["mqtt_host"])) + print(" MQTT inbox : agents/{}/inbox".format(agent_name)) + print(" Service : {}.service".format(agent_name)) + print() - show_summary(agent_type, ssh, agent_cfg, catalog) confirm = input("Confirmer le déploiement ? (oui/non) : ").strip().lower() if confirm not in ("oui", "o", "yes", "y"): print(c("yellow", "Déploiement annulé.")) @@ -230,6 +232,7 @@ def main(): host = ssh["host"], ssh_user = ssh["user"], agent_type = agent_type, + agent_name = agent_name, xmpp_jid = agent_cfg["xmpp_jid"], xmpp_pass = agent_cfg["xmpp_pass"], mqtt_host = agent_cfg["mqtt_host"],