Initial commit — agent_deploy v2.0
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Skill CATALOG — gérer le catalogue des agents déployables.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:catalog ARGS:list
|
||||
SKILL:catalog ARGS:show <type>
|
||||
SKILL:catalog ARGS:add <type> | <json_config>
|
||||
SKILL:catalog ARGS:remove <type>
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "/opt/agent_deploy")
|
||||
|
||||
from deployer import AgentCatalog, CATALOG_PATH
|
||||
|
||||
DESCRIPTION = "Gérer le catalogue des types d'agents déployables"
|
||||
USAGE = "SKILL:catalog ARGS:list | show <type> | add <type>|<json> | remove <type>"
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
parts = args.strip().split(None, 1)
|
||||
action = parts[0].lower() if parts else "list"
|
||||
rest = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
catalog = AgentCatalog()
|
||||
|
||||
if action == "list":
|
||||
return catalog.summary()
|
||||
|
||||
if action == "show":
|
||||
agent_type = rest.strip()
|
||||
info = catalog.get(agent_type)
|
||||
if not info:
|
||||
return f"Type '{agent_type}' inconnu."
|
||||
return json.dumps(info, indent=2, ensure_ascii=False)
|
||||
|
||||
if action == "add":
|
||||
if "|" not in rest:
|
||||
return "Format : add <type> | <json_config>"
|
||||
agent_type, config_json = rest.split("|", 1)
|
||||
agent_type = agent_type.strip()
|
||||
try:
|
||||
config = json.loads(config_json.strip())
|
||||
# Charge le fichier existant et ajoute
|
||||
if os.path.exists(CATALOG_PATH):
|
||||
with open(CATALOG_PATH) as f:
|
||||
data = json.load(f)
|
||||
else:
|
||||
data = catalog._default_catalog()
|
||||
data[agent_type] = config
|
||||
with open(CATALOG_PATH, "w") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
return f"Type '{agent_type}' ajouté au catalogue."
|
||||
except json.JSONDecodeError as e:
|
||||
return f"JSON invalide : {e}"
|
||||
|
||||
if action == "remove":
|
||||
agent_type = rest.strip()
|
||||
if os.path.exists(CATALOG_PATH):
|
||||
with open(CATALOG_PATH) as f:
|
||||
data = json.load(f)
|
||||
if agent_type in data:
|
||||
del data[agent_type]
|
||||
with open(CATALOG_PATH, "w") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
return f"Type '{agent_type}' supprimé du catalogue."
|
||||
return f"Type '{agent_type}' introuvable."
|
||||
|
||||
return "Action inconnue. Disponible : list, show, add, remove"
|
||||
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
Skill DEPLOY — déployer un agent sur une machine distante ou locale.
|
||||
|
||||
Le LLM collecte les paramètres via une conversation guidée, puis lance le déploiement.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:deploy ARGS:start <type> <nom> <host> <user> <auth:password|key> <credential> <xmpp_jid> <xmpp_pass> <mqtt_host>
|
||||
SKILL:deploy ARGS:local <type> <nom> <xmpp_jid> <xmpp_pass> <mqtt_host>
|
||||
SKILL:deploy ARGS:catalog
|
||||
SKILL:deploy ARGS:status <host>
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "/opt/agent_deploy")
|
||||
|
||||
from deployer import Deployer, DeployConfig, AgentCatalog
|
||||
|
||||
DESCRIPTION = "Déployer un agent sur une machine distante (SSH) ou locale"
|
||||
USAGE = (
|
||||
"SKILL:deploy ARGS:catalog — liste les agents déployables\n"
|
||||
"SKILL:deploy ARGS:start <type> <nom> <host> <user> password <mdp> <xmpp_jid> <xmpp_pass> <mqtt_host>\n"
|
||||
"SKILL:deploy ARGS:local <type> <nom> <xmpp_jid> <xmpp_pass> <mqtt_host>"
|
||||
)
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
parts = args.strip().split(None, 1)
|
||||
action = parts[0].lower() if parts else "catalog"
|
||||
rest = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
if action == "catalog":
|
||||
catalog = AgentCatalog()
|
||||
return catalog.summary()
|
||||
|
||||
if action == "local":
|
||||
# ARGS: local <type> <nom> <xmpp_jid> <xmpp_pass> <mqtt_host>
|
||||
p = rest.split()
|
||||
if len(p) < 5:
|
||||
return "Format : local <type> <nom> <xmpp_jid> <xmpp_pass> <mqtt_host>"
|
||||
agent_type, agent_name, xmpp_jid, xmpp_pass, mqtt_host = p[0], p[1], p[2], p[3], p[4]
|
||||
|
||||
cfg = DeployConfig(
|
||||
agent_type=agent_type,
|
||||
agent_name=agent_name,
|
||||
host="localhost",
|
||||
ssh_user="root",
|
||||
ssh_auth="",
|
||||
ssh_credential="",
|
||||
xmpp_jid=xmpp_jid,
|
||||
xmpp_password=xmpp_pass,
|
||||
mqtt_host=mqtt_host,
|
||||
local=True,
|
||||
)
|
||||
return _do_deploy(cfg, context)
|
||||
|
||||
if action == "start":
|
||||
# ARGS: start <type> <nom> <host> <user> <auth> <credential> <xmpp_jid> <xmpp_pass> <mqtt_host>
|
||||
p = rest.split()
|
||||
if len(p) < 9:
|
||||
return (
|
||||
"Format : start <type> <nom> <host> <user> password|key <credential> "
|
||||
"<xmpp_jid> <xmpp_pass> <mqtt_host>"
|
||||
)
|
||||
cfg = DeployConfig(
|
||||
agent_type=p[0],
|
||||
agent_name=p[1],
|
||||
host=p[2],
|
||||
ssh_user=p[3],
|
||||
ssh_auth=p[4],
|
||||
ssh_credential=p[5],
|
||||
xmpp_jid=p[6],
|
||||
xmpp_password=p[7],
|
||||
mqtt_host=p[8],
|
||||
mqtt_port=int(p[9]) if len(p) > 9 else 1883,
|
||||
)
|
||||
return _do_deploy(cfg, context)
|
||||
|
||||
if action == "status":
|
||||
host = rest.strip()
|
||||
if not host:
|
||||
return "Précise l'hôte."
|
||||
import subprocess
|
||||
try:
|
||||
result = subprocess.run(
|
||||
f"ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no {host} 'systemctl list-units --type=service --state=active | grep agent'",
|
||||
shell=True, text=True, capture_output=True, timeout=15
|
||||
)
|
||||
return result.stdout.strip() or "Aucun agent actif trouvé sur cet hôte."
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
return "Action inconnue. Disponible : catalog, start, local, status"
|
||||
|
||||
|
||||
def _do_deploy(cfg: DeployConfig, context) -> str:
|
||||
"""Lance le déploiement et notifie via MQTT."""
|
||||
messages = []
|
||||
|
||||
def progress(msg: str):
|
||||
messages.append(msg)
|
||||
# Notifie en temps réel via MQTT
|
||||
context.mqtt.send_to(
|
||||
"nexus",
|
||||
f"[Deploy {cfg.agent_name}] {msg}",
|
||||
msg_type="result",
|
||||
)
|
||||
|
||||
deployer = Deployer(cfg, progress_cb=progress)
|
||||
success, result = deployer.deploy()
|
||||
|
||||
# Notification finale
|
||||
if success:
|
||||
# Notifie Nexus de l'enregistrement du nouvel agent
|
||||
registration = json.dumps({
|
||||
"agent_id": cfg.agent_name,
|
||||
"agent_type": cfg.agent_type,
|
||||
"host": cfg.host,
|
||||
"xmpp_jid": cfg.xmpp_jid,
|
||||
"mqtt_inbox": f"agents/{cfg.agent_name}/inbox",
|
||||
})
|
||||
context.mqtt.publish_raw("agents/nexus/inbox", registration)
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Skill MQTT_SEND — publier sur un topic MQTT.
|
||||
"""
|
||||
DESCRIPTION = "Publier un message sur un topic MQTT (communication inter-agents)"
|
||||
USAGE = "SKILL:mqtt_send ARGS:<topic> | <message>"
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
if "|" not in args:
|
||||
return "Format : SKILL:mqtt_send ARGS:<topic> | <message>"
|
||||
topic, message = args.split("|", 1)
|
||||
context.mqtt.publish_raw(topic.strip(), message.strip())
|
||||
return f"Message publié sur '{topic.strip()}'."
|
||||
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
Skill SSH — exécuter des commandes sur une machine distante via SSH.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:ssh ARGS:<host> <user> password <mdp> | <commande>
|
||||
SKILL:ssh ARGS:<host> <user> key <chemin_cle> | <commande>
|
||||
SKILL:ssh ARGS:copy <host> <user> password <mdp> | <fichier_local> <chemin_distant>
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
DESCRIPTION = "Exécuter des commandes SSH sur une machine distante"
|
||||
USAGE = "SKILL:ssh ARGS:<host> <user> password <mdp> | <commande> ou key <chemin_cle> | <commande>"
|
||||
|
||||
|
||||
def _ssh_cmd(host: str, user: str, auth: str, credential: str, cmd: str, timeout: int = 30) -> str:
|
||||
if auth == "password":
|
||||
full_cmd = (
|
||||
f"sshpass -p '{credential}' ssh "
|
||||
f"-o StrictHostKeyChecking=no "
|
||||
f"-o ConnectTimeout=10 "
|
||||
f"{user}@{host} '{cmd}'"
|
||||
)
|
||||
else:
|
||||
full_cmd = (
|
||||
f"ssh -i {credential} "
|
||||
f"-o StrictHostKeyChecking=no "
|
||||
f"-o ConnectTimeout=10 "
|
||||
f"{user}@{host} '{cmd}'"
|
||||
)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
full_cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=timeout
|
||||
)
|
||||
out = (result.stdout + result.stderr).strip()
|
||||
return out[:4000] if out else f"(code retour : {result.returncode})"
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"Timeout ({timeout}s)"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
if "|" not in args:
|
||||
return "Format : <host> <user> password|key <credential> | <commande>"
|
||||
|
||||
left, command = args.split("|", 1)
|
||||
command = command.strip()
|
||||
parts = left.strip().split()
|
||||
|
||||
if len(parts) < 4:
|
||||
return "Format : <host> <user> password|key <credential> | <commande>"
|
||||
|
||||
host, user, auth, credential = parts[0], parts[1], parts[2], parts[3]
|
||||
|
||||
if auth not in ("password", "key"):
|
||||
return "Auth doit être 'password' ou 'key'"
|
||||
|
||||
# Sous-commande copy
|
||||
if command.startswith("COPY "):
|
||||
file_parts = command[5:].split()
|
||||
if len(file_parts) < 2:
|
||||
return "Format copy : COPY <fichier_local> <chemin_distant>"
|
||||
local_file, remote_path = file_parts[0], file_parts[1]
|
||||
if auth == "password":
|
||||
scp_cmd = (
|
||||
f"sshpass -p '{credential}' scp "
|
||||
f"-o StrictHostKeyChecking=no "
|
||||
f"{local_file} {user}@{host}:{remote_path}"
|
||||
)
|
||||
else:
|
||||
scp_cmd = (
|
||||
f"scp -i {credential} "
|
||||
f"-o StrictHostKeyChecking=no "
|
||||
f"{local_file} {user}@{host}:{remote_path}"
|
||||
)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
scp_cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=60
|
||||
)
|
||||
return (result.stdout + result.stderr).strip() or f"Fichier copié vers {host}:{remote_path}"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
return _ssh_cmd(host, user, auth, credential, command)
|
||||
Reference in New Issue
Block a user