# 🔧 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*