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 <noreply@anthropic.com>
This commit is contained in:
root
2026-03-07 21:24:34 +00:00
parent 9c3f24776c
commit ee6b671e2e
2 changed files with 104 additions and 89 deletions
+12
View File
@@ -42,10 +42,22 @@ def mqtt_publish(topic: str, message: str):
def start_mqtt(): def start_mqtt():
global _mqtt_pub 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, _mqtt_pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=MQTT_CLIENT + "_pub") client_id=MQTT_CLIENT + "_pub")
_mqtt_pub.will_set(_status_topic, _offline_payload, retain=True)
_mqtt_pub.connect(MQTT_HOST, MQTT_PORT) _mqtt_pub.connect(MQTT_HOST, MQTT_PORT)
_mqtt_pub.loop_start() _mqtt_pub.loop_start()
_mqtt_pub.publish(_status_topic, _online_payload, retain=True)
register_to_agent1() register_to_agent1()
def register_to_agent1(): def register_to_agent1():
+87 -84
View File
@@ -2,20 +2,19 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Script de déploiement CLI standalone. 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 : Usage :
python3 deploy.py # déploiement interactif via SSH python3 deploy.py # déploiement interactif (local ou SSH)
python3 deploy.py --local <agent> # installe l'agent sur la machine locale
""" """
import sys import sys
import argparse import re
import getpass import getpass
import json import json
import subprocess
from pathlib import Path from pathlib import Path
# Ajouter le répertoire courant au path pour importer deployer
sys.path.insert(0, str(Path(__file__).parent)) sys.path.insert(0, str(Path(__file__).parent))
from deployer import deploy_agent, load_catalog 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"] return COLORS.get(color, "") + text + COLORS["reset"]
def print_header(): # ── Helpers ───────────────────────────────────────────────────────────────
print(c("bold", "\n=== Agent Deploy — Déploiement interactif ===\n"))
def choose_agent(catalog: dict) -> str: def choose_agent(catalog: dict) -> str:
print(c("cyan", "Agents disponibles :")) print(c("cyan", "\nAgents disponibles :"))
agents = list(catalog.items()) agents = list(catalog.items())
for i, (name, info) in enumerate(agents, 1): for i, (name, info) in enumerate(agents, 1):
print(" {}. {}{}".format(i, c("bold", name), info["description"])) print(" {}. {}{}".format(i, c("bold", name), info["description"]))
print() print()
while True: while True:
choice = input("Choisissez un agent (numéro ou nom) : ").strip() choice = input("Choisissez un agent (numéro ou nom) : ").strip()
if choice.isdigit(): if choice.isdigit():
@@ -54,15 +50,38 @@ def choose_agent(catalog: dict) -> str:
print(c("red", "Choix invalide, réessayez.")) 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: def collect_ssh_info() -> dict:
print(c("cyan", "\nInformations SSH :")) print(c("cyan", "\nInformations SSH :"))
host = input(" Adresse IP de la machine cible : ").strip() host = input(" Adresse IP de la machine cible : ").strip()
user = input(" Nom d'utilisateur SSH : ").strip() user = input(" Nom d'utilisateur SSH : ").strip()
print(" Authentification :") print(" Authentification :")
print(" 1. Mot de passe") print(" 1. Mot de passe")
print(" 2. Clé SSH") print(" 2. Clé SSH")
while True: while True:
auth = input(" Choix (1/2) : ").strip() auth = input(" Choix (1/2) : ").strip()
if auth == "1": if auth == "1":
@@ -78,93 +97,68 @@ def collect_ssh_info() -> dict:
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: def collect_agent_config(agent_name: str, host: str) -> dict:
print(c("cyan", "\nConfiguration de l'agent :")) print(c("cyan", "\nConfiguration XMPP/MQTT :"))
default_jid = "{}@xmpp.ovh".format(agent_type) default_jid = "{}@xmpp.ovh".format(agent_name)
xmpp_jid = input(" JID XMPP [{}] : ".format(default_jid)).strip() or default_jid xmpp_jid = input(" JID XMPP [{}] : ".format(default_jid)).strip() or default_jid
xmpp_pass = getpass.getpass(" Mot de passe XMPP : ") 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} 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): # ── Déploiement local ──────────────────────────────────────────────────────
def deploy_local(agent_type: str, agent_name: str, catalog: dict):
info = catalog[agent_type] 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
info = catalog[agent_type]
install_path = info["install_path"]
repo_url = info["repo_url"] repo_url = info["repo_url"]
service_name = info["service_name"]
main_script = info["main_script"] main_script = info["main_script"]
deps = info["dependencies"] 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): def run(cmd):
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, **kwargs) result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0: if result.returncode != 0:
print(c("red", " Erreur : {}".format(result.stderr.strip() or result.stdout.strip()))) print(c("red", " Erreur : {}".format(result.stderr.strip() or result.stdout.strip())))
sys.exit(1) sys.exit(1)
return result.stdout.strip() return result.stdout.strip()
# Prérequis print(" [1/5] Installation des prérequis...")
print(" Installation des prérequis...")
run("apt-get install -y -qq python3 python3-pip python3-venv git 2>&1") run("apt-get install -y -qq python3 python3-pip python3-venv git 2>&1")
# Clone print(" [2/5] Clonage du dépôt...")
print(" Clonage du dépôt...")
if Path(install_path + "/.git").exists(): if Path(install_path + "/.git").exists():
run("git -C {} pull".format(install_path)) run("git -C {} pull".format(install_path))
else: else:
run("git clone {} {}".format(repo_url, install_path)) run("git clone {} {}".format(repo_url, install_path))
# Venv print(" [3/5] Création du venv et installation des dépendances...")
print(" Création du venv...")
run("python3 -m venv {}/venv".format(install_path)) run("python3 -m venv {}/venv".format(install_path))
run("{}/venv/bin/pip install -q {}".format(install_path, " ".join(deps))) run("{}/venv/bin/pip install -q {}".format(install_path, " ".join(deps)))
# Config print(" [4/5] Configuration...")
print(c("cyan", "\nConfiguration de l'agent :")) agent_cfg = collect_agent_config(agent_name, "localhost")
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"
config = { config = {
"ollama_url" : "http://192.168.7.119:11434/api/chat", "ollama_url" : "http://192.168.7.119:11434/api/chat",
"model" : "qwen3:8b", "model" : "qwen3:8b",
"xmpp_jid" : xmpp_jid, "xmpp_jid" : agent_cfg["xmpp_jid"],
"xmpp_pass" : xmpp_pass, "xmpp_pass" : agent_cfg["xmpp_pass"],
"admin_jid" : "sylvain@xmpp.ovh", "admin_jid" : "sylvain@xmpp.ovh",
"db_path" : "{}/memory.db".format(install_path), "db_path" : "{}/memory.db".format(install_path),
"mqtt_host" : mqtt_host, "mqtt_host" : agent_cfg["mqtt_host"],
"mqtt_port" : 1883, "mqtt_port" : 1883,
"mqtt_client_id": agent_type, "mqtt_client_id": agent_name,
"mqtt_inbox" : info["mqtt_inbox"], "mqtt_inbox" : "agents/{}/inbox".format(agent_name),
"mqtt_outbox" : info["mqtt_outbox"], "mqtt_outbox" : "agents/agent1/inbox",
} }
config_path = Path(install_path) / "config" / "config.json" config_path = Path(install_path) / "config" / "config.json"
config_path.write_text(json.dumps(config, indent=2, ensure_ascii=False)) 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] service = """[Unit]
Description=Agent {name} Description=Agent {name}
After=network.target mosquitto.service After=network.target mosquitto.service
@@ -181,41 +175,49 @@ StandardError=journal
[Install] [Install]
WantedBy=multi-user.target 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) 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(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(" Service : systemctl status {}".format(service_name))
print(" Logs : journalctl -u {} -f".format(service_name)) print(" Logs : journalctl -u {} -f".format(service_name))
def main(): # ── 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() def main():
print(c("bold", "\n=== Agent Deploy — Déploiement interactif ==="))
catalog = load_catalog() catalog = load_catalog()
# Mode local agent_type = choose_agent(catalog)
if args.local: agent_name = choose_name(agent_type)
agent_type = args.local if args.local in catalog else None mode = choose_mode()
if not agent_type:
print(c("red", "Agent inconnu : {}".format(args.local))) if mode == "local":
print("Agents disponibles : {}".format(", ".join(catalog.keys()))) deploy_local(agent_type, agent_name, catalog)
sys.exit(1)
deploy_local(agent_type, catalog)
return return
# Mode SSH interactif # Mode SSH
agent_type = choose_agent(catalog)
ssh = collect_ssh_info() ssh = collect_ssh_info()
agent_cfg = collect_agent_config(agent_type, catalog, ssh["host"]) 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() confirm = input("Confirmer le déploiement ? (oui/non) : ").strip().lower()
if confirm not in ("oui", "o", "yes", "y"): if confirm not in ("oui", "o", "yes", "y"):
print(c("yellow", "Déploiement annulé.")) print(c("yellow", "Déploiement annulé."))
@@ -230,6 +232,7 @@ def main():
host = ssh["host"], host = ssh["host"],
ssh_user = ssh["user"], ssh_user = ssh["user"],
agent_type = agent_type, agent_type = agent_type,
agent_name = agent_name,
xmpp_jid = agent_cfg["xmpp_jid"], xmpp_jid = agent_cfg["xmpp_jid"],
xmpp_pass = agent_cfg["xmpp_pass"], xmpp_pass = agent_cfg["xmpp_pass"],
mqtt_host = agent_cfg["mqtt_host"], mqtt_host = agent_cfg["mqtt_host"],