490 lines
19 KiB
Markdown
490 lines
19 KiB
Markdown
# 🔧 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 n’as **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*
|