Kuby/Explication.md

19 KiB
Raw Blame History

🔧 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 :

{
  "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 :

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 :
    {
      "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 :
      {
        "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 :
    pip install aiohttp python-dotenv
    

2. Fichier .env (Pour chaque nœud)

# 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

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

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 :
    {
      "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 :

{
  "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 :
    {
      "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 :
    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

# 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 technique - Version 1.0 - Dernière mise à jour : 15/10/2024