Initial commit — agent_debian v2.0
This commit is contained in:
+107
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
Skill APT — gestion des paquets Debian.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:apt ARGS:update
|
||||
SKILL:apt ARGS:upgrade
|
||||
SKILL:apt ARGS:install <paquet1> [paquet2...]
|
||||
SKILL:apt ARGS:remove <paquet>
|
||||
SKILL:apt ARGS:purge <paquet>
|
||||
SKILL:apt ARGS:search <terme>
|
||||
SKILL:apt ARGS:show <paquet>
|
||||
SKILL:apt ARGS:list-installed [filtre]
|
||||
SKILL:apt ARGS:list-upgradable
|
||||
SKILL:apt ARGS:autoremove
|
||||
SKILL:apt ARGS:check-updates
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
DESCRIPTION = "Gestion des paquets Debian via apt/dpkg"
|
||||
USAGE = "SKILL:apt ARGS:install <paquet> | remove <paquet> | update | upgrade | search <terme> | show <paquet> | list-installed | list-upgradable | autoremove"
|
||||
|
||||
ENV = {"DEBIAN_FRONTEND": "noninteractive", "PATH": "/usr/bin:/bin:/usr/sbin:/sbin"}
|
||||
|
||||
|
||||
def _run(cmd: str, timeout: int = 120) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=timeout, env=ENV
|
||||
)
|
||||
out = (result.stdout + result.stderr).strip()
|
||||
return out[:4000] if out else "(aucune sortie)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"Timeout ({timeout}s) — commande trop longue."
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
parts = args.strip().split(None, 1)
|
||||
action = parts[0].lower() if parts else ""
|
||||
extra = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
if action == "update":
|
||||
return _run("apt-get update -q")
|
||||
|
||||
if action == "upgrade":
|
||||
return _run("apt-get upgrade -y -q", timeout=300)
|
||||
|
||||
if action == "dist-upgrade":
|
||||
return _run("apt-get dist-upgrade -y -q", timeout=300)
|
||||
|
||||
if action == "install":
|
||||
if not extra:
|
||||
return "Précise le(s) paquet(s) à installer."
|
||||
return _run(f"apt-get install -y -q {extra}", timeout=180)
|
||||
|
||||
if action == "remove":
|
||||
if not extra:
|
||||
return "Précise le paquet à supprimer."
|
||||
return _run(f"apt-get remove -y {extra}")
|
||||
|
||||
if action == "purge":
|
||||
if not extra:
|
||||
return "Précise le paquet à purger."
|
||||
return _run(f"apt-get purge -y {extra}")
|
||||
|
||||
if action == "autoremove":
|
||||
return _run("apt-get autoremove -y")
|
||||
|
||||
if action == "search":
|
||||
if not extra:
|
||||
return "Précise le terme de recherche."
|
||||
return _run(f"apt-cache search {extra} | head -30")
|
||||
|
||||
if action == "show":
|
||||
if not extra:
|
||||
return "Précise le paquet."
|
||||
return _run(f"apt-cache show {extra}")
|
||||
|
||||
if action in ("list-installed", "list"):
|
||||
cmd = f"dpkg -l | grep '^ii' | awk '{{print $2\" \"$3}}'"
|
||||
if extra:
|
||||
cmd += f" | grep {extra}"
|
||||
return _run(cmd)
|
||||
|
||||
if action == "list-upgradable":
|
||||
return _run("apt list --upgradable 2>/dev/null")
|
||||
|
||||
if action == "check-updates":
|
||||
out = _run("apt-get -s upgrade 2>/dev/null | grep '^[0-9]'")
|
||||
return out or "Système à jour."
|
||||
|
||||
if action == "hold":
|
||||
if not extra:
|
||||
return "Précise le paquet."
|
||||
return _run(f"apt-mark hold {extra}")
|
||||
|
||||
if action == "unhold":
|
||||
if not extra:
|
||||
return "Précise le paquet."
|
||||
return _run(f"apt-mark unhold {extra}")
|
||||
|
||||
return (
|
||||
"Action inconnue. Disponible : update, upgrade, install, remove, purge, "
|
||||
"search, show, list-installed, list-upgradable, autoremove, check-updates, hold, unhold"
|
||||
)
|
||||
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
Skill CONTAINER — gestion des conteneurs Docker et LXC/LXD.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:container ARGS:docker ps [all]
|
||||
SKILL:container ARGS:docker start <nom>
|
||||
SKILL:container ARGS:docker stop <nom>
|
||||
SKILL:container ARGS:docker restart <nom>
|
||||
SKILL:container ARGS:docker logs <nom> [N]
|
||||
SKILL:container ARGS:docker stats
|
||||
SKILL:container ARGS:docker images
|
||||
SKILL:container ARGS:docker pull <image>
|
||||
SKILL:container ARGS:docker rm <nom>
|
||||
SKILL:container ARGS:docker rmi <image>
|
||||
SKILL:container ARGS:docker exec <nom> <commande>
|
||||
SKILL:container ARGS:docker inspect <nom>
|
||||
SKILL:container ARGS:lxc list
|
||||
SKILL:container ARGS:lxc start <nom>
|
||||
SKILL:container ARGS:lxc stop <nom>
|
||||
SKILL:container ARGS:lxc exec <nom> <commande>
|
||||
SKILL:container ARGS:lxc info <nom>
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
DESCRIPTION = "Gestion conteneurs Docker et LXC/LXD : start, stop, logs, exec, stats, images"
|
||||
USAGE = "SKILL:container ARGS:docker ps|start|stop|restart|logs|exec|stats|images|pull | lxc list|start|stop|exec|info"
|
||||
|
||||
|
||||
def _run(cmd: str, timeout: int = 30) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=timeout
|
||||
)
|
||||
out = (result.stdout + result.stderr).strip()
|
||||
return out[:4000] if out else "(aucune sortie)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"Timeout ({timeout}s)"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
def _docker(action: str, args: list) -> str:
|
||||
a = args[0] if args else ""
|
||||
|
||||
if action == "ps":
|
||||
flag = "-a" if (a == "all" or a == "-a") else ""
|
||||
return _run(f"docker ps {flag} --format 'table {{{{.Names}}}}\\t{{{{.Image}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}'")
|
||||
|
||||
if action == "start":
|
||||
return _run(f"docker start {a}") if a else "Précise le nom du conteneur."
|
||||
|
||||
if action == "stop":
|
||||
return _run(f"docker stop {a}") if a else "Précise le nom du conteneur."
|
||||
|
||||
if action == "restart":
|
||||
return _run(f"docker restart {a}") if a else "Précise le nom du conteneur."
|
||||
|
||||
if action == "logs":
|
||||
if not a:
|
||||
return "Précise le nom du conteneur."
|
||||
n = args[1] if len(args) > 1 else "50"
|
||||
return _run(f"docker logs --tail {n} {a}")
|
||||
|
||||
if action == "stats":
|
||||
return _run("docker stats --no-stream --format 'table {{{{.Name}}}}\\t{{{{.CPUPerc}}}}\\t{{{{.MemUsage}}}}'")
|
||||
|
||||
if action == "images":
|
||||
return _run("docker images --format 'table {{{{.Repository}}}}\\t{{{{.Tag}}}}\\t{{{{.Size}}}}\\t{{{{.CreatedSince}}}}'")
|
||||
|
||||
if action == "pull":
|
||||
return _run(f"docker pull {a}", timeout=120) if a else "Précise l'image."
|
||||
|
||||
if action == "rm":
|
||||
return _run(f"docker rm {a}") if a else "Précise le nom du conteneur."
|
||||
|
||||
if action == "rm-stopped":
|
||||
return _run("docker container prune -f")
|
||||
|
||||
if action == "rmi":
|
||||
return _run(f"docker rmi {a}") if a else "Précise l'image."
|
||||
|
||||
if action == "exec":
|
||||
if len(args) < 2:
|
||||
return "Format : docker exec <conteneur> <commande>"
|
||||
container = args[0]
|
||||
cmd = " ".join(args[1:])
|
||||
return _run(f"docker exec {container} {cmd}")
|
||||
|
||||
if action == "inspect":
|
||||
return _run(f"docker inspect {a}") if a else "Précise le nom du conteneur."
|
||||
|
||||
if action == "network":
|
||||
return _run("docker network ls")
|
||||
|
||||
if action == "volumes":
|
||||
return _run("docker volume ls")
|
||||
|
||||
if action == "compose-up":
|
||||
return _run("docker compose up -d", timeout=120) if not a else _run(f"docker compose -f {a} up -d", timeout=120)
|
||||
|
||||
if action == "compose-down":
|
||||
return _run("docker compose down")
|
||||
|
||||
return f"Action docker inconnue : {action}"
|
||||
|
||||
|
||||
def _lxc(action: str, args: list) -> str:
|
||||
a = args[0] if args else ""
|
||||
|
||||
if action == "list":
|
||||
return _run("lxc list --format table 2>/dev/null || lxc-ls -f 2>/dev/null")
|
||||
|
||||
if action == "start":
|
||||
return _run(f"lxc start {a}") if a else "Précise le nom."
|
||||
|
||||
if action == "stop":
|
||||
return _run(f"lxc stop {a}") if a else "Précise le nom."
|
||||
|
||||
if action == "restart":
|
||||
return _run(f"lxc restart {a}") if a else "Précise le nom."
|
||||
|
||||
if action == "exec":
|
||||
if len(args) < 2:
|
||||
return "Format : lxc exec <conteneur> <commande>"
|
||||
cmd = " ".join(args[1:])
|
||||
return _run(f"lxc exec {a} -- {cmd}")
|
||||
|
||||
if action == "info":
|
||||
return _run(f"lxc info {a}") if a else _run("lxc info")
|
||||
|
||||
if action == "snapshot":
|
||||
return _run(f"lxc snapshot {a}") if a else "Précise le nom."
|
||||
|
||||
if action == "delete":
|
||||
return _run(f"lxc delete {a} --force") if a else "Précise le nom."
|
||||
|
||||
return f"Action lxc inconnue : {action}"
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
parts = args.strip().split()
|
||||
if not parts:
|
||||
return "Précise : docker ou lxc suivi d'une action."
|
||||
|
||||
runtime = parts[0].lower()
|
||||
action = parts[1].lower() if len(parts) > 1 else "ps"
|
||||
rest = parts[2:] if len(parts) > 2 else []
|
||||
|
||||
if runtime == "docker":
|
||||
return _docker(action, rest)
|
||||
|
||||
if runtime in ("lxc", "lxd"):
|
||||
return _lxc(action, rest)
|
||||
|
||||
# Tentative de détection auto
|
||||
if runtime in ("ps", "stats", "images", "logs", "start", "stop", "restart", "exec"):
|
||||
return _docker(runtime, parts[1:])
|
||||
|
||||
return "Précise le runtime : docker ou lxc. Ex: SKILL:container ARGS:docker ps"
|
||||
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Skill CRON — gestion des tâches cron.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:cron ARGS:list [utilisateur]
|
||||
SKILL:cron ARGS:add <expression_cron> <commande>
|
||||
SKILL:cron ARGS:remove <pattern>
|
||||
SKILL:cron ARGS:system-list
|
||||
"""
|
||||
import subprocess
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
DESCRIPTION = "Gestion des tâches cron (crontab)"
|
||||
USAGE = "SKILL:cron ARGS:list | add <* * * * *> <commande> | remove <pattern> | system-list"
|
||||
|
||||
|
||||
def _run(cmd: str, timeout: int = 10) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=timeout
|
||||
)
|
||||
return (result.stdout + result.stderr).strip() or "(aucune sortie)"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
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 ""
|
||||
|
||||
if action == "list":
|
||||
user = rest.strip() or ""
|
||||
flag = f"-u {user}" if user else ""
|
||||
result = _run(f"crontab {flag} -l 2>/dev/null")
|
||||
return result if result else "Crontab vide."
|
||||
|
||||
if action == "add":
|
||||
# Format attendu : "* * * * * commande"
|
||||
# On split les 5 premiers champs (expression cron) + le reste (commande)
|
||||
words = rest.split()
|
||||
if len(words) < 6:
|
||||
return "Format : add <min> <heure> <jour> <mois> <jourSem> <commande>\nEx: add 0 3 * * * /usr/bin/apt-get update"
|
||||
cron_expr = " ".join(words[:5])
|
||||
command = " ".join(words[5:])
|
||||
entry = f"{cron_expr} {command}"
|
||||
|
||||
# Récupère le crontab actuel, ajoute la ligne
|
||||
current = _run("crontab -l 2>/dev/null")
|
||||
if entry in current:
|
||||
return f"Cette entrée existe déjà : {entry}"
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".cron", delete=False) as f:
|
||||
if current and "no crontab" not in current.lower():
|
||||
f.write(current + "\n")
|
||||
f.write(entry + "\n")
|
||||
tmpfile = f.name
|
||||
|
||||
out = _run(f"crontab {tmpfile}")
|
||||
os.unlink(tmpfile)
|
||||
return f"Entrée ajoutée : {entry}\n{out}"
|
||||
|
||||
if action == "remove":
|
||||
if not rest:
|
||||
return "Précise le pattern à supprimer."
|
||||
current = _run("crontab -l 2>/dev/null")
|
||||
lines = [l for l in current.splitlines() if rest not in l]
|
||||
new_cron = "\n".join(lines)
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".cron", delete=False) as f:
|
||||
f.write(new_cron + "\n")
|
||||
tmpfile = f.name
|
||||
|
||||
out = _run(f"crontab {tmpfile}")
|
||||
os.unlink(tmpfile)
|
||||
removed = len(current.splitlines()) - len(lines)
|
||||
return f"{removed} entrée(s) supprimée(s) contenant '{rest}'.\n{out}"
|
||||
|
||||
if action == "clear":
|
||||
return _run("crontab -r 2>/dev/null && echo 'Crontab effacé'")
|
||||
|
||||
if action == "system-list":
|
||||
# Crons système dans /etc/cron.*
|
||||
out = []
|
||||
for d in ["/etc/cron.d", "/etc/cron.daily", "/etc/cron.weekly", "/etc/cron.monthly"]:
|
||||
files = _run(f"ls {d} 2>/dev/null")
|
||||
if files:
|
||||
out.append(f"{d}:\n{files}")
|
||||
return "\n\n".join(out) or "Aucun cron système trouvé."
|
||||
|
||||
return "Action inconnue. Disponible : list, add, remove, clear, system-list"
|
||||
@@ -0,0 +1,177 @@
|
||||
"""
|
||||
Skill FILESYSTEM — opérations sur le système de fichiers.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:filesystem ARGS:ls <chemin>
|
||||
SKILL:filesystem ARGS:cat <fichier>
|
||||
SKILL:filesystem ARGS:write <fichier> | <contenu>
|
||||
SKILL:filesystem ARGS:append <fichier> | <contenu>
|
||||
SKILL:filesystem ARGS:delete <chemin>
|
||||
SKILL:filesystem ARGS:mkdir <chemin>
|
||||
SKILL:filesystem ARGS:move <src> | <dst>
|
||||
SKILL:filesystem ARGS:copy <src> | <dst>
|
||||
SKILL:filesystem ARGS:chmod <mode> <chemin>
|
||||
SKILL:filesystem ARGS:chown <owner> <chemin>
|
||||
SKILL:filesystem ARGS:find <chemin> <pattern>
|
||||
SKILL:filesystem ARGS:grep <pattern> <fichier>
|
||||
SKILL:filesystem ARGS:df
|
||||
SKILL:filesystem ARGS:du <chemin>
|
||||
SKILL:filesystem ARGS:stat <chemin>
|
||||
SKILL:filesystem ARGS:tail <fichier> [N]
|
||||
SKILL:filesystem ARGS:head <fichier> [N]
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
DESCRIPTION = "Opérations filesystem : ls, cat, write, delete, move, copy, chmod, find, grep, df, du"
|
||||
USAGE = "SKILL:filesystem ARGS:ls <path> | cat <file> | write <file>|<content> | delete <path> | find <path> <pattern> | grep <pattern> <file> | df | du <path> | tail <file> [N]"
|
||||
|
||||
# Chemins interdits pour éviter les accidents
|
||||
FORBIDDEN = ["/proc", "/sys", "/dev", "/run/systemd"]
|
||||
|
||||
|
||||
def _safe_path(path: str) -> bool:
|
||||
path = os.path.realpath(path)
|
||||
return not any(path.startswith(f) for f in FORBIDDEN)
|
||||
|
||||
|
||||
def _run(cmd: str, timeout: int = 15) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=timeout
|
||||
)
|
||||
out = (result.stdout + result.stderr).strip()
|
||||
return out[:4000] if out else "(aucune sortie)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"Timeout ({timeout}s)"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
# Sépare l'action du reste
|
||||
parts = args.strip().split(None, 1)
|
||||
action = parts[0].lower() if parts else ""
|
||||
rest = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
if action == "ls":
|
||||
path = rest or "."
|
||||
return _run(f"ls -lah {path}")
|
||||
|
||||
if action == "cat":
|
||||
if not rest:
|
||||
return "Précise le fichier."
|
||||
if not _safe_path(rest):
|
||||
return f"Accès refusé : {rest}"
|
||||
return _run(f"cat {rest}")
|
||||
|
||||
if action == "tail":
|
||||
parts2 = rest.split()
|
||||
filepath = parts2[0] if parts2 else ""
|
||||
n = parts2[1] if len(parts2) > 1 else "50"
|
||||
return _run(f"tail -n {n} {filepath}")
|
||||
|
||||
if action == "head":
|
||||
parts2 = rest.split()
|
||||
filepath = parts2[0] if parts2 else ""
|
||||
n = parts2[1] if len(parts2) > 1 else "30"
|
||||
return _run(f"head -n {n} {filepath}")
|
||||
|
||||
if action == "write":
|
||||
if "|" not in rest:
|
||||
return "Format : write <fichier> | <contenu>"
|
||||
filepath, content = rest.split("|", 1)
|
||||
filepath = filepath.strip()
|
||||
content = content.strip()
|
||||
if not _safe_path(filepath):
|
||||
return f"Accès refusé : {filepath}"
|
||||
try:
|
||||
os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True)
|
||||
with open(filepath, "w") as f:
|
||||
f.write(content)
|
||||
return f"Fichier écrit : {filepath} ({len(content)} caractères)"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
if action == "append":
|
||||
if "|" not in rest:
|
||||
return "Format : append <fichier> | <contenu>"
|
||||
filepath, content = rest.split("|", 1)
|
||||
filepath = filepath.strip()
|
||||
if not _safe_path(filepath):
|
||||
return f"Accès refusé : {filepath}"
|
||||
try:
|
||||
with open(filepath, "a") as f:
|
||||
f.write(content.strip() + "\n")
|
||||
return f"Contenu ajouté à {filepath}"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
if action == "delete":
|
||||
if not rest:
|
||||
return "Précise le chemin."
|
||||
if not _safe_path(rest):
|
||||
return f"Accès refusé : {rest}"
|
||||
# Confirmation implicite : on ne supprime pas récursivement sans -r explicite
|
||||
if os.path.isdir(rest):
|
||||
return _run(f"rm -rf {rest}")
|
||||
return _run(f"rm -f {rest}")
|
||||
|
||||
if action == "mkdir":
|
||||
if not rest:
|
||||
return "Précise le chemin."
|
||||
return _run(f"mkdir -p {rest}")
|
||||
|
||||
if action == "move":
|
||||
if "|" not in rest:
|
||||
return "Format : move <src> | <dst>"
|
||||
src, dst = rest.split("|", 1)
|
||||
return _run(f"mv {src.strip()} {dst.strip()}")
|
||||
|
||||
if action == "copy":
|
||||
if "|" not in rest:
|
||||
return "Format : copy <src> | <dst>"
|
||||
src, dst = rest.split("|", 1)
|
||||
return _run(f"cp -r {src.strip()} {dst.strip()}")
|
||||
|
||||
if action == "chmod":
|
||||
parts2 = rest.split(None, 1)
|
||||
if len(parts2) < 2:
|
||||
return "Format : chmod <mode> <chemin>"
|
||||
return _run(f"chmod {parts2[0]} {parts2[1]}")
|
||||
|
||||
if action == "chown":
|
||||
parts2 = rest.split(None, 1)
|
||||
if len(parts2) < 2:
|
||||
return "Format : chown <owner:group> <chemin>"
|
||||
return _run(f"chown -R {parts2[0]} {parts2[1]}")
|
||||
|
||||
if action == "find":
|
||||
parts2 = rest.split(None, 1)
|
||||
path = parts2[0] if parts2 else "."
|
||||
pattern = parts2[1] if len(parts2) > 1 else "*"
|
||||
return _run(f"find {path} -name '{pattern}' 2>/dev/null | head -50")
|
||||
|
||||
if action == "grep":
|
||||
parts2 = rest.split(None, 1)
|
||||
if len(parts2) < 2:
|
||||
return "Format : grep <pattern> <fichier>"
|
||||
return _run(f"grep -n '{parts2[0]}' {parts2[1]} 2>/dev/null | head -50")
|
||||
|
||||
if action == "df":
|
||||
return _run("df -h")
|
||||
|
||||
if action == "du":
|
||||
path = rest or "."
|
||||
return _run(f"du -sh {path}/* 2>/dev/null | sort -rh | head -20")
|
||||
|
||||
if action == "stat":
|
||||
if not rest:
|
||||
return "Précise le chemin."
|
||||
return _run(f"stat {rest}")
|
||||
|
||||
return (
|
||||
"Action inconnue. Disponible : ls, cat, tail, head, write, append, delete, "
|
||||
"mkdir, move, copy, chmod, chown, find, grep, df, du, stat"
|
||||
)
|
||||
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
Skill JOURNAL — consultation des logs système via journalctl.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:journal ARGS:tail [N]
|
||||
SKILL:journal ARGS:service <service> [N]
|
||||
SKILL:journal ARGS:boot [N]
|
||||
SKILL:journal ARGS:errors [N]
|
||||
SKILL:journal ARGS:since <durée> (ex: "1h", "30min", "yesterday")
|
||||
SKILL:journal ARGS:grep <pattern> [service]
|
||||
SKILL:journal ARGS:kernel [N]
|
||||
SKILL:journal ARGS:disk-usage
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
DESCRIPTION = "Consultation des logs système via journalctl et /var/log"
|
||||
USAGE = "SKILL:journal ARGS:tail [N] | service <service> [N] | errors [N] | since <durée> | grep <pattern> | boot | kernel"
|
||||
|
||||
|
||||
def _run(cmd: str, timeout: int = 15) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=timeout
|
||||
)
|
||||
out = (result.stdout + result.stderr).strip()
|
||||
return out[:4000] if out else "(aucune sortie)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"Timeout ({timeout}s)"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
parts = args.strip().split(None, 1)
|
||||
action = parts[0].lower() if parts else "tail"
|
||||
rest = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
if action == "tail":
|
||||
n = rest.strip() or "50"
|
||||
return _run(f"journalctl -n {n} --no-pager -o short-iso")
|
||||
|
||||
if action == "service":
|
||||
parts2 = rest.split()
|
||||
service = parts2[0] if parts2 else ""
|
||||
n = parts2[1] if len(parts2) > 1 else "50"
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
return _run(f"journalctl -u {service} -n {n} --no-pager -o short-iso")
|
||||
|
||||
if action == "boot":
|
||||
n = rest.strip() or "50"
|
||||
return _run(f"journalctl -b -n {n} --no-pager -o short-iso")
|
||||
|
||||
if action == "errors":
|
||||
n = rest.strip() or "30"
|
||||
return _run(f"journalctl -p err..emerg -n {n} --no-pager -o short-iso")
|
||||
|
||||
if action == "warnings":
|
||||
n = rest.strip() or "30"
|
||||
return _run(f"journalctl -p warning..emerg -n {n} --no-pager -o short-iso")
|
||||
|
||||
if action == "since":
|
||||
if not rest:
|
||||
return "Précise la durée (ex: '1h', '30min', 'yesterday', '2024-01-01')"
|
||||
# Convertit "1h" → "1 hour ago", "30min" → "30 minutes ago"
|
||||
since = rest.strip()
|
||||
if since.endswith("h") and since[:-1].isdigit():
|
||||
since = f"{since[:-1]} hours ago"
|
||||
elif since.endswith("min") and since[:-3].isdigit():
|
||||
since = f"{since[:-3]} minutes ago"
|
||||
return _run(f"journalctl --since='{since}' --no-pager -o short-iso | tail -100")
|
||||
|
||||
if action == "grep":
|
||||
parts2 = rest.split(None, 1)
|
||||
pattern = parts2[0] if parts2 else ""
|
||||
service = parts2[1].strip() if len(parts2) > 1 else ""
|
||||
if not pattern:
|
||||
return "Précise le pattern."
|
||||
cmd = f"journalctl --no-pager -o short-iso"
|
||||
if service:
|
||||
cmd += f" -u {service}"
|
||||
cmd += f" | grep -i '{pattern}' | tail -50"
|
||||
return _run(cmd)
|
||||
|
||||
if action == "kernel":
|
||||
n = rest.strip() or "30"
|
||||
return _run(f"journalctl -k -n {n} --no-pager -o short-iso")
|
||||
|
||||
if action == "disk-usage":
|
||||
return _run("journalctl --disk-usage")
|
||||
|
||||
if action == "vacuum":
|
||||
# Nettoyage des vieux logs
|
||||
size = rest.strip() or "500M"
|
||||
return _run(f"journalctl --vacuum-size={size}")
|
||||
|
||||
if action == "file":
|
||||
# Lire un fichier de log classique
|
||||
filepath = rest.strip()
|
||||
if not filepath:
|
||||
return "Précise le fichier."
|
||||
return _run(f"tail -100 {filepath}")
|
||||
|
||||
return (
|
||||
"Action inconnue. Disponible : tail, service, boot, errors, warnings, "
|
||||
"since, grep, kernel, disk-usage, vacuum, file"
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Skill MQTT_SEND — publier un message sur n'importe quel topic MQTT.
|
||||
Permet à l'agent de communiquer proactivement avec d'autres agents.
|
||||
|
||||
Usage LLM : SKILL:mqtt_send ARGS:<topic> | <message>
|
||||
"""
|
||||
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)
|
||||
topic = topic.strip()
|
||||
message = message.strip()
|
||||
|
||||
if not topic:
|
||||
return "Topic vide."
|
||||
|
||||
context.mqtt.publish_raw(topic, message)
|
||||
return f"Message publié sur '{topic}'."
|
||||
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
Skill NETWORK — administration réseau.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:network ARGS:ip [show|route|link]
|
||||
SKILL:network ARGS:ping <hôte> [count]
|
||||
SKILL:network ARGS:traceroute <hôte>
|
||||
SKILL:network ARGS:dns <nom>
|
||||
SKILL:network ARGS:ports [tcp|udp]
|
||||
SKILL:network ARGS:connections
|
||||
SKILL:network ARGS:firewall status
|
||||
SKILL:network ARGS:firewall allow <port/service>
|
||||
SKILL:network ARGS:firewall deny <port/service>
|
||||
SKILL:network ARGS:firewall delete <règle>
|
||||
SKILL:network ARGS:firewall list
|
||||
SKILL:network ARGS:bandwidth [interface]
|
||||
SKILL:network ARGS:hosts [add|remove] <ip> <nom>
|
||||
SKILL:network ARGS:wget <url>
|
||||
SKILL:network ARGS:curl <url>
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
DESCRIPTION = "Administration réseau : ip, ping, traceroute, DNS, ports, firewall ufw/iptables"
|
||||
USAGE = "SKILL:network ARGS:ip | ping <host> | traceroute <host> | dns <host> | ports | connections | firewall status|allow|deny|list | wget <url>"
|
||||
|
||||
|
||||
def _run(cmd: str, timeout: int = 20) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=timeout
|
||||
)
|
||||
out = (result.stdout + result.stderr).strip()
|
||||
return out[:4000] if out else "(aucune sortie)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"Timeout ({timeout}s)"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
parts = args.strip().split(None, 1)
|
||||
action = parts[0].lower() if parts else ""
|
||||
rest = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
if action == "ip":
|
||||
sub = rest.strip().lower() or "show"
|
||||
if sub == "show" or sub == "addr":
|
||||
return _run("ip -br addr show")
|
||||
if sub == "route":
|
||||
return _run("ip route show")
|
||||
if sub == "link":
|
||||
return _run("ip -br link show")
|
||||
if sub == "full":
|
||||
return _run("ip addr show")
|
||||
return _run(f"ip {rest}")
|
||||
|
||||
if action == "ping":
|
||||
parts2 = rest.split()
|
||||
host = parts2[0] if parts2 else ""
|
||||
count = parts2[1] if len(parts2) > 1 else "4"
|
||||
if not host:
|
||||
return "Précise l'hôte."
|
||||
return _run(f"ping -c {count} {host}", timeout=int(count) * 3 + 5)
|
||||
|
||||
if action == "traceroute":
|
||||
host = rest.strip()
|
||||
if not host:
|
||||
return "Précise l'hôte."
|
||||
return _run(f"traceroute -m 15 {host}", timeout=60)
|
||||
|
||||
if action == "dns":
|
||||
host = rest.strip()
|
||||
if not host:
|
||||
return "Précise le nom à résoudre."
|
||||
return _run(f"dig +short {host} && dig +short -x $(dig +short {host} | head -1) 2>/dev/null || nslookup {host}")
|
||||
|
||||
if action == "ports":
|
||||
proto = rest.strip().lower()
|
||||
if proto == "udp":
|
||||
return _run("ss -ulnp")
|
||||
return _run("ss -tlnp")
|
||||
|
||||
if action == "connections":
|
||||
return _run("ss -tunp | head -50")
|
||||
|
||||
if action == "netstat":
|
||||
return _run("ss -s")
|
||||
|
||||
if action == "firewall":
|
||||
parts2 = rest.split(None, 1)
|
||||
sub = parts2[0].lower() if parts2 else "status"
|
||||
arg = parts2[1] if len(parts2) > 1 else ""
|
||||
|
||||
# Détecte ufw ou iptables
|
||||
ufw_available = _run("which ufw") != ""
|
||||
|
||||
if sub == "status":
|
||||
if ufw_available:
|
||||
return _run("ufw status verbose")
|
||||
return _run("iptables -L -n -v --line-numbers")
|
||||
|
||||
if sub == "allow":
|
||||
if not arg:
|
||||
return "Précise le port/service."
|
||||
if ufw_available:
|
||||
return _run(f"ufw allow {arg}")
|
||||
return _run(f"iptables -A INPUT -p tcp --dport {arg} -j ACCEPT")
|
||||
|
||||
if sub == "deny":
|
||||
if not arg:
|
||||
return "Précise le port/service."
|
||||
if ufw_available:
|
||||
return _run(f"ufw deny {arg}")
|
||||
return _run(f"iptables -A INPUT -p tcp --dport {arg} -j DROP")
|
||||
|
||||
if sub == "delete":
|
||||
if not arg:
|
||||
return "Précise la règle ou le numéro."
|
||||
if ufw_available:
|
||||
return _run(f"ufw delete {arg}")
|
||||
return _run(f"iptables -D INPUT {arg}")
|
||||
|
||||
if sub == "list":
|
||||
if ufw_available:
|
||||
return _run("ufw status numbered")
|
||||
return _run("iptables -L INPUT -n -v --line-numbers")
|
||||
|
||||
if sub == "enable":
|
||||
return _run("ufw --force enable") if ufw_available else "ufw non disponible."
|
||||
|
||||
if sub == "disable":
|
||||
return _run("ufw disable") if ufw_available else "ufw non disponible."
|
||||
|
||||
return f"Sous-commande firewall inconnue : {sub}"
|
||||
|
||||
if action == "bandwidth":
|
||||
iface = rest.strip() or "eth0"
|
||||
# ifstat ou /proc/net/dev si pas dispo
|
||||
result = _run(f"cat /proc/net/dev | grep {iface}")
|
||||
if not result:
|
||||
return f"Interface {iface} introuvable."
|
||||
return result
|
||||
|
||||
if action == "hosts":
|
||||
parts2 = rest.split()
|
||||
sub = parts2[0].lower() if parts2 else "list"
|
||||
if sub == "list" or not parts2:
|
||||
return _run("cat /etc/hosts")
|
||||
if sub == "add" and len(parts2) >= 3:
|
||||
ip, name = parts2[1], parts2[2]
|
||||
return _run(f"echo '{ip} {name}' >> /etc/hosts && echo 'Ajouté : {ip} {name}'")
|
||||
if sub == "remove" and len(parts2) >= 2:
|
||||
name = parts2[1]
|
||||
return _run(f"sed -i '/{name}/d' /etc/hosts && echo 'Supprimé : {name}'")
|
||||
return "Usage : hosts list | add <ip> <nom> | remove <nom>"
|
||||
|
||||
if action == "wget":
|
||||
url = rest.strip()
|
||||
if not url:
|
||||
return "Précise l'URL."
|
||||
return _run(f"wget -q --spider {url} && echo 'URL accessible' || echo 'URL inaccessible'")
|
||||
|
||||
if action == "curl":
|
||||
url = rest.strip()
|
||||
if not url:
|
||||
return "Précise l'URL."
|
||||
return _run(f"curl -sI {url} | head -10")
|
||||
|
||||
if action == "arp":
|
||||
return _run("arp -n")
|
||||
|
||||
if action == "hostname":
|
||||
return _run("hostname -f")
|
||||
|
||||
return (
|
||||
"Action inconnue. Disponible : ip, ping, traceroute, dns, ports, connections, "
|
||||
"firewall, hosts, wget, curl, arp, hostname, netstat"
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
Skill PROCESS — gestion des processus.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:process ARGS:list [filtre]
|
||||
SKILL:process ARGS:top [N]
|
||||
SKILL:process ARGS:kill <pid> [signal]
|
||||
SKILL:process ARGS:killall <nom>
|
||||
SKILL:process ARGS:nice <pid> <priorité>
|
||||
SKILL:process ARGS:info <pid>
|
||||
SKILL:process ARGS:tree
|
||||
SKILL:process ARGS:find <nom>
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
DESCRIPTION = "Gestion des processus : list, top, kill, killall, nice, find"
|
||||
USAGE = "SKILL:process ARGS:list [filtre] | top [N] | kill <pid> [signal] | killall <nom> | info <pid> | tree | find <nom>"
|
||||
|
||||
|
||||
def _run(cmd: str, timeout: int = 10) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=timeout
|
||||
)
|
||||
out = (result.stdout + result.stderr).strip()
|
||||
return out[:4000] if out else "(aucune sortie)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"Timeout ({timeout}s)"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
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 ""
|
||||
|
||||
if action == "list":
|
||||
cmd = "ps aux --sort=-%cpu | head -30"
|
||||
if rest:
|
||||
cmd = f"ps aux | grep -i {rest} | grep -v grep"
|
||||
return _run(cmd)
|
||||
|
||||
if action == "top":
|
||||
n = rest.strip() or "15"
|
||||
return _run(
|
||||
f"ps aux --sort=-%cpu | head -{n} | "
|
||||
"awk 'NR==1{print} NR>1{printf \"%-10s %-6s %-5s %-5s %s\\n\",$1,$2,$3,$4,$11}'"
|
||||
)
|
||||
|
||||
if action == "kill":
|
||||
parts2 = rest.split()
|
||||
if not parts2:
|
||||
return "Précise le PID."
|
||||
pid = parts2[0]
|
||||
signal = parts2[1] if len(parts2) > 1 else "15"
|
||||
return _run(f"kill -{signal} {pid}")
|
||||
|
||||
if action == "kill9":
|
||||
if not rest:
|
||||
return "Précise le PID."
|
||||
return _run(f"kill -9 {rest.strip()}")
|
||||
|
||||
if action == "killall":
|
||||
if not rest:
|
||||
return "Précise le nom du processus."
|
||||
return _run(f"killall {rest.strip()}")
|
||||
|
||||
if action == "nice":
|
||||
parts2 = rest.split()
|
||||
if len(parts2) < 2:
|
||||
return "Format : nice <pid> <priorité (-20 à 19)>"
|
||||
pid, prio = parts2[0], parts2[1]
|
||||
return _run(f"renice {prio} -p {pid}")
|
||||
|
||||
if action == "info":
|
||||
if not rest:
|
||||
return "Précise le PID."
|
||||
pid = rest.strip()
|
||||
out = _run(f"ps -p {pid} -o pid,ppid,user,%cpu,%mem,vsz,rss,stat,start,time,comm --no-headers")
|
||||
cmdline = _run(f"cat /proc/{pid}/cmdline 2>/dev/null | tr '\\0' ' '")
|
||||
return f"Process {pid}:\n{out}\nCmdline: {cmdline}"
|
||||
|
||||
if action == "tree":
|
||||
return _run("pstree -p | head -50")
|
||||
|
||||
if action == "find":
|
||||
if not rest:
|
||||
return "Précise le nom du processus."
|
||||
return _run(f"pgrep -a -i {rest.strip()}")
|
||||
|
||||
if action == "lsof":
|
||||
# Fichiers ouverts par un processus
|
||||
if rest:
|
||||
return _run(f"lsof -p {rest.strip()} | head -30")
|
||||
return _run("lsof | wc -l && echo fichiers ouverts au total")
|
||||
|
||||
return "Action inconnue. Disponible : list, top, kill, kill9, killall, nice, info, tree, find, lsof"
|
||||
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
Skill SCRIPT — créer et exécuter un script bash, avec renvoi du résultat via MQTT.
|
||||
|
||||
L'environnement du script expose automatiquement :
|
||||
MQTT_BROKER, MQTT_REPLY_TOPIC, AGENT_ID
|
||||
|
||||
Ainsi un script peut publier son résultat directement :
|
||||
mosquitto_pub -h $MQTT_BROKER -t $MQTT_REPLY_TOPIC -m "mon résultat"
|
||||
|
||||
Usage LLM :
|
||||
SKILL:script ARGS:run | <contenu du script>
|
||||
SKILL:script ARGS:save <nom> | <contenu>
|
||||
SKILL:script ARGS:exec <nom> [args]
|
||||
SKILL:script ARGS:list
|
||||
SKILL:script ARGS:show <nom>
|
||||
SKILL:script ARGS:delete <nom>
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import stat
|
||||
|
||||
DESCRIPTION = "Créer/exécuter des scripts bash avec renvoi du résultat via MQTT"
|
||||
USAGE = "SKILL:script ARGS:run|<contenu> | save <nom>|<contenu> | exec <nom> | list | show <nom>"
|
||||
|
||||
SCRIPTS_DIR = "/opt/agent_debian/scripts"
|
||||
|
||||
|
||||
def _ensure_dir():
|
||||
os.makedirs(SCRIPTS_DIR, exist_ok=True)
|
||||
|
||||
|
||||
def _run(cmd: str, env: dict = None, timeout: int = 60) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=timeout,
|
||||
env=env, executable="/bin/bash"
|
||||
)
|
||||
out = (result.stdout + result.stderr).strip()
|
||||
if len(out) > 4000:
|
||||
out = out[:4000] + "\n... [tronqué]"
|
||||
return out or f"(code retour : {result.returncode})"
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"Timeout ({timeout}s)"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
def _build_env(context) -> dict:
|
||||
"""Environnement injecté dans chaque script."""
|
||||
env = os.environ.copy()
|
||||
mc = context.config.get("mqtt", {})
|
||||
env["MQTT_BROKER"] = mc.get("host", "localhost")
|
||||
env["MQTT_PORT"] = str(mc.get("port", 1883))
|
||||
env["MQTT_REPLY_TOPIC"] = "agents/nexus/inbox"
|
||||
env["AGENT_ID"] = context.agent_id
|
||||
return env
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
_ensure_dir()
|
||||
parts = args.strip().split(None, 1)
|
||||
action = parts[0].lower() if parts else "run"
|
||||
rest = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
if action == "run":
|
||||
# Exécution directe d'un script inline
|
||||
if not rest:
|
||||
return "Précise le contenu du script."
|
||||
content = rest.replace("\\n", "\n")
|
||||
# Fichier temporaire
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w", suffix=".sh", delete=False, dir="/tmp"
|
||||
) as f:
|
||||
f.write("#!/bin/bash\nset -e\n" + content)
|
||||
tmpfile = f.name
|
||||
os.chmod(tmpfile, stat.S_IRWXU)
|
||||
env = _build_env(context)
|
||||
out = _run(tmpfile, env=env, timeout=60)
|
||||
os.unlink(tmpfile)
|
||||
return out
|
||||
|
||||
if action == "save":
|
||||
if "|" not in rest:
|
||||
return "Format : save <nom> | <contenu du script>"
|
||||
name, content = rest.split("|", 1)
|
||||
name = name.strip().replace("/", "_") # Sécurité
|
||||
content = content.strip().replace("\\n", "\n")
|
||||
path = os.path.join(SCRIPTS_DIR, name + ".sh")
|
||||
with open(path, "w") as f:
|
||||
f.write("#!/bin/bash\n" + content)
|
||||
os.chmod(path, stat.S_IRWXU)
|
||||
return f"Script sauvegardé : {path}"
|
||||
|
||||
if action == "exec":
|
||||
parts2 = rest.split(None, 1)
|
||||
name = parts2[0] if parts2 else ""
|
||||
sargs = parts2[1] if len(parts2) > 1 else ""
|
||||
if not name:
|
||||
return "Précise le nom du script."
|
||||
path = os.path.join(SCRIPTS_DIR, name + ".sh")
|
||||
if not os.path.exists(path):
|
||||
return f"Script '{name}' introuvable dans {SCRIPTS_DIR}"
|
||||
env = _build_env(context)
|
||||
return _run(f"{path} {sargs}", env=env, timeout=120)
|
||||
|
||||
if action == "list":
|
||||
files = [f for f in os.listdir(SCRIPTS_DIR) if f.endswith(".sh")]
|
||||
return "\n".join(files) if files else "Aucun script sauvegardé."
|
||||
|
||||
if action == "show":
|
||||
name = rest.strip()
|
||||
if not name:
|
||||
return "Précise le nom du script."
|
||||
path = os.path.join(SCRIPTS_DIR, name + ".sh")
|
||||
if not os.path.exists(path):
|
||||
return f"Script '{name}' introuvable."
|
||||
with open(path) as f:
|
||||
return f.read()
|
||||
|
||||
if action == "delete":
|
||||
name = rest.strip()
|
||||
if not name:
|
||||
return "Précise le nom du script."
|
||||
path = os.path.join(SCRIPTS_DIR, name + ".sh")
|
||||
if os.path.exists(path):
|
||||
os.unlink(path)
|
||||
return f"Script '{name}' supprimé."
|
||||
return f"Script '{name}' introuvable."
|
||||
|
||||
return "Action inconnue. Disponible : run, save, exec, list, show, delete"
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Skill SHELL — exécution de commandes shell arbitraires.
|
||||
Skill de dernier recours quand aucun skill spécialisé ne convient.
|
||||
|
||||
Usage LLM : SKILL:shell ARGS:<commande bash>
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
DESCRIPTION = "Exécution de commandes shell arbitraires (fallback général)"
|
||||
USAGE = "SKILL:shell ARGS:<commande bash complète>"
|
||||
|
||||
# Commandes bloquées pour éviter les accidents critiques
|
||||
BLOCKED = [
|
||||
"rm -rf /",
|
||||
"dd if=/dev/zero of=/dev/",
|
||||
"mkfs",
|
||||
"> /dev/sda",
|
||||
":(){ :|:& };:", # fork bomb
|
||||
]
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
cmd = args.strip()
|
||||
if not cmd:
|
||||
return "Commande vide."
|
||||
|
||||
# Vérification des commandes dangereuses
|
||||
for blocked in BLOCKED:
|
||||
if blocked in cmd:
|
||||
return f"Commande bloquée pour sécurité : {blocked}"
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
timeout=60,
|
||||
executable="/bin/bash",
|
||||
)
|
||||
stdout = result.stdout.strip()
|
||||
stderr = result.stderr.strip()
|
||||
returncode = result.returncode
|
||||
|
||||
output = ""
|
||||
if stdout:
|
||||
output += stdout
|
||||
if stderr:
|
||||
output += ("\n" if output else "") + f"[stderr] {stderr}"
|
||||
if not output:
|
||||
output = f"(Commande exécutée, code retour : {returncode})"
|
||||
|
||||
# Tronqué à 4000 caractères
|
||||
if len(output) > 4000:
|
||||
output = output[:4000] + f"\n... [tronqué, {len(output)} caractères total]"
|
||||
|
||||
return output
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return "Timeout (60s) — commande trop longue."
|
||||
except Exception as e:
|
||||
return f"Erreur : {e}"
|
||||
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Skill SYSINFO — informations système complètes.
|
||||
|
||||
Usage LLM : SKILL:sysinfo ARGS:all | cpu | mem | disk | uptime | load | net
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
DESCRIPTION = "Informations système : CPU, RAM, disque, uptime, charge, réseau"
|
||||
USAGE = "SKILL:sysinfo ARGS:all | cpu | mem | disk | uptime | load | net"
|
||||
|
||||
|
||||
def _run(cmd: str) -> str:
|
||||
try:
|
||||
return subprocess.check_output(cmd, shell=True, text=True, stderr=subprocess.STDOUT).strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
return e.output.strip() or str(e)
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
what = args.strip().lower() or "all"
|
||||
|
||||
sections = []
|
||||
|
||||
if what in ("all", "uptime"):
|
||||
uptime = _run("uptime -p")
|
||||
since = _run("uptime -s")
|
||||
sections.append(f"Uptime : {uptime} (depuis {since})")
|
||||
|
||||
if what in ("all", "load"):
|
||||
load = _run("cat /proc/loadavg")
|
||||
cpus = _run("nproc")
|
||||
sections.append(f"Charge système : {load} ({cpus} CPU)")
|
||||
|
||||
if what in ("all", "cpu"):
|
||||
cpu_info = _run("lscpu | grep -E 'Model name|CPU\\(s\\)|MHz'")
|
||||
cpu_usage = _run(
|
||||
"top -bn1 | grep 'Cpu(s)' | awk '{print $2+$4\"%\"}'"
|
||||
)
|
||||
sections.append(f"CPU :\n{cpu_info}\nUtilisation : {cpu_usage}")
|
||||
|
||||
if what in ("all", "mem"):
|
||||
mem = _run("free -h")
|
||||
sections.append(f"Mémoire :\n{mem}")
|
||||
|
||||
if what in ("all", "disk"):
|
||||
disk = _run("df -h --output=source,size,used,avail,pcent,target | column -t")
|
||||
sections.append(f"Disques :\n{disk}")
|
||||
|
||||
if what in ("all", "net"):
|
||||
ifaces = _run("ip -br addr show")
|
||||
sections.append(f"Interfaces réseau :\n{ifaces}")
|
||||
|
||||
if what == "os":
|
||||
osinfo = _run("cat /etc/os-release | grep -E '^(NAME|VERSION)='")
|
||||
kernel = _run("uname -r")
|
||||
sections.append(f"OS :\n{osinfo}\nKernel : {kernel}")
|
||||
|
||||
if not sections:
|
||||
return (
|
||||
"Option inconnue. Utilise : all, cpu, mem, disk, uptime, load, net, os"
|
||||
)
|
||||
|
||||
return "\n\n".join(sections)
|
||||
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
Skill SYSTEMD — gestion des services systemd.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:systemd ARGS:status <service>
|
||||
SKILL:systemd ARGS:start <service>
|
||||
SKILL:systemd ARGS:stop <service>
|
||||
SKILL:systemd ARGS:restart <service>
|
||||
SKILL:systemd ARGS:reload <service>
|
||||
SKILL:systemd ARGS:enable <service>
|
||||
SKILL:systemd ARGS:disable <service>
|
||||
SKILL:systemd ARGS:logs <service> [lignes]
|
||||
SKILL:systemd ARGS:list [pattern]
|
||||
SKILL:systemd ARGS:failed
|
||||
SKILL:systemd ARGS:daemon-reload
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
DESCRIPTION = "Gestion des services systemd (start/stop/restart/status/logs/enable/disable)"
|
||||
USAGE = "SKILL:systemd ARGS:status <service> | start <service> | stop <service> | restart <service> | enable <service> | disable <service> | logs <service> [N] | list | failed"
|
||||
|
||||
|
||||
def _run(cmd: str, timeout: int = 30) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=timeout
|
||||
)
|
||||
out = (result.stdout + result.stderr).strip()
|
||||
return out[:4000] if out else "(aucune sortie)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"Timeout ({timeout}s)"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
parts = args.strip().split()
|
||||
action = parts[0].lower() if parts else ""
|
||||
service = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
if action == "status":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
return _run(f"systemctl status {service} --no-pager -l")
|
||||
|
||||
if action == "start":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
out = _run(f"systemctl start {service}")
|
||||
status = _run(f"systemctl is-active {service}")
|
||||
return f"Démarrage de {service}... Statut : {status}\n{out}"
|
||||
|
||||
if action == "stop":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
out = _run(f"systemctl stop {service}")
|
||||
status = _run(f"systemctl is-active {service}")
|
||||
return f"Arrêt de {service}... Statut : {status}\n{out}"
|
||||
|
||||
if action == "restart":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
out = _run(f"systemctl restart {service}")
|
||||
status = _run(f"systemctl is-active {service}")
|
||||
return f"Redémarrage de {service}... Statut : {status}\n{out}"
|
||||
|
||||
if action == "reload":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
return _run(f"systemctl reload {service}")
|
||||
|
||||
if action == "enable":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
return _run(f"systemctl enable {service}")
|
||||
|
||||
if action == "disable":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
return _run(f"systemctl disable {service}")
|
||||
|
||||
if action == "mask":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
return _run(f"systemctl mask {service}")
|
||||
|
||||
if action == "unmask":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
return _run(f"systemctl unmask {service}")
|
||||
|
||||
if action == "logs":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
n = parts[2] if len(parts) > 2 else "50"
|
||||
try:
|
||||
n = int(n)
|
||||
except ValueError:
|
||||
n = 50
|
||||
return _run(f"journalctl -u {service} -n {n} --no-pager -o short-iso")
|
||||
|
||||
if action == "list":
|
||||
pattern = parts[1] if len(parts) > 1 else ""
|
||||
cmd = "systemctl list-units --type=service --no-pager"
|
||||
if pattern:
|
||||
cmd += f" | grep {pattern}"
|
||||
return _run(cmd)
|
||||
|
||||
if action == "list-all":
|
||||
return _run("systemctl list-units --type=service --all --no-pager")
|
||||
|
||||
if action == "failed":
|
||||
return _run("systemctl list-units --state=failed --no-pager")
|
||||
|
||||
if action == "daemon-reload":
|
||||
return _run("systemctl daemon-reload")
|
||||
|
||||
if action == "is-active":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
return _run(f"systemctl is-active {service}")
|
||||
|
||||
if action == "is-enabled":
|
||||
if not service:
|
||||
return "Précise le service."
|
||||
return _run(f"systemctl is-enabled {service}")
|
||||
|
||||
return (
|
||||
"Action inconnue. Disponible : status, start, stop, restart, reload, "
|
||||
"enable, disable, mask, unmask, logs, list, list-all, failed, "
|
||||
"daemon-reload, is-active, is-enabled"
|
||||
)
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Skill USER — gestion des utilisateurs et groupes.
|
||||
|
||||
Usage LLM :
|
||||
SKILL:user ARGS:list
|
||||
SKILL:user ARGS:add <nom> [--sudo]
|
||||
SKILL:user ARGS:delete <nom>
|
||||
SKILL:user ARGS:passwd <nom>
|
||||
SKILL:user ARGS:info <nom>
|
||||
SKILL:user ARGS:groups <nom>
|
||||
SKILL:user ARGS:addgroup <nom> <groupe>
|
||||
SKILL:user ARGS:removegroup <nom> <groupe>
|
||||
SKILL:user ARGS:lock <nom>
|
||||
SKILL:user ARGS:unlock <nom>
|
||||
SKILL:user ARGS:whoami
|
||||
SKILL:user ARGS:logged
|
||||
SKILL:user ARGS:sudoers
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
DESCRIPTION = "Gestion utilisateurs et groupes : add, delete, passwd, groups, lock/unlock, sudoers"
|
||||
USAGE = "SKILL:user ARGS:list | add <nom> [--sudo] | delete <nom> | passwd <nom> | info <nom> | groups <nom> | addgroup <nom> <groupe> | lock <nom> | unlock <nom> | logged"
|
||||
|
||||
|
||||
def _run(cmd: str, timeout: int = 15) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, text=True,
|
||||
capture_output=True, timeout=timeout
|
||||
)
|
||||
out = (result.stdout + result.stderr).strip()
|
||||
return out[:3000] if out else "(aucune sortie)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"Timeout ({timeout}s)"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
def run(args: str, context) -> str:
|
||||
parts = args.strip().split()
|
||||
action = parts[0].lower() if parts else "list"
|
||||
rest = parts[1:] if len(parts) > 1 else []
|
||||
|
||||
if action == "list":
|
||||
return _run("getent passwd | awk -F: '$3>=1000 || $3==0 {print $1\" (uid=\"$3\", shell=\"$7\")\"}'")
|
||||
|
||||
if action == "add":
|
||||
if not rest:
|
||||
return "Précise le nom d'utilisateur."
|
||||
name = rest[0]
|
||||
sudo = "--sudo" in rest or "-s" in rest
|
||||
cmds = [
|
||||
f"adduser --gecos '' --disabled-password {name}",
|
||||
]
|
||||
if sudo:
|
||||
cmds.append(f"usermod -aG sudo {name}")
|
||||
return "\n".join(_run(c) for c in cmds)
|
||||
|
||||
if action == "delete":
|
||||
if not rest:
|
||||
return "Précise le nom d'utilisateur."
|
||||
return _run(f"deluser --remove-home {rest[0]}")
|
||||
|
||||
if action == "passwd":
|
||||
if not rest:
|
||||
return "Précise le nom d'utilisateur."
|
||||
# Génère un mot de passe aléatoire
|
||||
pwd = _run("openssl rand -base64 12").strip()
|
||||
out = _run(f"echo '{rest[0]}:{pwd}' | chpasswd")
|
||||
return f"{out}\nNouveauMDP : {pwd}"
|
||||
|
||||
if action == "info":
|
||||
if not rest:
|
||||
return "Précise le nom d'utilisateur."
|
||||
name = rest[0]
|
||||
return _run(f"id {name} && getent passwd {name}")
|
||||
|
||||
if action == "groups":
|
||||
if not rest:
|
||||
return "Précise le nom d'utilisateur."
|
||||
return _run(f"groups {rest[0]}")
|
||||
|
||||
if action == "addgroup":
|
||||
if len(rest) < 2:
|
||||
return "Format : addgroup <utilisateur> <groupe>"
|
||||
return _run(f"usermod -aG {rest[1]} {rest[0]}")
|
||||
|
||||
if action == "removegroup":
|
||||
if len(rest) < 2:
|
||||
return "Format : removegroup <utilisateur> <groupe>"
|
||||
return _run(f"gpasswd -d {rest[0]} {rest[1]}")
|
||||
|
||||
if action == "lock":
|
||||
if not rest:
|
||||
return "Précise le nom d'utilisateur."
|
||||
return _run(f"usermod -L {rest[0]}")
|
||||
|
||||
if action == "unlock":
|
||||
if not rest:
|
||||
return "Précise le nom d'utilisateur."
|
||||
return _run(f"usermod -U {rest[0]}")
|
||||
|
||||
if action == "whoami":
|
||||
return _run("whoami && id")
|
||||
|
||||
if action == "logged":
|
||||
return _run("who && echo '---' && last -n 10")
|
||||
|
||||
if action == "sudoers":
|
||||
return _run("getent group sudo | cut -d: -f4")
|
||||
|
||||
if action == "ssh-key":
|
||||
# Ajouter une clé SSH pour un utilisateur
|
||||
if len(rest) < 2:
|
||||
return "Format : ssh-key <utilisateur> <clé_publique>"
|
||||
name = rest[0]
|
||||
key = " ".join(rest[1:])
|
||||
return _run(
|
||||
f"mkdir -p /home/{name}/.ssh && "
|
||||
f"echo '{key}' >> /home/{name}/.ssh/authorized_keys && "
|
||||
f"chmod 700 /home/{name}/.ssh && "
|
||||
f"chmod 600 /home/{name}/.ssh/authorized_keys && "
|
||||
f"chown -R {name}:{name} /home/{name}/.ssh && "
|
||||
f"echo 'Clé ajoutée pour {name}'"
|
||||
)
|
||||
|
||||
return "Action inconnue. Disponible : list, add, delete, passwd, info, groups, addgroup, removegroup, lock, unlock, whoami, logged, sudoers, ssh-key"
|
||||
Reference in New Issue
Block a user