Compare commits

..

6 Commits

Author SHA1 Message Date
sylvain 0fe1ece68d Support agents distants (SSH) pour !agentUPDATE/UPGRADE
- agent_update.py : _run_ssh() via sshpass, dispatche local ou SSH selon ssh_host
- agent1.py : _get_agent_git_info() transmet ssh_host/ssh_user depuis le registre
- agents_registry.json : agent2_test → ssh_host: 192.168.7.13, ssh_user: root

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 16:11:43 +00:00
sylvain 1565b145dc !agentUPDATE/UPGRADE : fallback automatique sur /opt/<nom> si install_path absent
_get_agent_git_info déduit le chemin depuis le nom de l'agent par convention
(/opt/<nom>, service <nom>) au lieu de bloquer avec une erreur.
!agentsUPDATE/UPGRADE couvre tous les agents du registre sans filtre.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 16:06:14 +00:00
sylvain 3575b391b6 Ajout !agentUPDATE/UPGRADE : mises à jour agents depuis git
- skills/agent_update.py : check_update (git fetch + log) et do_upgrade (git pull + systemctl restart)
- agent1.py : commandes !agentUPDATE <nom>, !agentsUPDATE, !agentUPGRADE <nom>, !agentsUPGRADE
  - _handle_agent_command retourne (handled, reply) pour gérer le self-upgrade agent1
  - !agentUPGRADE agent1 : envoie la réponse XMPP avant systemctl restart
  - !agentsUPGRADE : met à jour tous les agents puis agent1 en dernier
