Kuby/Explication.md

490 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🔧 Système Décentralisé de Secours pour Kuby - Documentation Technique
---
## 📌 Contexte
Kuby est un bot Discord critique pour les serveurs affiliés à Omega Kube. Pour éviter toute interruption de service en cas de crash du serveur principal, nous mettons en place un **système décentralisé de secours** où plusieurs **nœuds de confiance** (ordinateurs personnels de membres sélectionnés) peuvent héberger et lancer Kuby automatiquement.
**Objectif** : **Zéro temps d'arrêt**, même si le serveur principal est hors ligne.
---
## 🏗️ Architecture du Système
### **1. Composants Clés**
| Composant | Rôle | Emplacement |
|-----------|------|--------------|
| **Fichier de Statut Partagé** (`kuby_status.json`) | Stocke l'état du système (dernier heartbeat, nœud actif, etc.) | **GitHub Gist** (gratuit, accessible en lecture/écriture) |
| **Nœud Principal** | Instance principale de Kuby (sur ton serveur) | Ton VPS/serveur actuel |
| **Nœuds de Secours** | Instances de Kuby sur les PCs des membres de confiance | Ordinateurs personnels |
| **Script de Surveillance** (`monitor.py`) | Vérifie le heartbeat et déclenche la bascule | Exécuté par chaque nœud |
| **Script de Lancement** (`launch_kuby.py`) | Lance Kuby en mode actif/secours | Exécuté par chaque nœud |
---
### **2. Flux de Données**
```
┌─────────────────────────────────────────────────────────────────────────┐
│ GitHub Gist (kuby_status.json) │
│ { │
│ "last_heartbeat": "2024-10-15T14:00:00Z", │
│ "active_node": "node_principal", │
│ "active_node_ip": "123.45.67.89", │
│ "version": "1.2.0", │
│ "mode": "normal" // ou "secours" │
│ } │
└─────────────────────────────────────────────────────────────────────────┘
┌───────────────┴───────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Nœud Principal │ │ Nœud de Secours │
│ (Ton serveur) │ │ (PC d'un membre) │
│ │ │ │
│ 1. Exécute Kuby │ │ 1. Exécute monitor │
│ 2. Met à jour │ │ 2. Vérifie │
│ heartbeat/5min │ │ kuby_status.json│
│ 3. Si crash → │ │ 3. Si last_heartbeat│
│ ne fait rien │ │ > 10 min ET │
│ │ │ active_node != │
│ │ │ "moi" → │
│ │ │ Prend le relais │
└─────────────────────┘ └─────────────────────┘
```
---
### **3. Rôles des Fichiers**
#### **📄 `kuby_status.json` (Sur GitHub Gist)**
Stocke l'état global du système. **Tous les nœuds lisent et écrivent dans ce fichier.**
**Structure** :
```json
{
"last_heartbeat": "2024-10-15T14:00:00Z", // Dernière mise à jour par le nœud actif
"active_node": "node_principal", // ID du nœud actuellement actif
"active_node_name": "Kuby (Serveur Principal)", // Nom du nœud (pour les logs)
"active_node_discord_id": "123456789", // ID Discord du bot (pour éviter les conflits)
"version": "1.2.0", // Version de Kuby en cours
"mode": "normal", // "normal" ou "secours"
"emergency_message": null // Message d'urgence si panne totale
}
```
#### **🐍 `monitor.py` (À exécuter par chaque nœud)**
**Rôle** :
- Vérifie toutes les **5 minutes** si le nœud actif (`active_node`) a mis à jour `last_heartbeat`.
- Si **aucune mise à jour depuis 10+ minutes**, le nœud **prend le relais**.
**Logique** :
```python
while True:
status = fetch_status_from_gist()
last_heartbeat = datetime.fromisoformat(status["last_heartbeat"])
if (now - last_heartbeat).total_seconds() > 600: # 10 minutes
if status["active_node"] != MY_NODE_ID:
take_over(status) # Prend le relais
if status["active_node"] == MY_NODE_ID:
update_heartbeat() # Met à jour last_heartbeat
sleep(300) # Attend 5 minutes
```
#### **🚀 `launch_kuby.py` (Lance Kuby en mode actif/secours)**
**Rôle** :
- Lance le bot Kuby avec les **bonnes permissions** (mode normal ou secours).
- **Met à jour `active_node`** dans `kuby_status.json` pour indiquer qu'il est actif.
**Cas d'usage** :
- **Nœud principal** : Lance Kuby normalement.
- **Nœud de secours** : Lance Kuby **seulement si `active_node == MY_NODE_ID`**.
---
## 🔄 Mécanisme de Bascule (Failover)
### **1. Détection d'un Crash**
- Un **nœud de secours** détecte que `last_heartbeat` a **plus de 10 minutes**.
- Il vérifie que **`active_node` != son ID** (pour éviter les conflits).
- Il **met à jour `kuby_status.json`** :
```json
{
"last_heartbeat": "2024-10-15T14:10:00Z",
"active_node": "node_secours_1",
"active_node_name": "Kuby (PC de [Membre])",
"active_node_discord_id": "987654321",
"mode": "secours",
"emergency_message": "Le serveur principal est down. Ce nœud a pris le relais."
}
```
- Il **lance Kuby** en mode actif.
### **2. Notification aux Propriétaires**
Le nœud de secours envoie :
1. Un **message dans le serveur hub** (canal `#🚨-urgent-kuby`) :
```
🚨 **BASCULE AUTOMATIQUE ACTIVÉE** 🚨
⚠️ Le serveur principal de Kuby est hors ligne.
🛡️ **Nouveau nœud actif** : Kuby (PC de [Membre])
⏰ **Heure de bascule** : [HH:MM]
📌 **Action** : Aucun redémarrage manuel nécessaire.
```
2. Un **DM à tous les propriétaires** :
```
⚠️ **Kuby a basculé sur un nœud de secours**
Le serveur principal est temporairement indisponible.
🔄 **Nouveau nœud actif** : PC de [Membre]
💬 **Pas d'action requise** : Le service reste opérationnel.
```
### **3. Retour à la Normale**
- Le **nœud principal** revient en ligne.
- Il détecte que `active_node != "node_principal"`.
- Il **attend 5 minutes** (pour éviter les oscillations).
- Il **reprend le contrôle** en :
1. Mettant à jour `kuby_status.json` :
```json
{
"active_node": "node_principal",
"mode": "normal",
"emergency_message": null
}
```
2. **Arrêtant le nœud de secours** (via un message Discord ou un appel API).
3. Envoyant un **message de rétablissement** :
```
✅ **Kuby est de retour sur le serveur principal**
🎉 Merci au nœud de secours pour son aide !
```
---
## 🛠️ Implémentation Technique (Disnake)
### **1. Prérequis**
- **Python 3.8+**
- **Disnake** (déjà utilisé dans le projet)
- **Bibliothèques supplémentaires** :
```bash
pip install aiohttp python-dotenv
```
### **2. Fichier `.env` (Pour chaque nœud)**
```env
# Identifiant unique du nœud (ex: node_principal, node_secours_1, node_secours_2)
NODE_ID=node_secours_1
NODE_NAME=Kuby (PC de [Prénom])
NODE_DISCORD_BOT_TOKEN=ton_token_bot_discord
# GitHub Gist
GIST_ID=ton_id_gist
GITHUB_TOKEN=ton_token_github # Optionnel (pour éviter les rate limits)
# Discord
SERVEUR_HUB_ID=123456789
CANAL_URGENT_ID=987654321
# Délais (en secondes)
HEARTBEAT_INTERVAL=300 # 5 minutes
CRASH_THRESHOLD=600 # 10 minutes
```
### **3. Structure des Fichiers**
```
/kuby_decentralise
├── monitor.py # Surveillance du heartbeat
├── launch_kuby.py # Lancement de Kuby
├── status_manager.py # Gestion de kuby_status.json
├── config.py # Configuration commune
├── .env # Variables d'environnement
└── README.md # Instructions
```
---
### **4. Exemple de Code (Pseudocode)**
#### **📄 `status_manager.py`**
```python
import aiohttp
import os
from dotenv import load_dotenv
load_dotenv()
GIST_URL = f"https://gist.githubusercontent.com/{os.getenv('GITHUB_USER')}/{os.getenv('GIST_ID')}/raw/kuby_status.json"
GIST_API_URL = f"https://api.github.com/gists/{os.getenv('GIST_ID')}"
async def fetch_status():
async with aiohttp.ClientSession() as session:
async with session.get(GIST_URL) as response:
if response.status == 200:
return await response.json()
else:
raise Exception(f"Erreur {response.status} : Impossible de récupérer kuby_status.json")
async def update_status(new_status: dict):
headers = {"Authorization": f"token {os.getenv('GITHUB_TOKEN')}"} if os.getenv('GITHUB_TOKEN') else {}
data = {
"files": {
"kuby_status.json": {
"content": json.dumps(new_status, indent=2)
}
}
}
async with aiohttp.ClientSession() as session:
async with session.patch(GIST_API_URL, json=data, headers=headers) as response:
if response.status != 200:
raise Exception(f"Erreur {response.status} : Impossible de mettre à jour kuby_status.json")
return await response.json()
```
#### **📄 `monitor.py`**
```python
import asyncio
from datetime import datetime, timedelta
from status_manager import fetch_status, update_status
from launch_kuby import launch_kuby
import os
NODE_ID = os.getenv("NODE_ID")
NODE_NAME = os.getenv("NODE_NAME")
NODE_DISCORD_ID = os.getenv("NODE_DISCORD_BOT_TOKEN") # À adapter
async def take_over():
"""Prend le relais si le nœud actif est down."""
status = await fetch_status()
now = datetime.utcnow().isoformat() + "Z"
# Vérifier si on peut prendre le relais
last_heartbeat = datetime.fromisoformat(status["last_heartbeat"])
if (datetime.utcnow() - last_heartbeat) < timedelta(minutes=10):
return False # Pas besoin de prendre le relais
if status["active_node"] == NODE_ID:
return False # Déjà actif
# Mettre à jour le statut
status["last_heartbeat"] = now
status["active_node"] = NODE_ID
status["active_node_name"] = NODE_NAME
status["active_node_discord_id"] = NODE_DISCORD_ID
status["mode"] = "secours"
status["emergency_message"] = f"Bascule automatique : {NODE_NAME} a pris le relais."
await update_status(status)
return True
async def monitor():
while True:
try:
status = await fetch_status()
last_heartbeat = datetime.fromisoformat(status["last_heartbeat"])
now = datetime.utcnow()
# Si le nœud actif est down depuis 10+ min, prendre le relais
if (now - last_heartbeat) > timedelta(minutes=10) and status["active_node"] != NODE_ID:
if await take_over():
print(f"[{NODE_NAME}] Prend le relais !")
await launch_kuby() # Lance Kuby
# Si ce nœud est actif, mettre à jour le heartbeat
if status["active_node"] == NODE_ID:
status["last_heartbeat"] = now.isoformat() + "Z"
await update_status(status)
except Exception as e:
print(f"[ERREUR] {e}")
await asyncio.sleep(300) # Attend 5 minutes
if __name__ == "__main__":
asyncio.run(monitor())
```
---
## 🔐 Sécurité et Bonnes Pratiques
### **1. Gestion des Conflits**
- **Problème** : Deux nœuds de secours pourraient essayer de prendre le relais en même temps.
- **Solution** : Utiliser un **verrou optimiste** dans `kuby_status.json` :
```json
{
"lock": null, // ou l'ID du nœud qui tente de prendre le relais
"lock_timestamp": null
}
```
- Un nœud ne peut prendre le relais que si `lock` est `null` ou a **plus de 5 minutes**.
### **2. Authentification**
- **Problème** : Un malware pourrait modifier `kuby_status.json`.
- **Solution** :
- Utiliser un **token GitHub** pour les mises à jour.
- **Signer les requêtes** avec une clé secrète (optionnel).
### **3. Liste des Nœuds de Confiance**
Maintenir une **liste blanchie** dans `kuby_status.json` :
```json
{
"trusted_nodes": [
{"id": "node_principal", "name": "Kuby (Serveur)", "discord_id": "123456789"},
{"id": "node_secours_1", "name": "Kuby (PC de Alice)", "discord_id": "987654321"},
{"id": "node_secours_2", "name": "Kuby (PC de Bob)", "discord_id": "567891234"}
]
}
```
- Un nœud ne peut prendre le relais que s'il est dans cette liste.
---
## 📊 Exemple de Workflow Complet
### **Scénario 1 : Maintenance Planifiée**
1. **Toi** exécutes `!maintenance start 72h "Mise à jour majeure"` sur le **nœud principal**.✅
2. Le nœud principal met à jour `kuby_status.json` :
```json
{
"maintenance": {
"date": "2024-10-18T14:00:00Z",
"raison": "Mise à jour majeure"
}
}
```
3. Les nœuds de secours **détectent la maintenance** et n'interviennent pas.
### **Scénario 2 : Crash du Serveur Principal**
1. Le **nœud principal** crash → `last_heartbeat` n'est plus mis à jour.
2. Après **10 minutes**, un **nœud de secours** (ex: PC d'Alice) détecte le problème.
3. Alice's node **met à jour `kuby_status.json`** et lance Kuby.
4. Un **message est envoyé** dans `#🚨-urgent-kuby` et aux propriétaires.
5. Quand le nœud principal revient, il **reprend le contrôle** automatiquement.
### **Scénario 3 : Panne Totale (Tous les nœuds down)**
1. **Aucun nœud** ne met à jour `last_heartbeat`.
2. **UptimeRobot** (optionnel) détecte que le bot Discord ne répond plus.
3. UptimeRobot envoie un **webhook dans `#🚨-urgent-kuby`** :
```
🚨 **PANNE TOTALE DE KUBY** 🚨
Tous les nœuds sont hors ligne !
@OmegaKube **Action manuelle requise**.
```
---
## 🛑 Limitations et Solutions
| Limitation | Cause | Solution |
|------------|-------|----------|
| **Délai de bascule** (~10 min) | Temps de détection du crash | Réduire `CRASH_THRESHOLD` à 5 min (mais risque de fausses alertes). |
| **PC éteint** | Un nœud de secours est hors ligne | Avoir **au moins 3 nœuds de confiance**. |
| **GitHub down** | Impossible d'accéder à `kuby_status.json` | Utiliser un **mirror sur Google Drive** ou **IPFS**. |
| **Conflits de nœuds** | Deux nœuds prennent le relais en même temps | Implémenter un **verrou optimiste** (voir section Sécurité). |
| **Données non synchronisées** | Un nœud a des données différentes | Utiliser une **base de données partagée** (Firebase gratuit). |
---
## 📝 Étapes de Déploiement
### **1. Préparation**
- [ ] Créer un **GitHub Gist** avec le `kuby_status.json` initial.
- [ ] Sélectionner **2-3 membres de confiance** pour héberger des nœuds de secours.
- [ ] Leur fournir :
- Le code (`monitor.py`, `launch_kuby.py`, etc.).
- Un **token Discord** pour leur instance de Kuby (chaque nœud a son propre bot).
- Un **fichier `.env`** pré-rempli avec leurs identifiants.
### **2. Configuration des Nœuds**
Pour chaque membre :
1. Cloner le dépôt du projet Kuby.
2. Installer les dépendances (`pip install -r requirements.txt`).
3. Remplir leur `.env` avec leurs informations.
4. **Tester le nœud** :
```bash
python monitor.py
```
### **3. Test du Système**
1. **Simuler un crash** :
- Arrêter le nœud principal.
- Vérifier qu'un nœud de secours prend le relais après 10 min.
2. **Simuler un retour** :
- Redémarrer le nœud principal.
- Vérifier qu'il reprend le contrôle.
---
## 💬 Communication avec les Nœuds de Secours
### **Message à Envoyer aux Membre Hébergeurs**
```
Bonjour [Prénom],
Tu as été sélectionné(e) pour faire partie des **nœuds de secours de Kuby** !
Cela signifie que ton PC peut héberger une copie de Kuby en cas de panne du serveur principal.
📌 **Ce que tu dois faire** :
1. Installer Python 3.8+ sur ton PC.
2. Exécuter le script **monitor.py** en permanence (en arrière-plan).
3. **Ne pas éteindre ton PC** trop souvent (ou prévenir si tu dois le faire).
💡 **Ce qui se passe en cas de panne** :
- Si le serveur principal de Kuby crash, ton PC **démarrera automatiquement** une copie de secours.
- Tu nas **rien à faire** : tout est automatique !
- Quand le serveur principal revient, la copie de secours **séteint toute seule**.
⚠️ **Important** :
- Ton PC doit être **connecté à Internet** pour que ça fonctionne.
- Tu auras un **token Discord** dédié pour ton nœud (ne le partage pas).
- En cas de problème, contacte @OmegaKube.
Merci pour ton aide ! 🚀
```
---
## 📞 Support et Dépannage
### **Problèmes Courants**
| Problème | Cause | Solution |
|----------|-------|----------|
| `monitor.py` ne se connecte pas à GitHub | Mauvais token GitHub | Vérifier `.env` et régénérer le token. |
| Kuby ne se lance pas sur le nœud de secours | Permissions Discord manquantes | Vérifier que le bot a les permissions `Send Messages`, `Embed Links`. |
| Conflit entre deux nœuds | Problème de verrouillage | Redémarrer les deux nœuds et vérifier `kuby_status.json`. |
| `last_heartbeat` n'est pas mis à jour | Erreur de réseau | Vérifier la connexion Internet et les logs. |
### **Commandes Utiles**
```bash
# Vérifier les logs de monitor.py
python monitor.py
# Forcer la prise de relais (pour tester)
python monitor.py --force-takeover
# Mettre à jour manuellement le heartbeat
python monitor.py --update-heartbeat
```
---
## 🔮 Améliorations Futures
- [ ] **Base de données partagée** (Firebase) pour stocker les données des serveurs.
- [ ] **Système de vote** : Si plusieurs nœuds détectent un crash, ils votent pour élire le nouveau nœud actif.
- [ ] **Notifications push** (via Telegram ou email) pour les admins en cas de bascule.
- [ ] **Tableau de bord** pour surveiller l'état de tous les nœuds.
---
## 📚 Ressources
- [Documentation Disnake](https://docs.disnake.dev/)
- [GitHub Gist API](https://docs.github.com/en/rest/gists/gists?apiVersion=2022-11-28)
- [Python asyncio](https://docs.python.org/3/library/asyncio.html)
---
*Documentation technique - Version 1.0 - Dernière mise à jour : 15/10/2024*