Initial commit : agent2_deploy - déploiement interactif via XMPP/SSH
- agent2_deploy.py : bot XMPP avec machine à états pour le déploiement guidé - deployer.py : logique SSH partagée (paramiko) - deploy.py : script CLI standalone (après git clone) - agents_catalog.json : catalogue des agents déployables - README.md : documentation complète Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
|||||||
|
# Agent2_Deploy — Agent de déploiement
|
||||||
|
|
||||||
|
Agent spécialisé dans le déploiement d'autres agents sur des machines distantes ou locales. Interactif via XMPP, il guide l'utilisateur étape par étape pour déployer un agent via SSH, puis notifie agent1 du nouveau déploiement.
|
||||||
|
|
||||||
|
## Rôle
|
||||||
|
|
||||||
|
- Déployer des agents spécialisés sur n'importe quelle machine via SSH
|
||||||
|
- Guider l'utilisateur interactivement (choix de l'agent, IP, credentials SSH, config XMPP/MQTT)
|
||||||
|
- Cloner automatiquement le dépôt git de l'agent cible
|
||||||
|
- Créer le service systemd sur la machine distante
|
||||||
|
- Notifier agent1 du nouveau déploiement (via MQTT)
|
||||||
|
|
||||||
|
## Agents déployables
|
||||||
|
|
||||||
|
Définis dans `agents_catalog.json` :
|
||||||
|
|
||||||
|
| Agent | Description |
|
||||||
|
|---|---|
|
||||||
|
| `agent2_debian13` | Spécialiste Debian (apt, systemd, docker...) |
|
||||||
|
| `agent2_ansible` | Automatisation Ansible (playbooks, ad-hoc...) |
|
||||||
|
| `agent2_deploy` | Agent de déploiement (ce même agent) |
|
||||||
|
|
||||||
|
## Utilisation via XMPP
|
||||||
|
|
||||||
|
Contacter `agent2_deploy@xmpp.ovh` via XMPP :
|
||||||
|
|
||||||
|
```
|
||||||
|
!deploy # Démarrer un déploiement guidé
|
||||||
|
!agents # Lister les agents disponibles
|
||||||
|
!annuler # Annuler le déploiement en cours
|
||||||
|
!help # Aide
|
||||||
|
```
|
||||||
|
|
||||||
|
### Déroulement du déploiement guidé
|
||||||
|
|
||||||
|
1. Choix de l'agent à déployer
|
||||||
|
2. Adresse IP de la machine cible
|
||||||
|
3. Nom d'utilisateur SSH
|
||||||
|
4. Authentification : mot de passe ou clé SSH
|
||||||
|
5. JID XMPP pour l'instance déployée
|
||||||
|
6. Mot de passe XMPP
|
||||||
|
7. Adresse du broker MQTT
|
||||||
|
8. Confirmation et déploiement
|
||||||
|
|
||||||
|
## Utilisation en mode CLI (depuis git clone)
|
||||||
|
|
||||||
|
Cloner ce dépôt sur n'importe quelle machine et lancer le script :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Déploiement SSH vers une machine distante
|
||||||
|
git clone https://git.piaf.im/sylvain/agent2_deploy.git
|
||||||
|
cd agent2_deploy
|
||||||
|
python3 deploy.py
|
||||||
|
|
||||||
|
# Installation locale (sur la machine cible elle-même)
|
||||||
|
python3 deploy.py --local agent2_debian13
|
||||||
|
python3 deploy.py --local agent2_ansible
|
||||||
|
```
|
||||||
|
|
||||||
|
## Déploiement de agent2_deploy lui-même
|
||||||
|
|
||||||
|
### Prérequis sur la machine hôte
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt-get install -y python3 python3-venv python3-paramiko git mosquitto
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installation manuelle
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.piaf.im/sylvain/agent2_deploy.git /opt/agent2_deploy
|
||||||
|
cd /opt/agent2_deploy
|
||||||
|
|
||||||
|
# Configurer
|
||||||
|
nano config/config.json
|
||||||
|
|
||||||
|
# Service systemd
|
||||||
|
cat > /etc/systemd/system/agent2_deploy.service << 'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=Agent2 Deploy
|
||||||
|
After=network.target mosquitto.service agent.service
|
||||||
|
Wants=mosquitto.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/opt/agent2_deploy
|
||||||
|
ExecStart=/usr/bin/python3 /opt/agent2_deploy/agent2_deploy.py
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable agent2_deploy
|
||||||
|
systemctl start agent2_deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration (`config/config.json`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"xmpp_jid" : "agent2_deploy@xmpp.ovh",
|
||||||
|
"xmpp_pass" : "<mot_de_passe>",
|
||||||
|
"admin_jid" : "sylvain@xmpp.ovh",
|
||||||
|
"mqtt_host" : "localhost",
|
||||||
|
"mqtt_port" : 1883,
|
||||||
|
"mqtt_client_id": "agent2_deploy",
|
||||||
|
"mqtt_inbox" : "agents/agent2_deploy/inbox",
|
||||||
|
"mqtt_outbox" : "agents/agent1/inbox",
|
||||||
|
"agent1_inbox" : "agents/agent1/inbox"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prérequis sur les machines cibles
|
||||||
|
|
||||||
|
La machine sur laquelle déployer doit être accessible via SSH. Le script installe automatiquement :
|
||||||
|
- `python3`, `python3-pip`, `python3-venv`, `git`
|
||||||
|
- Les dépendances Python de l'agent (via pip dans le venv)
|
||||||
|
|
||||||
|
L'utilisateur SSH doit avoir les droits `sudo` ou être `root` (pour systemd et apt).
|
||||||
|
|
||||||
|
## Ajouter un nouvel agent au catalogue
|
||||||
|
|
||||||
|
Editer `agents_catalog.json` :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"agent2_monagent": {
|
||||||
|
"description" : "Description de l'agent",
|
||||||
|
"repo_url" : "https://git.piaf.im/sylvain/agent2_monagent.git",
|
||||||
|
"install_path" : "/opt/agent2_monagent",
|
||||||
|
"service_name" : "agent2_monagent",
|
||||||
|
"main_script" : "agent2_monagent.py",
|
||||||
|
"mqtt_inbox" : "agents/agent2_monagent/inbox",
|
||||||
|
"mqtt_outbox" : "agents/agent1/inbox",
|
||||||
|
"dependencies" : ["slixmpp", "paho-mqtt", "requests"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl -u agent2_deploy -f
|
||||||
|
```
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Agent2_Deploy — Agent de déploiement interactif via XMPP.
|
||||||
|
|
||||||
|
Interaction guidée (machine à états) déclenchée par "!deploy".
|
||||||
|
L'agent pose les questions nécessaires, déploie via SSH,
|
||||||
|
puis notifie agent1 du nouvel agent via MQTT.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
|
from slixmpp import ClientXMPP
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
|
||||||
|
from deployer import deploy_agent, load_catalog
|
||||||
|
|
||||||
|
# ── CONFIG ────────────────────────────────────────────────────────────────
|
||||||
|
CONFIG_FILE = Path("/opt/agent2_deploy/config/config.json")
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
with open(CONFIG_FILE, encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
cfg = load_config()
|
||||||
|
XMPP_JID = cfg["xmpp_jid"]
|
||||||
|
XMPP_PASS = cfg["xmpp_pass"]
|
||||||
|
ADMIN_JID = cfg["admin_jid"]
|
||||||
|
MQTT_HOST = cfg["mqtt_host"]
|
||||||
|
MQTT_PORT = int(cfg["mqtt_port"])
|
||||||
|
MQTT_CLIENT = cfg["mqtt_client_id"]
|
||||||
|
AGENT1_INBOX = cfg["agent1_inbox"]
|
||||||
|
|
||||||
|
# ── MQTT ──────────────────────────────────────────────────────────────────
|
||||||
|
_mqtt_pub = None
|
||||||
|
|
||||||
|
def mqtt_publish(topic: str, message: str):
|
||||||
|
if _mqtt_pub:
|
||||||
|
_mqtt_pub.publish(topic, message)
|
||||||
|
|
||||||
|
def start_mqtt():
|
||||||
|
global _mqtt_pub
|
||||||
|
_mqtt_pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
|
||||||
|
client_id=MQTT_CLIENT + "_pub")
|
||||||
|
_mqtt_pub.connect(MQTT_HOST, MQTT_PORT)
|
||||||
|
_mqtt_pub.loop_start()
|
||||||
|
|
||||||
|
def notify_agent1(agent_type: str, host: str, xmpp_jid: str, mqtt_inbox: str):
|
||||||
|
"""Informe agent1 du nouveau déploiement."""
|
||||||
|
msg = (
|
||||||
|
"[DEPLOY] Nouvel agent déployé.\n"
|
||||||
|
" Type : {}\n"
|
||||||
|
" Hôte : {}\n"
|
||||||
|
" XMPP JID : {}\n"
|
||||||
|
" MQTT : {}"
|
||||||
|
).format(agent_type, host, xmpp_jid, mqtt_inbox)
|
||||||
|
mqtt_publish(AGENT1_INBOX, msg)
|
||||||
|
|
||||||
|
# ── MACHINE À ÉTATS ───────────────────────────────────────────────────────
|
||||||
|
# sessions[jid] = dict avec toutes les infos collectées + état courant
|
||||||
|
|
||||||
|
STEPS = [
|
||||||
|
"choose_agent",
|
||||||
|
"enter_ip",
|
||||||
|
"enter_user",
|
||||||
|
"enter_auth",
|
||||||
|
"enter_credential",
|
||||||
|
"enter_xmpp_jid",
|
||||||
|
"enter_xmpp_pass",
|
||||||
|
"enter_mqtt_host",
|
||||||
|
"confirm",
|
||||||
|
]
|
||||||
|
|
||||||
|
sessions: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
def list_agents() -> str:
|
||||||
|
catalog = load_catalog()
|
||||||
|
lines = ["Agents disponibles :"]
|
||||||
|
for i, (name, info) in enumerate(catalog.items(), 1):
|
||||||
|
lines.append(" {}. {} — {}".format(i, name, info["description"]))
|
||||||
|
lines.append("\nTapez le numéro ou le nom de l'agent à déployer.")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def start_session(jid: str) -> str:
|
||||||
|
sessions[jid] = {
|
||||||
|
"state" : "choose_agent",
|
||||||
|
"agent_type" : None,
|
||||||
|
"target_ip" : None,
|
||||||
|
"ssh_user" : None,
|
||||||
|
"auth_type" : None, # "password" ou "key"
|
||||||
|
"ssh_password": None,
|
||||||
|
"ssh_key_path": None,
|
||||||
|
"xmpp_jid" : None,
|
||||||
|
"xmpp_pass" : None,
|
||||||
|
"mqtt_host" : None,
|
||||||
|
}
|
||||||
|
return "Déploiement d'agent démarré. Tapez !annuler pour abandonner.\n\n" + list_agents()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_session(jid: str, text: str) -> str | None:
|
||||||
|
"""
|
||||||
|
Gère un message dans le contexte d'une session de déploiement.
|
||||||
|
Retourne la réponse à envoyer, ou None si hors session.
|
||||||
|
"""
|
||||||
|
session = sessions.get(jid)
|
||||||
|
if session is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
text = text.strip()
|
||||||
|
|
||||||
|
if text.lower() in ("!annuler", "annuler", "cancel"):
|
||||||
|
del sessions[jid]
|
||||||
|
return "Déploiement annulé."
|
||||||
|
|
||||||
|
state = session["state"]
|
||||||
|
catalog = load_catalog()
|
||||||
|
|
||||||
|
# ── choose_agent ──────────────────────────────────────────────────────
|
||||||
|
if state == "choose_agent":
|
||||||
|
agents = list(catalog.keys())
|
||||||
|
# Accepte numéro ou nom
|
||||||
|
if text.isdigit():
|
||||||
|
idx = int(text) - 1
|
||||||
|
if 0 <= idx < len(agents):
|
||||||
|
session["agent_type"] = agents[idx]
|
||||||
|
else:
|
||||||
|
return "Numéro invalide. Choisissez entre 1 et {}.".format(len(agents))
|
||||||
|
elif text in catalog:
|
||||||
|
session["agent_type"] = text
|
||||||
|
else:
|
||||||
|
return "Agent inconnu. Tapez le numéro ou le nom exact.\n\n" + list_agents()
|
||||||
|
session["state"] = "enter_ip"
|
||||||
|
return "Agent choisi : {}.\n\nAdresse IP de la machine cible ?".format(session["agent_type"])
|
||||||
|
|
||||||
|
# ── enter_ip ──────────────────────────────────────────────────────────
|
||||||
|
elif state == "enter_ip":
|
||||||
|
session["target_ip"] = text
|
||||||
|
session["state"] = "enter_user"
|
||||||
|
return "Nom d'utilisateur SSH sur {} ?".format(text)
|
||||||
|
|
||||||
|
# ── enter_user ────────────────────────────────────────────────────────
|
||||||
|
elif state == "enter_user":
|
||||||
|
session["ssh_user"] = text
|
||||||
|
session["state"] = "enter_auth"
|
||||||
|
return (
|
||||||
|
"Méthode d'authentification SSH ?\n"
|
||||||
|
" 1. Mot de passe\n"
|
||||||
|
" 2. Clé SSH (chemin local)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── enter_auth ────────────────────────────────────────────────────────
|
||||||
|
elif state == "enter_auth":
|
||||||
|
if text in ("1", "mot de passe", "password"):
|
||||||
|
session["auth_type"] = "password"
|
||||||
|
session["state"] = "enter_credential"
|
||||||
|
return "Mot de passe SSH pour {}@{} :".format(
|
||||||
|
session["ssh_user"], session["target_ip"])
|
||||||
|
elif text in ("2", "clé", "clé ssh", "key"):
|
||||||
|
session["auth_type"] = "key"
|
||||||
|
session["state"] = "enter_credential"
|
||||||
|
return "Chemin local de la clé SSH (ex: /root/.ssh/id_rsa) :"
|
||||||
|
else:
|
||||||
|
return "Répondez 1 (mot de passe) ou 2 (clé SSH)."
|
||||||
|
|
||||||
|
# ── enter_credential ─────────────────────────────────────────────────
|
||||||
|
elif state == "enter_credential":
|
||||||
|
if session["auth_type"] == "password":
|
||||||
|
session["ssh_password"] = text
|
||||||
|
else:
|
||||||
|
session["ssh_key_path"] = text
|
||||||
|
session["state"] = "enter_xmpp_jid"
|
||||||
|
agent_type = session["agent_type"]
|
||||||
|
return (
|
||||||
|
"JID XMPP pour cet agent sur la machine distante ?\n"
|
||||||
|
"(ex: {}@xmpp.ovh)".format(agent_type)
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── enter_xmpp_jid ───────────────────────────────────────────────────
|
||||||
|
elif state == "enter_xmpp_jid":
|
||||||
|
session["xmpp_jid"] = text
|
||||||
|
session["state"] = "enter_xmpp_pass"
|
||||||
|
return "Mot de passe XMPP pour {} :".format(text)
|
||||||
|
|
||||||
|
# ── enter_xmpp_pass ──────────────────────────────────────────────────
|
||||||
|
elif state == "enter_xmpp_pass":
|
||||||
|
session["xmpp_pass"] = text
|
||||||
|
session["state"] = "enter_mqtt_host"
|
||||||
|
return (
|
||||||
|
"Adresse du broker MQTT pour cet agent ?\n"
|
||||||
|
"(laisser vide pour utiliser l'IP de la machine distante : {})".format(
|
||||||
|
session["target_ip"])
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── enter_mqtt_host ──────────────────────────────────────────────────
|
||||||
|
elif state == "enter_mqtt_host":
|
||||||
|
session["mqtt_host"] = text if text else session["target_ip"]
|
||||||
|
session["state"] = "confirm"
|
||||||
|
s = session
|
||||||
|
agent_info = catalog[s["agent_type"]]
|
||||||
|
return (
|
||||||
|
"Récapitulatif du déploiement :\n"
|
||||||
|
" Agent : {agent_type}\n"
|
||||||
|
" Repo : {repo}\n"
|
||||||
|
" Machine : {ssh_user}@{target_ip}\n"
|
||||||
|
" Auth SSH : {auth}\n"
|
||||||
|
" XMPP JID : {xmpp_jid}\n"
|
||||||
|
" MQTT broker: {mqtt_host}\n"
|
||||||
|
" MQTT inbox : {mqtt_inbox}\n\n"
|
||||||
|
"Confirmer le déploiement ? (oui / non)"
|
||||||
|
).format(
|
||||||
|
agent_type=s["agent_type"],
|
||||||
|
repo=agent_info["repo_url"],
|
||||||
|
ssh_user=s["ssh_user"],
|
||||||
|
target_ip=s["target_ip"],
|
||||||
|
auth="clé SSH" if s["auth_type"] == "key" else "mot de passe",
|
||||||
|
xmpp_jid=s["xmpp_jid"],
|
||||||
|
mqtt_host=s["mqtt_host"],
|
||||||
|
mqtt_inbox=agent_info["mqtt_inbox"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── confirm ───────────────────────────────────────────────────────────
|
||||||
|
elif state == "confirm":
|
||||||
|
if text.lower() in ("oui", "o", "yes", "y"):
|
||||||
|
session["state"] = "deploying"
|
||||||
|
return None # Signal pour lancer le déploiement en arrière-plan
|
||||||
|
else:
|
||||||
|
del sessions[jid]
|
||||||
|
return "Déploiement annulé."
|
||||||
|
|
||||||
|
return "État inconnu. Tapez !annuler pour recommencer."
|
||||||
|
|
||||||
|
|
||||||
|
# ── BOT XMPP ─────────────────────────────────────────────────────────────
|
||||||
|
class DeployBot(ClientXMPP):
|
||||||
|
def __init__(self):
|
||||||
|
ClientXMPP.__init__(self, XMPP_JID, XMPP_PASS)
|
||||||
|
self.add_event_handler("session_start", self.session_start)
|
||||||
|
self.add_event_handler("message", self.on_message)
|
||||||
|
self.register_plugin('xep_0030')
|
||||||
|
self.register_plugin('xep_0199')
|
||||||
|
|
||||||
|
def reply(self, jid: str, body: str):
|
||||||
|
self.send_message(mto=jid, mbody=body, mtype='chat')
|
||||||
|
|
||||||
|
async def session_start(self, event):
|
||||||
|
self.send_presence()
|
||||||
|
await self.get_roster()
|
||||||
|
self.reply(ADMIN_JID, (
|
||||||
|
"Agent2_Deploy en ligne !\n"
|
||||||
|
"Tapez !deploy pour déployer un agent via SSH.\n"
|
||||||
|
"Tapez !agents pour voir les agents disponibles."
|
||||||
|
))
|
||||||
|
|
||||||
|
async def on_message(self, msg):
|
||||||
|
if msg['type'] not in ('chat', 'normal'):
|
||||||
|
return
|
||||||
|
jid = str(msg['from']).split('/')[0]
|
||||||
|
if jid != ADMIN_JID:
|
||||||
|
return
|
||||||
|
|
||||||
|
text = msg['body'].strip()
|
||||||
|
|
||||||
|
# Commandes globales
|
||||||
|
if text == "!agents":
|
||||||
|
self.reply(jid, list_agents())
|
||||||
|
return
|
||||||
|
|
||||||
|
if text == "!deploy":
|
||||||
|
self.reply(jid, start_session(jid))
|
||||||
|
return
|
||||||
|
|
||||||
|
if text == "!help":
|
||||||
|
self.reply(jid, (
|
||||||
|
"Commandes disponibles :\n"
|
||||||
|
" !deploy — Démarrer un déploiement guidé\n"
|
||||||
|
" !agents — Lister les agents déployables\n"
|
||||||
|
" !annuler — Annuler le déploiement en cours\n"
|
||||||
|
" !help — Afficher cette aide"
|
||||||
|
))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Session de déploiement en cours ?
|
||||||
|
if jid in sessions:
|
||||||
|
reply = handle_session(jid, text)
|
||||||
|
|
||||||
|
if reply is not None:
|
||||||
|
self.reply(jid, reply)
|
||||||
|
else:
|
||||||
|
# reply=None signifie : lancer le déploiement
|
||||||
|
session = sessions[jid]
|
||||||
|
self.reply(jid, "Déploiement en cours... Cela peut prendre plusieurs minutes.")
|
||||||
|
|
||||||
|
def run_deploy():
|
||||||
|
def progress(msg):
|
||||||
|
self.reply(jid, "[{}]".format(msg))
|
||||||
|
|
||||||
|
catalog = load_catalog()
|
||||||
|
success, result = deploy_agent(
|
||||||
|
host = session["target_ip"],
|
||||||
|
ssh_user = session["ssh_user"],
|
||||||
|
agent_type = session["agent_type"],
|
||||||
|
xmpp_jid = session["xmpp_jid"],
|
||||||
|
xmpp_pass = session["xmpp_pass"],
|
||||||
|
mqtt_host = session["mqtt_host"],
|
||||||
|
progress_cb = progress,
|
||||||
|
ssh_password= session.get("ssh_password"),
|
||||||
|
ssh_key_path= session.get("ssh_key_path"),
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
mqtt_inbox = catalog[session["agent_type"]]["mqtt_inbox"]
|
||||||
|
notify_agent1(
|
||||||
|
agent_type = session["agent_type"],
|
||||||
|
host = session["target_ip"],
|
||||||
|
xmpp_jid = session["xmpp_jid"],
|
||||||
|
mqtt_inbox = mqtt_inbox,
|
||||||
|
)
|
||||||
|
self.reply(jid, result)
|
||||||
|
else:
|
||||||
|
self.reply(jid, "Déploiement échoué : " + result)
|
||||||
|
|
||||||
|
if jid in sessions:
|
||||||
|
del sessions[jid]
|
||||||
|
|
||||||
|
threading.Thread(target=run_deploy, daemon=True).start()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Hors session → aide basique
|
||||||
|
self.reply(jid, (
|
||||||
|
"Je suis agent2_deploy, spécialisé dans le déploiement d'agents.\n"
|
||||||
|
"Tapez !deploy pour déployer un agent, ou !help pour l'aide."
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
# ── MAIN ─────────────────────────────────────────────────────────────────
|
||||||
|
if __name__ == "__main__":
|
||||||
|
mqtt_thread = threading.Thread(target=start_mqtt, daemon=True)
|
||||||
|
mqtt_thread.start()
|
||||||
|
|
||||||
|
bot = DeployBot()
|
||||||
|
bot.connect()
|
||||||
|
bot.loop.run_forever()
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"agent2_debian13": {
|
||||||
|
"description" : "Spécialiste Debian : apt, systemd, conteneurs LXC/Docker, KVM, réseau, sécurité",
|
||||||
|
"repo_url" : "https://git.piaf.im/sylvain/agent2_debian13.git",
|
||||||
|
"install_path" : "/opt/agent2_debian13",
|
||||||
|
"service_name" : "agent2_debian13",
|
||||||
|
"main_script" : "agent2_debian13.py",
|
||||||
|
"mqtt_inbox" : "agents/agent2_debian13/inbox",
|
||||||
|
"mqtt_outbox" : "agents/agent1/inbox",
|
||||||
|
"dependencies" : ["slixmpp", "paho-mqtt", "requests", "ddgs", "beautifulsoup4", "chromadb"]
|
||||||
|
},
|
||||||
|
"agent2_ansible": {
|
||||||
|
"description" : "Automatisation Ansible : playbooks, commandes ad-hoc, déploiement multi-hôtes",
|
||||||
|
"repo_url" : "https://git.piaf.im/sylvain/agent2_ansible.git",
|
||||||
|
"install_path" : "/opt/agent2_ansible",
|
||||||
|
"service_name" : "agent2_ansible",
|
||||||
|
"main_script" : "agent2_ansible.py",
|
||||||
|
"mqtt_inbox" : "agents/agent2_ansible/inbox",
|
||||||
|
"mqtt_outbox" : "agents/agent1/inbox",
|
||||||
|
"dependencies" : ["slixmpp", "paho-mqtt", "requests", "ddgs", "beautifulsoup4", "chromadb"]
|
||||||
|
},
|
||||||
|
"agent2_deploy": {
|
||||||
|
"description" : "Agent de déploiement : installe et configure d'autres agents via SSH",
|
||||||
|
"repo_url" : "https://git.piaf.im/sylvain/agent2_deploy.git",
|
||||||
|
"install_path" : "/opt/agent2_deploy",
|
||||||
|
"service_name" : "agent2_deploy",
|
||||||
|
"main_script" : "agent2_deploy.py",
|
||||||
|
"mqtt_inbox" : "agents/agent2_deploy/inbox",
|
||||||
|
"mqtt_outbox" : "agents/agent1/inbox",
|
||||||
|
"dependencies" : ["slixmpp", "paho-mqtt", "requests"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"xmpp_jid" : "agent2_deploy@xmpp.ovh",
|
||||||
|
"xmpp_pass" : "Matador3721",
|
||||||
|
"admin_jid" : "sylvain@xmpp.ovh",
|
||||||
|
"mqtt_host" : "localhost",
|
||||||
|
"mqtt_port" : 1883,
|
||||||
|
"mqtt_client_id": "agent2_deploy",
|
||||||
|
"mqtt_inbox" : "agents/agent2_deploy/inbox",
|
||||||
|
"mqtt_outbox" : "agents/agent1/inbox",
|
||||||
|
"agent1_inbox" : "agents/agent1/inbox",
|
||||||
|
"ollama_url" : "http://192.168.7.119:11434/api/chat",
|
||||||
|
"model" : "qwen3:8b"
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Script de déploiement CLI standalone.
|
||||||
|
Utilisable après un git clone, sans avoir besoin d'un agent XMPP actif.
|
||||||
|
|
||||||
|
Usage :
|
||||||
|
python3 deploy.py # déploiement interactif via SSH
|
||||||
|
python3 deploy.py --local <agent> # installe l'agent sur la machine locale
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import getpass
|
||||||
|
import json
|
||||||
|
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
|
||||||
|
|
||||||
|
COLORS = {
|
||||||
|
"reset" : "\033[0m",
|
||||||
|
"bold" : "\033[1m",
|
||||||
|
"green" : "\033[32m",
|
||||||
|
"yellow": "\033[33m",
|
||||||
|
"red" : "\033[31m",
|
||||||
|
"cyan" : "\033[36m",
|
||||||
|
}
|
||||||
|
|
||||||
|
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"))
|
||||||
|
|
||||||
|
|
||||||
|
def choose_agent(catalog: dict) -> str:
|
||||||
|
print(c("cyan", "Agents 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():
|
||||||
|
idx = int(choice) - 1
|
||||||
|
if 0 <= idx < len(agents):
|
||||||
|
return agents[idx][0]
|
||||||
|
elif choice in catalog:
|
||||||
|
return choice
|
||||||
|
print(c("red", "Choix invalide, réessayez."))
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
print(" Authentification :")
|
||||||
|
print(" 1. Mot de passe")
|
||||||
|
print(" 2. Clé SSH")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
auth = input(" Choix (1/2) : ").strip()
|
||||||
|
if auth == "1":
|
||||||
|
password = getpass.getpass(" Mot de passe SSH : ")
|
||||||
|
return {"host": host, "user": user, "auth": "password",
|
||||||
|
"password": password, "key_path": None}
|
||||||
|
elif auth == "2":
|
||||||
|
key_path = input(" Chemin de la clé SSH [~/.ssh/id_rsa] : ").strip()
|
||||||
|
if not key_path:
|
||||||
|
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."))
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
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"]
|
||||||
|
|
||||||
|
print(c("cyan", "\nDéploiement local de {} dans {}...".format(agent_type, install_path)))
|
||||||
|
|
||||||
|
def run(cmd, **kwargs):
|
||||||
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, **kwargs)
|
||||||
|
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...")
|
||||||
|
run("apt-get install -y -qq python3 python3-pip python3-venv git 2>&1")
|
||||||
|
|
||||||
|
# Clone
|
||||||
|
print(" 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...")
|
||||||
|
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"
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"ollama_url" : "http://192.168.7.119:11434/api/chat",
|
||||||
|
"model" : "qwen3:8b",
|
||||||
|
"xmpp_jid" : xmpp_jid,
|
||||||
|
"xmpp_pass" : xmpp_pass,
|
||||||
|
"admin_jid" : "sylvain@xmpp.ovh",
|
||||||
|
"db_path" : "{}/memory.db".format(install_path),
|
||||||
|
"mqtt_host" : mqtt_host,
|
||||||
|
"mqtt_port" : 1883,
|
||||||
|
"mqtt_client_id": agent_type,
|
||||||
|
"mqtt_inbox" : info["mqtt_inbox"],
|
||||||
|
"mqtt_outbox" : info["mqtt_outbox"],
|
||||||
|
}
|
||||||
|
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
|
||||||
|
service = """[Unit]
|
||||||
|
Description=Agent {name}
|
||||||
|
After=network.target mosquitto.service
|
||||||
|
Wants=mosquitto.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory={path}
|
||||||
|
ExecStart=/usr/bin/python3 {path}/{script}
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
""".format(name=agent_type, 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))
|
||||||
|
|
||||||
|
print(c("green", "\nDéploiement local terminé !"))
|
||||||
|
print(" Service : systemctl status {}".format(service_name))
|
||||||
|
print(" Logs : journalctl -u {} -f".format(service_name))
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Mode SSH interactif
|
||||||
|
agent_type = choose_agent(catalog)
|
||||||
|
ssh = collect_ssh_info()
|
||||||
|
agent_cfg = collect_agent_config(agent_type, catalog, ssh["host"])
|
||||||
|
|
||||||
|
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é."))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
print(c("cyan", "\nDéploiement en cours..."))
|
||||||
|
|
||||||
|
def progress(msg):
|
||||||
|
print(c("yellow", " [{}]".format(msg)))
|
||||||
|
|
||||||
|
success, result = deploy_agent(
|
||||||
|
host = ssh["host"],
|
||||||
|
ssh_user = ssh["user"],
|
||||||
|
agent_type = agent_type,
|
||||||
|
xmpp_jid = agent_cfg["xmpp_jid"],
|
||||||
|
xmpp_pass = agent_cfg["xmpp_pass"],
|
||||||
|
mqtt_host = agent_cfg["mqtt_host"],
|
||||||
|
progress_cb = progress,
|
||||||
|
ssh_password= ssh["password"],
|
||||||
|
ssh_key_path= ssh["key_path"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print(c("green", "\n" + result))
|
||||||
|
else:
|
||||||
|
print(c("red", "\nÉchec : " + result))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Logique de déploiement SSH partagée.
|
||||||
|
Utilisée par agent2_deploy.py (XMPP) et deploy.py (CLI).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import io
|
||||||
|
import paramiko
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
CATALOG_FILE = Path(__file__).parent / "agents_catalog.json"
|
||||||
|
|
||||||
|
|
||||||
|
def load_catalog() -> dict:
|
||||||
|
with open(CATALOG_FILE, encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_ssh(client: paramiko.SSHClient, cmd: str, timeout: int = 60) -> tuple[int, str, str]:
|
||||||
|
"""Exécute une commande SSH et retourne (returncode, stdout, stderr)."""
|
||||||
|
_, stdout, stderr = client.exec_command(cmd, timeout=timeout)
|
||||||
|
rc = stdout.channel.recv_exit_status()
|
||||||
|
return rc, stdout.read().decode(errors="replace").strip(), stderr.read().decode(errors="replace").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def connect_ssh(host: str, user: str, password: str = None, key_path: str = None) -> paramiko.SSHClient:
|
||||||
|
"""Crée et retourne une connexion SSH authentifiée."""
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
if key_path:
|
||||||
|
client.connect(host, username=user, key_filename=key_path, timeout=15)
|
||||||
|
else:
|
||||||
|
client.connect(host, username=user, password=password, timeout=15)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def write_remote_file(client: paramiko.SSHClient, remote_path: str, content: str):
|
||||||
|
"""Écrit un fichier sur la machine distante via SFTP."""
|
||||||
|
sftp = client.open_sftp()
|
||||||
|
with sftp.open(remote_path, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
sftp.close()
|
||||||
|
|
||||||
|
|
||||||
|
def deploy_agent(
|
||||||
|
host: str,
|
||||||
|
ssh_user: str,
|
||||||
|
agent_type: str,
|
||||||
|
xmpp_jid: str,
|
||||||
|
xmpp_pass: str,
|
||||||
|
mqtt_host: str,
|
||||||
|
progress_cb,
|
||||||
|
ssh_password: str = None,
|
||||||
|
ssh_key_path: str = None,
|
||||||
|
) -> tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
Déploie un agent sur la machine distante.
|
||||||
|
progress_cb(msg) : callback appelé à chaque étape pour informer l'utilisateur.
|
||||||
|
Retourne (succès, message_final).
|
||||||
|
"""
|
||||||
|
catalog = load_catalog()
|
||||||
|
if agent_type not in catalog:
|
||||||
|
return False, "Agent inconnu : {}".format(agent_type)
|
||||||
|
|
||||||
|
agent = catalog[agent_type]
|
||||||
|
install_path = agent["install_path"]
|
||||||
|
repo_url = agent["repo_url"]
|
||||||
|
service_name = agent["service_name"]
|
||||||
|
main_script = agent["main_script"]
|
||||||
|
deps = " ".join(agent["dependencies"])
|
||||||
|
mqtt_inbox = agent["mqtt_inbox"]
|
||||||
|
mqtt_outbox = agent["mqtt_outbox"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# ── Connexion SSH ──────────────────────────────────────────────────
|
||||||
|
progress_cb("Connexion SSH à {}@{}...".format(ssh_user, host))
|
||||||
|
client = connect_ssh(host, ssh_user, password=ssh_password, key_path=ssh_key_path)
|
||||||
|
progress_cb("Connexion SSH établie.")
|
||||||
|
|
||||||
|
# ── Prérequis système ──────────────────────────────────────────────
|
||||||
|
progress_cb("Installation des prérequis système...")
|
||||||
|
rc, out, err = _run_ssh(client,
|
||||||
|
"apt-get update -qq && apt-get install -y -qq python3 python3-pip python3-venv git python3-paramiko 2>&1",
|
||||||
|
timeout=180)
|
||||||
|
if rc != 0:
|
||||||
|
return False, "Erreur prérequis : {}".format(err or out)
|
||||||
|
progress_cb("Prérequis installés.")
|
||||||
|
|
||||||
|
# ── Clone ou mise à jour du dépôt ──────────────────────────────────
|
||||||
|
progress_cb("Clonage du dépôt {}...".format(repo_url))
|
||||||
|
rc, out, err = _run_ssh(client,
|
||||||
|
"if [ -d {path}/.git ]; then git -C {path} pull; else git clone {url} {path}; fi".format(
|
||||||
|
path=install_path, url=repo_url),
|
||||||
|
timeout=120)
|
||||||
|
if rc != 0:
|
||||||
|
return False, "Erreur git : {}".format(err or out)
|
||||||
|
progress_cb("Dépôt cloné dans {}.".format(install_path))
|
||||||
|
|
||||||
|
# ── Venv et dépendances Python ─────────────────────────────────────
|
||||||
|
progress_cb("Création du venv et installation des dépendances Python...")
|
||||||
|
rc, out, err = _run_ssh(client,
|
||||||
|
"python3 -m venv {path}/venv && {path}/venv/bin/pip install -q {deps}".format(
|
||||||
|
path=install_path, deps=deps),
|
||||||
|
timeout=300)
|
||||||
|
if rc != 0:
|
||||||
|
return False, "Erreur pip : {}".format(err or out)
|
||||||
|
progress_cb("Dépendances Python installées.")
|
||||||
|
|
||||||
|
# ── Configuration ──────────────────────────────────────────────────
|
||||||
|
progress_cb("Écriture de config.json...")
|
||||||
|
config = {
|
||||||
|
"ollama_url" : "http://192.168.7.119:11434/api/chat",
|
||||||
|
"model" : "qwen3:8b",
|
||||||
|
"xmpp_jid" : xmpp_jid,
|
||||||
|
"xmpp_pass" : xmpp_pass,
|
||||||
|
"admin_jid" : "sylvain@xmpp.ovh",
|
||||||
|
"db_path" : "{}/memory.db".format(install_path),
|
||||||
|
"mqtt_host" : mqtt_host,
|
||||||
|
"mqtt_port" : 1883,
|
||||||
|
"mqtt_client_id": agent_type,
|
||||||
|
"mqtt_inbox" : mqtt_inbox,
|
||||||
|
"mqtt_outbox" : mqtt_outbox,
|
||||||
|
}
|
||||||
|
write_remote_file(client,
|
||||||
|
"{}/config/config.json".format(install_path),
|
||||||
|
json.dumps(config, indent=2, ensure_ascii=False))
|
||||||
|
progress_cb("Configuration écrite.")
|
||||||
|
|
||||||
|
# ── Service systemd ────────────────────────────────────────────────
|
||||||
|
progress_cb("Création du service systemd...")
|
||||||
|
service_content = """[Unit]
|
||||||
|
Description=Agent {name}
|
||||||
|
After=network.target mosquitto.service
|
||||||
|
Wants=mosquitto.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory={path}
|
||||||
|
ExecStart=/usr/bin/python3 {path}/{script}
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
""".format(name=agent_type, path=install_path, script=main_script)
|
||||||
|
|
||||||
|
write_remote_file(client,
|
||||||
|
"/etc/systemd/system/{}.service".format(service_name),
|
||||||
|
service_content)
|
||||||
|
|
||||||
|
rc, out, err = _run_ssh(client,
|
||||||
|
"systemctl daemon-reload && systemctl enable {s} && systemctl restart {s}".format(
|
||||||
|
s=service_name),
|
||||||
|
timeout=30)
|
||||||
|
if rc != 0:
|
||||||
|
return False, "Erreur systemd : {}".format(err or out)
|
||||||
|
progress_cb("Service {} activé et démarré.".format(service_name))
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
summary = (
|
||||||
|
"Déploiement de {} terminé sur {} !\n"
|
||||||
|
" JID XMPP : {}\n"
|
||||||
|
" MQTT inbox : {}\n"
|
||||||
|
" Service : systemctl status {}"
|
||||||
|
).format(agent_type, host, xmpp_jid, mqtt_inbox, service_name)
|
||||||
|
|
||||||
|
return True, summary
|
||||||
|
|
||||||
|
except paramiko.AuthenticationException:
|
||||||
|
return False, "Erreur : authentification SSH refusée."
|
||||||
|
except paramiko.NoValidConnectionsError:
|
||||||
|
return False, "Erreur : impossible de se connecter à {}.".format(host)
|
||||||
|
except Exception as e:
|
||||||
|
return False, "Erreur inattendue : {}".format(e)
|
||||||
Reference in New Issue
Block a user