- agents_registry.json : ajout install_path, service_name, git_branch + entrée agent1
- README.md : documentation des nouvelles commandes
- TODO.md : tâches marquées comme terminées

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 15:55:31 +00:00
sylvain 1c951f46f1 TODO : ajout gestion mises à jour agents (git update/upgrade) 2026-03-08 15:49:21 +00:00
sylvain 37e881bc39 README : documentation complète de toutes les commandes 2026-03-08 15:45:08 +00:00
sylvain d765a8457a TODO.md : marquer les tâches batch 1-3 comme terminées 2026-03-08 15:42:48 +00:00
5 changed files with 648 additions and 124 deletions
+207 -54
View File
@@ -2,38 +2,221 @@
Agent principal du réseau. Il reçoit les instructions de l'utilisateur (via XMPP ou CLI MQTT), les analyse avec un LLM, et délègue les tâches aux agents spécialisés.
## Rôle
---
- Point d'entrée unique pour l'utilisateur (`sylvain@xmpp.ovh`)
- Délègue aux agents spécialisés via MQTT (`DELEGATE: <agent> | <tâche>`)
- Planifie des tâches récurrentes (`SCHEDULE:`)
- Centralise les rapports et erreurs des agents
- Maintient un registre des agents disponibles
## Commandes XMPP (sylvain@xmpp.ovh → agent1)
## Skills disponibles
### Contrôle des agents
| Trigger | Description |
| Commande | Effet |
|---|---|
| `SEARCH: <requête>` | Recherche DuckDuckGo |
| `READ: <url>` | Lecture de page web |
| `REMEMBER: <clé> \| <valeur>` | Mémoire SQLite |
| `RECALL: <clé>` | Récupération mémoire |
| `MQTT_PUBLISH: <topic> \| <msg>` | Publication MQTT |
| `DELEGATE: <agent> \| <tâche>` | Délégation à un agent spécialisé |
| `PLAN: <agent>\|<tâche> ;; <agent>\|<tâche>` | Plan multi-étapes |
| `SCHEDULE: <cron> \| <agent> \| <tâche>` | Planification |
| `PLAN_LIST:` | Lister les tâches planifiées |
| `PLAN_CANCEL: <id>` | Annuler une planification |
| `REPORT:` | Rapport d'exécutions |
| `REPORT_ERRORS:` | Rapport des erreurs |
| `!agentsOFF` | Agent1 en veille + pause de tous les agents |
| `!agentsON` | Agent1 actif + reprise de tous les agents |
| `!agentOFF <nom>` | Pause d'un agent spécifique (ex: `!agentOFF agent2_debian13`) |
| `!agentON <nom>` | Reprise d'un agent spécifique |
| `!agentOFF agent1` | Agent1 en veille uniquement (les autres agents continuent) |
| `!agentON agent1` | Agent1 sort de veille |
> En mode veille, agent1 reste connecté XMPP et répond uniquement aux commandes `!agentON`.
### Mises à jour git
| Commande | Effet |
|---|---|
| `!agentUPDATE <nom>` | Vérifie si une mise à jour est disponible sur le dépôt git de l'agent |
| `!agentsUPDATE` | Vérifie les dépôts de tous les agents enregistrés |
| `!agentUPGRADE <nom>` | `git pull` + `systemctl restart` de l'agent |
| `!agentsUPGRADE` | `git pull` + restart de tous les agents (agent1 en dernier) |
> `!agentUPGRADE agent1` redémarre agent1 lui-même via systemd. La réponse XMPP est envoyée avant le redémarrage.
### Affichage des configurations
| Commande | Effet |
|---|---|
| `!reports` | Afficher `reports_schedule.json` (horaires des rapports) |
| `!tasks` | Afficher `tasks_schedule.json` (tâches planifiées) |
| `!blackout` | Afficher `blackout_hours.json` (plages d'inactivité) |
### Divers
| Commande | Effet |
|---|---|
| `!reset` | Réinitialiser l'historique de conversation |
---
## Skills LLM (utilisables en XMPP ou CLI)
Ces commandes sont générées par le LLM en réponse à une instruction en langage naturel.
### Délégation et planification
| Trigger | Syntaxe | Description |
|---|---|---|
| `DELEGATE:` | `DELEGATE: <agent> \| <tâche>` | Délègue une tâche à un agent (avec vérif. plage horaire + blackout) |
| `PLAN:` | `PLAN: <agent>\|<tâche> ;; <agent>\|<tâche>` | Exécute plusieurs tâches en séquence |
| `SCHEDULE:` | `SCHEDULE: <cron> \| <agent> \| <tâche>` | Planifie une tâche récurrente |
| `PLAN_LIST:` | `PLAN_LIST:` | Lister les tâches planifiées |
| `PLAN_CANCEL:` | `PLAN_CANCEL: <id>` | Annuler une planification |
**Expressions cron supportées :**
```
daily 03:00 → tous les jours à 03h00
every 6h → toutes les 6 heures
every 30min → toutes les 30 minutes
weekly lun 08:00 → chaque lundi à 08h00
```
### Rapports
| Trigger | Syntaxe | Description |
|---|---|---|
| `REPORT:` | `REPORT: [agent]` | Rapport des 20 dernières exécutions |
| `REPORT_ERRORS:` | `REPORT_ERRORS: [agent]` | Rapport des 20 dernières erreurs |
| `DAILY_REPORT:` | `DAILY_REPORT: [agent]` | Rapport journalier compilé (stats de tous les agents) |
### Mémoire et recherche
| Trigger | Syntaxe | Description |
|---|---|---|
| `SEARCH:` | `SEARCH: <requête>` | Recherche DuckDuckGo |
| `READ:` | `READ: <url>` | Lecture de page web |
| `REMEMBER:` | `REMEMBER: <clé> \| <valeur>` | Mémoriser une information (SQLite) |
| `RECALL:` | `RECALL: <clé>` | Récupérer une information mémorisée |
### MQTT direct
| Trigger | Syntaxe | Description |
|---|---|---|
| `MQTT_PUBLISH:` | `MQTT_PUBLISH: <topic> \| <message>` | Publier un message MQTT |
| `MQTT_SUBSCRIBE:` | `MQTT_SUBSCRIBE: <topic>` | Écouter un topic MQTT |
---
## CLI (`cli.py`)
```bash
python3 /opt/agent/cli.py # parler à agent1
python3 /opt/agent/cli.py agent2_debian13 # accès direct à un agent
python3 /opt/agent/cli.py --plans # voir les planifications
```
**Commandes dans la CLI :**
| Commande | Effet |
|---|---|
| `/reset` | Réinitialiser la conversation |
| `/plans` | Lister les tâches planifiées |
| `/report` | Rapport des dernières exécutions |
| `/errors` | Rapport des erreurs |
| `/agent <nom>` | Basculer vers un autre agent |
| `/quit` | Quitter la CLI |
---
## Fichiers de configuration
| Fichier | Rôle | Modifié par |
|---|---|---|
| `config/config.json` | Paramètres XMPP, MQTT, Ollama | Utilisateur |
| `config/agents_registry.json` | Registre des agents (auto-mis à jour) | Agent1 au démarrage des agents |
| `config/blackout_hours.json` | Plages horaires d'inactivité totale | **Utilisateur directement** |
| `config/reports_schedule.json` | Horaires de sollicitation des rapports | Agent1 (avec confirmation) |
| `config/tasks_schedule.json` | Tâches planifiées par agent | Agent1 (avec confirmation) |
### `blackout_hours.json`
Aucun agent ne travaille pendant ces plages, quelle que soit la planification :
```json
[
{ "start": "02:00", "end": "05:00", "label": "maintenance nuit", "enabled": true }
]
```
### `reports_schedule.json`
```json
{
"agents": {
"agent2_debian13": { "report_time": "22:00", "enabled": true },
"agent2_ansible": { "report_time": "22:05", "enabled": true },
"agent2_deploy": { "report_time": "22:10", "enabled": true }
},
"daily_report_time": "22:30",
"daily_report_enabled": true
}
```
### `tasks_schedule.json`
```json
{
"tasks": [
{
"agent": "agent2_debian13",
"task": "apt update && apt upgrade -y",
"cron": "weekly lun 03:00",
"enabled": true
}
]
}
```
### `agents_registry.json` — champ `work_hours`
Plage horaire de travail par agent (vérifiée avant chaque DELEGATE) :
```json
"work_hours": {
"start": "07:00",
"end": "23:00",
"days": ["mon","tue","wed","thu","fri","sat","sun"],
"enabled": true
}
```
---
## Topics MQTT
| Topic | Direction | Rôle |
|---|---|---|
| `agents/agent1/inbox` | CLI / agents → agent1 | Tâches pour agent1 |
| `agents/<name>/inbox` | Agent1 → agent2_* | Tâches pour les agents |
| `agents/<name>/control` | Agent1 → agent2_* | Pause / Resume / Report |
| `agents/daily_report` | Agent2_* → agent1 | Rapports journaliers |
| `agents/errors` | Agent2_* → agent1 | Erreurs (notif XMPP) |
| `agents/register` | Agent2_* → agent1 | Enregistrement au démarrage |
| `agents/status/<name>` | Agent2_* → agent1 | Statut en ligne / hors ligne (LWT) |
| `agents/cli/outbox` | Agent1 → CLI | Réponses vers la CLI |
| `agents/scheduler/notifications` | Scheduler → agent1 | Notifications planificateur |
---
## Lancer les agents
```bash
# Agent1
python3 /opt/agent/agent1.py > /tmp/agent1.log 2>&1 &
# Agents spécialisés
python3 /opt/agent2_debian13/agent2_debian13.py > /tmp/agent2_debian13.log 2>&1 &
python3 /opt/agent2_ansible/agent2_ansible.py > /tmp/agent2_ansible.log 2>&1 &
python3 /opt/agent2_deploy/agent2_deploy.py > /tmp/agent2_deploy.log 2>&1 &
# Via systemd
systemctl start agent agent2_debian13 agent2_ansible agent2_deploy
```
## Logs
```bash
journalctl -u agent -f
journalctl -u agent2_debian13 -f
```
---
## Déploiement manuel
```bash
# Prérequis
apt-get install -y python3 python3-pip python3-venv git mosquitto
# Cloner et installer
git clone https://git.piaf.im/sylvain/agent1.git /opt/agent
cd /opt/agent
python3 -m venv venv
@@ -44,35 +227,5 @@ cp config/config.json.example config/config.json # adapter les valeurs
# Service systemd
cp agent.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable agent
systemctl start agent
```
## Configuration (`config/config.json`)
```json
{
"ollama_url" : "http://<ip_ollama>:11434/api/chat",
"model" : "qwen3:8b",
"xmpp_jid" : "agent1@xmpp.ovh",
"xmpp_pass" : "<mot_de_passe>",
"admin_jid" : "sylvain@xmpp.ovh",
"mqtt_host" : "localhost",
"mqtt_port" : 1883
}
```
## CLI
```bash
python3 /opt/agent/cli.py # parler à agent1
python3 /opt/agent/cli.py agent2_debian13 # accès direct à un agent
python3 /opt/agent/cli.py --plans # voir les planifications
```
## Logs
```bash
journalctl -u agent -f
systemctl daemon-reload && systemctl enable agent && systemctl start agent
```
+82 -50
View File
@@ -3,81 +3,61 @@
## 1. Fichiers de configuration (dans `/opt/agent/config/`)
### `reports_schedule.json` _(géré par agent1 avec confirmation utilisateur)_
- [ ] Créer le fichier avec horaires de sollicitation des rapports par agent
- [ ] Agent1 : commande XMPP pour modifier ce fichier (confirmation avant écriture)
- [ ] Agent1 : planifier les demandes de rapport selon ce fichier (APScheduler)
- [ ] Chaque agent2_* : répondre à la demande de rapport avec ses stats locales
- [x] Créer le fichier avec horaires de sollicitation des rapports par agent
- [x] Agent1 : commande !reports pour afficher, PENDING_CONFIG pour confirmation modification
- [x] Agent1 : planifier les demandes de rapport selon ce fichier (APScheduler)
- [x] Chaque agent2_* : répondre à la demande de rapport avec ses stats locales
### `tasks_schedule.json` _(géré par agent1 avec confirmation utilisateur)_
- [ ] Créer le fichier avec les tâches planifiées par agent
- [ ] Agent1 : commande XMPP pour ajouter/modifier/supprimer une tâche (confirmation avant écriture)
- [ ] Agent1 : charger ce fichier au démarrage et planifier les tâches via APScheduler
- [ ] Remplacer / compléter l'actuel `SCHEDULE:` skill
- [x] Créer le fichier avec les tâches planifiées par agent
- [x] Agent1 : !tasks pour afficher, PENDING_CONFIG pour modification avec confirmation
- [x] Agent1 : charger ce fichier au démarrage et planifier les tâches via APScheduler
### `blackout_hours.json` _(édité directement par l'utilisateur)_
- [ ] Créer le fichier avec plage(s) horaire(s) de blackout (aucun agent ne travaille)
- [ ] Format : `[{"start": "02:00", "end": "05:00", "label": "maintenance"}]`
- [ ] Agent1 : vérifier ce fichier avant chaque délégation de tâche
- [ ] Agent1 : vérifier ce fichier avant chaque tâche planifiée
- [x] Créer le fichier avec plage(s) horaire(s) de blackout (aucun agent ne travaille)
- [x] Agent1 : vérifier ce fichier avant chaque délégation de tâche (delegate.py)
---
## 2. Plages horaires par agent (`work_hours` dans `agents_registry.json`)
- [ ] Ajouter `work_hours: {start, end, days}` pour chaque agent dans `agents_registry.json`
- [ ] Modifier `skills/delegate.py` : vérifier plage horaire avant d'envoyer la tâche
- [ ] Si hors plage → retourner message d'indisponibilité (pas d'exécution)
- [ ] Prendre en compte le blackout_hours.json également dans delegate.py
- [x] Ajouter `work_hours: {start, end, days}` pour chaque agent dans `agents_registry.json`
- [x] Modifier `skills/delegate.py` : vérifier plage horaire avant d'envoyer la tâche
- [x] Si hors plage → retourner message d'indisponibilité (pas d'exécution)
- [x] Prendre en compte le blackout_hours.json également dans delegate.py
---
## 3. File d'attente locale par agent (SQLite)
### Pour chaque agent2_* (`/opt/agent2_*/queue.db`)
- [ ] Créer table `tasks_queue(id, received_at, started_at, completed_at, task, status, result, duration_s)`
- [ ] `on_mqtt_message()` : sauvegarder immédiatement le message en base (status: `pending`)
- [ ] Worker FIFO dans un thread séparé : traiter les tâches une par une
- [ ] Si paused : worker s'arrête, tâches s'accumulent en base
- [ ] Au resume : worker reprend depuis les tâches `pending`
- [ ] MQTT : passer à `clean_session=False` + `QoS=1` pour ne pas perdre les messages offline
- [x] Créer table `tasks_queue(id, received_at, started_at, completed_at, task, status, result, duration_s)`
- [x] `on_mqtt_message()` : sauvegarder immédiatement le message en base (status: `pending`)
- [x] Worker FIFO dans un thread séparé : traiter les tâches une par une
- [x] Si paused : worker s'arrête, tâches s'accumulent en base
- [x] Au resume : worker reprend depuis les tâches `pending`
- [x] MQTT : passer à `clean_session=False` + `QoS=1` pour ne pas perdre les messages offline
---
## 4. Mode pause / veille par agent
### Nouveau topic MQTT : `agents/<name>/control`
- [ ] Chaque agent2_* : s'abonner à `agents/<name>/control`
- [ ] Payload `{"command": "pause"}` → flag `self.paused = True`, stopper le worker
- [ ] Payload `{"command": "resume"}` → flag `self.paused = False`, relancer le worker
- [ ] Agent1 en veille : rester connecté XMPP, n'accepter que `!agentsON` / `!agentON agent1`
### Topic MQTT : `agents/<name>/control`
- [x] Chaque agent2_* : s'abonner à `agents/<name>/control`
- [x] pause → worker stoppé, resume → worker relancé
- [x] Agent1 en veille : n'accepte que `!agentsON` / `!agentON agent1`
### Commandes XMPP (sylvain → agent1)
- [ ] `!agentOFF <nom>` → envoyer pause à l'agent ciblé
- [ ] `!agentON <nom>` → envoyer resume à l'agent ciblé
- [ ] `!agentsOFF` → agent1 en veille + pause à tous les agent2_*
- [ ] `!agentsON` → agent1 sort de veille + resume à tous les agent2_*
- [ ] `!agentOFF agent1` → agent1 en veille uniquement
- [ ] Mettre à jour `agents_online.json` à chaque changement d'état
- [x] `!agentOFF <nom>` / `!agentON <nom>`
- [x] `!agentsOFF` / `!agentsON`
- [x] `!agentOFF agent1` / `!agentON agent1` (veille agent1 uniquement)
---
## 5. Rapports journaliers
### Chaque agent2_* (stats locales)
- [ ] Tracker en mémoire : `tasks_total`, `tasks_success`, `tasks_error`, `avg_duration_s`, `uptime_s`, `last_error`
- [ ] Alimenter ces stats depuis la `queue.db`
- [ ] Répondre à la demande de rapport d'agent1 via MQTT (`agents/<name>/control` payload `{"command": "report"}`)
- [ ] Publier le rapport sur `agents/daily_report` avec toutes les stats
### Agent1 (compilation)
- [ ] S'abonner à `agents/daily_report`
- [ ] Stocker les rapports reçus en mémoire + SQLite (`daily_reports` table dans `executions.db`)
- [ ] Nouveau skill `skills/daily_report.py` : trigger `DAILY_REPORT:`
- [ ] Compiler les rapports de tous les agents
- [ ] Formater un résumé lisible
- [ ] Envoyer à `sylvain@xmpp.ovh` via XMPP
- [ ] Stocker pour historique
- [ ] Planifier `DAILY_REPORT:` dans `tasks_schedule.json` (ex: 22:30)
- [ ] Disponible aussi à la demande : `DAILY_REPORT:` depuis CLI
- [x] Chaque agent2_* : stats depuis queue.db, envoi sur `agents/daily_report`
- [x] Agent1 : souscription `agents/daily_report`, stockage en mémoire
- [x] `skills/daily_report.py` : DAILY_REPORT: [agent]
- [x] APScheduler : sollicitation agents à 22:00/22:05/22:10, rapport journalier à 22:30
---
@@ -92,6 +72,58 @@
---
---
## 6. Gestion des mises à jour depuis les dépôts git
### Commandes XMPP (sylvain → agent1)
| Commande | Effet |
|---|---|
| `!agentUPDATE <nom>` | Vérifie si une mise à jour est disponible sur le dépôt git de l'agent (git fetch + comparaison) |
| `!agentsUPDATE` | Vérifie les dépôts de tous les agents enregistrés |
| `!agentUPGRADE <nom>` | Tire les modifications (git pull) + redémarre le service systemd de l'agent |
| `!agentsUPGRADE` | Git pull + restart de tous les agents |
### Comportement attendu
- `!agentUPDATE` : affiche le résultat de `git fetch` + `git log HEAD..origin/main --oneline`
- Réponse : "Mise à jour disponible : X commit(s)" ou "Déjà à jour"
- `!agentUPGRADE` :
1. `git pull origin main` dans le répertoire de l'agent
2. `systemctl restart <service_name>` pour relancer le service
3. Confirmation ou erreur envoyée via XMPP
4. Attendre que le service soit de nouveau EN LIGNE (statut MQTT) avant de confirmer
### Informations nécessaires par agent
Ajouter dans `agents_registry.json` :
```json
"agent2_debian13": {
"install_path" : "/opt/agent2_debian13",
"service_name" : "agent2_debian13",
"git_branch" : "main"
}
```
### Fichiers à modifier / créer
| Fichier | Action |
|---|---|
| `config/agents_registry.json` | Ajouter `install_path`, `service_name`, `git_branch` par agent |
| `agent1.py` | Gérer `!agentUPDATE/UPGRADE` et `!agentsUPDATE/UPGRADE` |
| `skills/agent_update.py` | **Créer** — logique git fetch/pull + systemctl restart |
### Points d'attention
- [x] Exécuter `git` et `systemctl` via subprocess
- [x] Timeout sur les commandes git/systemctl
- [x] `!agentUPGRADE agent1` : git pull + réponse XMPP envoyée avant `systemctl restart agent`
- [x] Gérer le cas où `install_path` n'est pas dans le registre
- [x] `!agentUPDATE` suggère `!agentUPGRADE` si commits disponibles
---
## Fichiers impactés
| Fichier | Action |
+135 -15
View File
@@ -137,10 +137,11 @@ def _get_all_agents() -> list:
return []
# ── COMMANDES !agent ──────────────────────────────────────────────────────
def _handle_agent_command(text: str) -> str | None:
def _handle_agent_command(text: str) -> tuple:
"""
Gère les commandes !agentON/OFF et !agentsON/OFF.
Retourne la réponse ou None si ce n'est pas une commande agent.
Gère toutes les commandes !agent*.
Retourne (handled: bool, reply: str|None).
reply=None signifie que la réponse XMPP a déjà été envoyée (ex: self-upgrade).
"""
global SLEEP_MODE
@@ -152,7 +153,7 @@ def _handle_agent_command(text: str) -> str | None:
for a in agents:
_send_control(a, "pause")
SLEEP_MODE = True
return "[VEILLE] Agent1 en veille. {} agent(s) mis en pause.\nEnvoyez !agentsON ou !agentON agent1 pour reprendre.".format(len(agents))
return True, "[VEILLE] Agent1 en veille. {} agent(s) mis en pause.\nEnvoyez !agentsON ou !agentON agent1 pour reprendre.".format(len(agents))
# !agentsON — sortie veille agent1 + resume tous les agents
if t == "!agentsON":
@@ -160,27 +161,145 @@ def _handle_agent_command(text: str) -> str | None:
for a in agents:
_send_control(a, "resume")
SLEEP_MODE = False
return "[ACTIF] Agent1 actif. {} agent(s) relancés.".format(len(agents))
return True, "[ACTIF] Agent1 actif. {} agent(s) relancés.".format(len(agents))
# !agentOFF <nom>
if t.startswith("!agentOFF "):
name = t[len("!agentOFF "):].strip()
if name == "agent1":
SLEEP_MODE = True
return "[VEILLE] Agent1 en veille. Envoyez !agentON agent1 pour reprendre."
return True, "[VEILLE] Agent1 en veille. Envoyez !agentON agent1 pour reprendre."
_send_control(name, "pause")
return "[PAUSE] Commande pause envoyée à {}.".format(name)
return True, "[PAUSE] Commande pause envoyée à {}.".format(name)
# !agentON <nom>
if t.startswith("!agentON "):
name = t[len("!agentON "):].strip()
if name == "agent1":
SLEEP_MODE = False
return "[ACTIF] Agent1 actif."
return True, "[ACTIF] Agent1 actif."
_send_control(name, "resume")
return "[ACTIF] Commande resume envoyée à {}.".format(name)
return True, "[ACTIF] Commande resume envoyée à {}.".format(name)
return None
# !agentsUPDATE — vérifier les mises à jour de tous les agents
if t == "!agentsUPDATE":
return True, _handle_update_all()
# !agentUPDATE <nom>
if t.startswith("!agentUPDATE "):
name = t[len("!agentUPDATE "):].strip()
return True, _handle_update_one(name)
# !agentsUPGRADE — mettre à jour tous les agents
if t == "!agentsUPGRADE":
return True, _handle_upgrade_all()
# !agentUPGRADE <nom>
if t.startswith("!agentUPGRADE "):
name = t[len("!agentUPGRADE "):].strip()
return True, _handle_upgrade_one(name)
return False, None
# ── MISE À JOUR DEPUIS GIT ───────────────────────────────────────────────
def _get_agent_git_info(name: str) -> dict:
"""
Retourne {install_path, service_name, git_branch} pour un agent.
Priorité : registre → convention /opt/<nom> (fallback automatique).
"""
try:
registry = json.loads(REGISTRY_FILE.read_text(encoding="utf-8"))
agent = registry.get(name, {})
except Exception:
agent = {}
# Fallback : déduire depuis le nom de l'agent (convention /opt/<nom>)
# agent1 est un cas particulier : /opt/agent, service "agent"
if name == "agent1":
default_path = "/opt/agent"
default_service = "agent"
else:
default_path = "/opt/{}".format(name)
default_service = name
return {
"install_path": agent.get("install_path", default_path),
"service_name": agent.get("service_name", default_service),
"git_branch" : agent.get("git_branch", "main"),
"ssh_host" : agent.get("ssh_host"),
"ssh_user" : agent.get("ssh_user", "root"),
}
def _handle_update_one(name: str) -> str:
from skills.agent_update import check_update
info = _get_agent_git_info(name)
return check_update(name, info["install_path"], info["git_branch"],
info["ssh_host"], info["ssh_user"])
def _handle_update_all() -> str:
from skills.agent_update import check_update
try:
registry = json.loads(REGISTRY_FILE.read_text(encoding="utf-8"))
except Exception:
return "Erreur lecture registre."
results = []
for name in registry:
info = _get_agent_git_info(name)
results.append(check_update(name, info["install_path"], info["git_branch"],
info["ssh_host"], info["ssh_user"]))
return "\n\n".join(results) if results else "Aucun agent dans le registre."
def _handle_upgrade_one(name: str) -> str:
from skills.agent_update import do_upgrade
info = _get_agent_git_info(name)
self_upgrade = (name == "agent1")
msg = do_upgrade(name, info["install_path"], info["service_name"],
info["git_branch"], self_upgrade=self_upgrade,
ssh_host=info["ssh_host"], ssh_user=info["ssh_user"])
if self_upgrade and "Redémarrage en cours" in msg:
# Envoyer le message XMPP avant le restart
if xmpp_bot:
xmpp_bot.send_message(mto=ADMIN_JID, mbody=msg, mtype='chat')
import subprocess
subprocess.Popen(["systemctl", "restart", info["service_name"]])
return None # Réponse déjà envoyée manuellement
return msg
def _handle_upgrade_all() -> str:
from skills.agent_update import do_upgrade
try:
registry = json.loads(REGISTRY_FILE.read_text(encoding="utf-8"))
except Exception:
return "Erreur lecture registre."
results = []
agent1_info = None
for name in registry:
info = _get_agent_git_info(name)
if name == "agent1":
agent1_info = (name, info) # traiter en dernier
continue
msg = do_upgrade(name, info["install_path"],
info["service_name"], info["git_branch"],
ssh_host=info["ssh_host"], ssh_user=info["ssh_user"])
results.append(msg)
summary = "\n\n".join(results) if results else "Aucun agent mis à jour."
if agent1_info:
name, info = agent1_info
pull_msg = do_upgrade(name, info["install_path"],
info["service_name"], info["git_branch"],
self_upgrade=True)
summary += "\n\n" + pull_msg
if xmpp_bot:
xmpp_bot.send_message(mto=ADMIN_JID, mbody=summary, mtype='chat')
import subprocess
subprocess.Popen(["systemctl", "restart", info.get("service_name", "agent")])
return None # Réponse déjà envoyée
return summary
# ── GESTION CONFIGS AVEC CONFIRMATION ────────────────────────────────────
def _handle_config_command(text: str) -> str | None:
@@ -495,11 +614,12 @@ class AgentBot(ClientXMPP):
user_input = msg['body'].strip()
# ── Commandes !agentON/OFF (prioritaires, toujours traitées) ──────
agent_reply = _handle_agent_command(user_input)
if agent_reply is not None:
self.send_message(mto=ADMIN_JID, mbody=agent_reply, mtype='chat')
return
# ── Commandes !agent* (prioritaires, toujours traitées) ──────────
handled, agent_reply = _handle_agent_command(user_input)
if handled:
if agent_reply is not None:
self.send_message(mto=ADMIN_JID, mbody=agent_reply, mtype='chat')
return # None = réponse déjà envoyée manuellement (ex: self-upgrade)
# ── Mode veille : ignorer tout sauf commandes agent ───────────────
if SLEEP_MODE:
+65 -5
View File
@@ -4,26 +4,86 @@
"mqtt_inbox": "agents/agent2_debian13/inbox",
"mqtt_outbox": "agents/agent1/inbox",
"speciality": "Administration Debian : apt, dpkg, systemd, conteneurs LXC/Docker, KVM, réseau, sécurité système",
"work_hours": { "start": "07:00", "end": "23:00", "days": ["mon","tue","wed","thu","fri","sat","sun"], "enabled": true }
"work_hours": {
"start": "07:00",
"end": "23:00",
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"enabled": true
},
"install_path": "/opt/agent2_debian13",
"service_name": "agent2_debian13",
"git_branch": "main"
},
"agent2_ansible": {
"jid": "agent2_ansible@xmpp.ovh",
"mqtt_inbox": "agents/agent2_ansible/inbox",
"mqtt_outbox": "agents/agent1/inbox",
"speciality": "Automatisation infrastructure via Ansible : playbooks, commandes ad-hoc, déploiement multi-hôtes, gestion de configuration sur le réseau local",
"work_hours": { "start": "07:00", "end": "23:00", "days": ["mon","tue","wed","thu","fri","sat","sun"], "enabled": true }
"work_hours": {
"start": "07:00",
"end": "23:00",
"days": [
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun"
],
"enabled": true
},
"install_path": "/opt/agent2_ansible",
"service_name": "agent2_ansible",
"git_branch": "main"
},
"agent2_deploy": {
"jid": "agent2_deploy@xmpp.ovh",
"mqtt_inbox": "agents/agent2_deploy/inbox",
"mqtt_outbox": "agents/agent1/inbox",
"speciality": "Déploiement d'agents : installe et configure d'autres agents sur des machines distantes ou locales via SSH",
"work_hours": { "start": "08:00", "end": "20:00", "days": ["mon","tue","wed","thu","fri"], "enabled": true }
"work_hours": {
"start": "08:00",
"end": "20:00",
"days": [
"mon",
"tue",
"wed",
"thu",
"fri"
],
"enabled": true
},
"install_path": "/opt/agent2_deploy",
"service_name": "agent2_deploy",
"git_branch": "main"
},
"agent2_test": {
"jid": "agent2_test@xmpp.ovh",
"mqtt_inbox": "agents/agent2_test/inbox",
"mqtt_outbox": "agents/agent1/inbox",
"speciality": "Spécialiste Debian : apt, systemd, conteneurs LXC/Docker, KVM, réseau, sécurité"
"speciality": "Administration Debian : apt, dpkg, systemd, conteneurs LXC/Docker, KVM, réseau, sécurité système",
"install_path": "/opt/agent2_test",
"service_name": "agent2_test",
"git_branch": "main",
"ssh_host": "192.168.7.13",
"ssh_user": "root"
},
"agent1": {
"jid": "agent1@xmpp.ovh",
"mqtt_inbox": "agents/agent1/inbox",
"mqtt_outbox": "agents/agent1/inbox",
"speciality": "Orchestrateur principal",
"install_path": "/opt/agent",
"service_name": "agent",
"git_branch": "main"
}
}
}
+159
View File
@@ -0,0 +1,159 @@
"""
Utilitaire : vérification et application des mises à jour git pour les agents.
Fonctions appelées directement depuis agent1.py (pas de trigger LLM).
check_update(name, install_path, branch, ssh_host, ssh_user)
do_upgrade(name, install_path, service, branch, ssh_host, ssh_user, self_upgrade)
Si ssh_host est défini, les commandes sont exécutées via SSH sur la machine distante.
"""
import subprocess
import shlex
from pathlib import Path
def _run_local(cmd: str, cwd: str = None, timeout: int = 30) -> tuple:
"""Lance une commande locale, retourne (stdout, stderr, returncode)."""
try:
result = subprocess.run(
shlex.split(cmd),
cwd=cwd,
capture_output=True,
text=True,
timeout=timeout
)
return result.stdout.strip(), result.stderr.strip(), result.returncode
except subprocess.TimeoutExpired:
return "", "Timeout ({} s)".format(timeout), -1
except Exception as e:
return "", str(e), -1
def _run_ssh(cmd: str, ssh_host: str, ssh_user: str = "root",
cwd: str = None, timeout: int = 30) -> tuple:
"""Lance une commande via SSH, retourne (stdout, stderr, returncode)."""
if cwd:
cmd = "cd {} && {}".format(cwd, cmd)
ssh_cmd = "sshpass -p 'Matador3721' ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 {}@{} {}".format(
ssh_user, ssh_host, shlex.quote(cmd))
try:
result = subprocess.run(
shlex.split(ssh_cmd),
capture_output=True,
text=True,
timeout=timeout
)
return result.stdout.strip(), result.stderr.strip(), result.returncode
except subprocess.TimeoutExpired:
return "", "Timeout SSH ({} s)".format(timeout), -1
except Exception as e:
return "", str(e), -1
def _run(cmd: str, cwd: str = None, timeout: int = 30,
ssh_host: str = None, ssh_user: str = "root") -> tuple:
"""Dispatche local ou SSH selon ssh_host."""
if ssh_host:
return _run_ssh(cmd, ssh_host, ssh_user, cwd=cwd, timeout=timeout)
return _run_local(cmd, cwd=cwd, timeout=timeout)
def check_update(agent_name: str, install_path: str, branch: str = "main",
ssh_host: str = None, ssh_user: str = "root") -> str:
"""
Vérifie si une mise à jour est disponible sur le dépôt distant.
Effectue un git fetch puis compare HEAD avec origin/<branch>.
"""
remote = " [{}]".format(ssh_host) if ssh_host else ""
# Vérifier que le répertoire existe
if ssh_host:
out, _, rc = _run("test -d {}".format(install_path),
ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0:
return "[{}{}] Répertoire introuvable : {}".format(
agent_name, remote, install_path)
else:
if not Path(install_path).is_dir():
return "[{}] Répertoire introuvable : {}".format(agent_name, install_path)
# git fetch
out, err, rc = _run("git fetch origin {}".format(branch),
cwd=install_path, timeout=20,
ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0:
return "[{}{}] Erreur git fetch : {}".format(agent_name, remote, err or out)
# Compter les commits disponibles
out, err, rc = _run(
"git log HEAD..origin/{} --oneline".format(branch),
cwd=install_path, ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0:
return "[{}{}] Erreur git log : {}".format(agent_name, remote, err or out)
commits = [l for l in out.splitlines() if l.strip()]
if not commits:
return "[{}{}] Déjà à jour.".format(agent_name, remote)
lines = ["[{}{}] {} commit(s) disponible(s) :".format(
agent_name, remote, len(commits))]
for c in commits[:10]:
lines.append(" {}".format(c))
if len(commits) > 10:
lines.append(" ... et {} autre(s)".format(len(commits) - 10))
lines.append("Lancez !agentUPGRADE {} pour appliquer.".format(agent_name))
return "\n".join(lines)
def do_upgrade(agent_name: str, install_path: str,
service_name: str, branch: str = "main",
self_upgrade: bool = False,
ssh_host: str = None, ssh_user: str = "root") -> str:
"""
Applique la mise à jour : git pull + systemctl restart.
Pour agent1 (self_upgrade=True), retourne le message AVANT le restart.
"""
remote = " [{}]".format(ssh_host) if ssh_host else ""
# Vérifier que le répertoire existe
if ssh_host:
_, _, rc = _run("test -d {}".format(install_path),
ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0:
return "[{}{}] Répertoire introuvable : {}".format(
agent_name, remote, install_path)
else:
if not Path(install_path).is_dir():
return "[{}] Répertoire introuvable : {}".format(agent_name, install_path)
# git pull
out, err, rc = _run(
"git pull origin {}".format(branch),
cwd=install_path, timeout=60,
ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0:
return "[{}{}] Erreur git pull : {}".format(agent_name, remote, err or out)
pull_msg = out or "Déjà à jour."
if self_upgrade:
return "[{}] {}\nRedémarrage en cours...".format(agent_name, pull_msg)
# systemctl restart
_, err, rc = _run("systemctl restart {}".format(service_name),
timeout=15, ssh_host=ssh_host, ssh_user=ssh_user)
if rc != 0:
return "[{}{}] Pull OK mais restart échoué : {}".format(
agent_name, remote, err)
# Vérifier que le service est bien remonté
out, _, _ = _run("systemctl is-active {}".format(service_name),
timeout=5, ssh_host=ssh_host, ssh_user=ssh_user)
state = out.strip()
if state == "active":
return "[{}{}] Mise à jour appliquée. Service actif.\n{}".format(
agent_name, remote, pull_msg)
else:
return "[{}{}] Pull OK, service état : {}. Vérifiez les logs.".format(
agent_name, remote, state)