This commit is contained in:
Louis Mazin 2026-02-02 00:42:49 +01:00
parent 40c76d4872
commit 602321529c
4 changed files with 71 additions and 224 deletions

View File

@ -1,146 +0,0 @@
# 🔍 Moniteur de RAM - Palworld Server
## Description
Le moniteur de RAM surveille automatiquement l'utilisation de la mémoire du serveur Palworld. Lorsque le seuil de **19 Go** est dépassé, le serveur est automatiquement redémarré pour éviter les problèmes de performance ou de crash.
## Fonctionnalités
### Surveillance automatique
- ✅ Vérification de la RAM toutes les 60 secondes
- ✅ Seuil de déclenchement : **19 Go**
- ✅ Redémarrage automatique si le seuil est dépassé
- ✅ Resynchronisation automatique du WebSocket après redémarrage
### Processus de redémarrage
Lorsque le seuil est atteint, le système effectue les étapes suivantes :
1. 💾 **Sauvegarde** du serveur
2. 📢 **Annonce** aux joueurs (broadcast : "Redémarrage automatique - RAM élevée - 30 secondes")
3. ⏹️ **Arrêt** du serveur (signal stop)
4. ⏳ **Attente** de 25 secondes
5. 🚀 **Redémarrage** du serveur (signal start)
6. ⏳ **Vérification** jusqu'à ce que le serveur soit opérationnel
7. 🔄 **Resynchronisation** du WebSocket (après 5 secondes supplémentaires)
### Commande Discord
#### `/ram-status`
Affiche l'état actuel de la RAM du serveur.
**Permissions requises :** Rôle Rygainland
**Informations affichées :**
- 📊 Utilisation actuelle (en Go et pourcentage)
- ⚠️ Seuil de redémarrage configuré
- 🔄 État du serveur (running, stopped, etc.)
- 💡 Niveau d'alerte (Normal 🟢 / Élevé 🟡 / Critique 🔴)
- ✅ État de la surveillance automatique
**Exemple d'affichage :**
```
🟢 État de la RAM du serveur Palworld
📊 Utilisation actuelle: 15.32 Go / 19.00 Go (80.6%)
⚠️ Seuil de redémarrage: 19.00 Go
🔄 État du serveur: running
💡 Niveau: Élevée
✅ Surveillance automatique active
```
## Configuration
### Variables d'environnement requises
Le moniteur utilise les variables d'environnement suivantes (déjà configurées) :
```env
PTERODACTYL_API_URL=https://panel.louismazin.ovh
PTERODACTYL_API_TOKEN=votre_token
PTERODACTYL_SERVER_ID=ae4a628f
```
### Paramètres modifiables
Dans le fichier `ramMonitor.js` :
```javascript
const RAM_THRESHOLD_MB = 19 * 1024; // Seuil en MB (19 Go par défaut)
const CHECK_INTERVAL_MS = 60 * 1000; // Intervalle de vérification (60 secondes)
```
## Intégration
### Fichiers modifiés/créés
1. **`ramMonitor.js`** (nouveau)
- Module principal de surveillance
- Gestion du redémarrage automatique
2. **`index.js`** (modifié)
- Ajout de `startRAMMonitoring(forceWebSocketReconnect)`
- Import du module ramMonitor
3. **`consoleMonitor.js`** (modifié)
- Ajout de la fonction `forceWebSocketReconnect()`
- Export de la fonction pour utilisation par ramMonitor
4. **`commands/server/ram-status.js`** (nouveau)
- Commande Discord pour consulter l'état de la RAM
## Logs
Le moniteur affiche des logs détaillés dans la console :
```
🚀 [RAM Monitor] Démarrage de la surveillance RAM (seuil: 19 Go)
⏱️ [RAM Monitor] Intervalle de vérification: 60s
🔍 [RAM Monitor] Utilisation RAM: 15.32 Go / État: running
⚠️ [RAM Monitor] SEUIL DÉPASSÉ ! 19.54 Go > 19 Go
🔄 [RAM Monitor] Déclenchement du redémarrage automatique...
💾 [RAM Monitor] Sauvegarde du serveur...
📢 [RAM Monitor] Annonce du redémarrage...
⏹️ [RAM Monitor] Arrêt du serveur...
⏳ [RAM Monitor] Attente de 25 secondes...
🚀 [RAM Monitor] Redémarrage du serveur...
✅ [RAM Monitor] Serveur redémarré avec succès !
🔄 [RAM Monitor] Resynchronisation du WebSocket...
```
## Sécurité
- 🔒 Un seul redémarrage ne peut être en cours à la fois (protection via flag `isRebooting`)
- 🔒 Timeout maximum de 2.5 minutes pour le redémarrage
- 🔒 Vérification de l'état du serveur avant déclenchement (doit être `running`)
- 🔒 Commande Discord protégée par rôle
## Notes techniques
- Le serveur Pterodactyl fournit `memory_bytes` qui est converti en Mo puis Go
- La surveillance ne se déclenche que si le serveur est en état `running`
- Le WebSocket est resynchronisé après 5 secondes de stabilisation post-redémarrage
- Les websockets existants sont proprement fermés avant reconnexion
## Dépannage
### La surveillance ne démarre pas
- Vérifier les variables d'environnement `PTERODACTYL_API_TOKEN` et `PTERODACTYL_SERVER_ID`
- Vérifier les permissions API Pterodactyl
### Le redémarrage ne se déclenche pas
- Vérifier que le seuil est correctement configuré
- Consulter les logs pour voir l'utilisation actuelle
- Utiliser `/ram-status` pour diagnostiquer
### Le WebSocket ne se reconnecte pas
- Vérifier que `consoleMonitor.js` est bien démarré
- Les logs indiqueront les tentatives de reconnexion
## Désactivation
Pour désactiver temporairement la surveillance, commenter dans `index.js` :
```javascript
// startRAMMonitoring(forceWebSocketReconnect);
```

View File

@ -1,58 +0,0 @@
# rygain_bot
Bot Discord pour la gestion du serveur Palworld Rygainland.
## Fonctionnalités
### 🔗 Système de liaison Discord-Palworld
- Liaison automatique des comptes Discord et Palworld via code à 6 chiffres
- Gestion automatique des rôles lors de la liaison/déliaison
- Suivi des dernières connexions
### 🌉 Pont Discord-Palworld
- Messages Discord → Palworld (broadcast)
- Messages Palworld → Discord (avec avatar et pseudo Discord pour les comptes liés)
- Salon dédié : `1467491354924814411`
### 📊 Statistiques serveur
- Affichage des joueurs connectés
- Statistiques du serveur Palworld
### 🔧 Commandes disponibles
- `/lier-rygainland` - Lier votre compte Discord à Palworld
- `/delier-rygainland` - Délier votre compte
- `/lier` (Admin) - Lier manuellement un compte
- `/delier` (Admin) - Délier manuellement un compte
- `/server-stats` - Afficher les stats du serveur
- `/trad` - Traduire un message
## Installation
1. Cloner le repo
2. Copier `.env.example` vers `.env`
3. Configurer les variables d'environnement
4. Installer les dépendances : `npm install`
5. Lancer le bot : `node index.js`
## Configuration
Voir le fichier `.env.example` pour la liste complète des variables d'environnement.
Variables importantes :
- `BRIDGE_CHANNEL_ID` - ID du salon Discord pour le pont Palworld (défaut: 1467491354924814411)
- `GUILD_ID` - ID du serveur Discord
- `PALWORLD_API_TOKEN` - Token API REST de Palworld
- `PTERODACTYL_API_TOKEN` - Token API Pterodactyl
## Débogage
Le bot affiche des logs détaillés préfixés par :
- `[BRIDGE]` - Logs du pont Discord-Palworld
- `[CONSOLE]` - Logs du monitoring de la console Pterodactyl
- `[LIER-RYGAINLAND]` - Logs de la commande de liaison
Si le pont ne fonctionne pas, vérifiez :
1. Que le `BRIDGE_CHANNEL_ID` est correct dans `.env`
2. Que le bot a les permissions d'envoyer des messages et créer des webhooks dans le salon
3. Les logs pour voir si les messages sont détectés
4. Que l'API Palworld est accessible (http://play.louismazin.ovh:8212)

View File

@ -297,6 +297,7 @@ const connectWebSocket = async (pterodactylToken, serverId) => {
console.log('✅ [WEBSOCKET] WebSocket Pterodactyl connecté avec succès');
isConnecting = false;
resetReconnectBackoff();
lastHeartbeatResponse = Date.now(); // Initialiser à maintenant
console.log('🔐 [WEBSOCKET] Envoi de l\'authentification...');
ws.send(JSON.stringify({
@ -304,7 +305,14 @@ const connectWebSocket = async (pterodactylToken, serverId) => {
args: [credentials.token]
}));
});
// Handler pour le pong WebSocket natif
ws.on('pong', () => {
lastHeartbeatResponse = Date.now();
console.log('🏓 [WEBSOCKET] Pong reçu - connexion OK');
});
ws.on('message', async (data) => {
try {
const message = JSON.parse(data.toString());
@ -318,38 +326,70 @@ const connectWebSocket = async (pterodactylToken, serverId) => {
args: [null]
}));
// Configurer le heartbeat
// Configurer le heartbeat avec détection agressive
if (heartbeatInterval) clearInterval(heartbeatInterval);
if (heartbeatTimeout) clearTimeout(heartbeatTimeout);
lastHeartbeatResponse = Date.now();
let missedHeartbeats = 0;
const MAX_MISSED_HEARTBEATS = 2; // 2 heartbeats manqués = reconnexion
heartbeatInterval = setInterval(() => {
if (ws && ws.readyState === WebSocket.OPEN) {
try {
const timeSinceLastResponse = Date.now() - lastHeartbeatResponse;
// Si on a pas eu de réponse depuis plus de 30s, incrémenter le compteur
if (timeSinceLastResponse > 30000) {
missedHeartbeats++;
console.warn(`⚠️ [WEBSOCKET] Heartbeat manqué (${missedHeartbeats}/${MAX_MISSED_HEARTBEATS}). Dernière réponse: ${Math.round(timeSinceLastResponse/1000)}s`);
if (missedHeartbeats >= MAX_MISSED_HEARTBEATS) {
console.error('💔 [WEBSOCKET] Trop de heartbeats manqués! Reconnexion forcée...');
if (ws) {
ws.terminate(); // Fermeture immédiate sans attendre
}
return;
}
} else {
// Reset le compteur si on a reçu une réponse récemment
if (missedHeartbeats > 0) {
console.log('✅ [WEBSOCKET] Heartbeat de retour à la normale');
missedHeartbeats = 0;
}
}
// Envoyer le heartbeat
ws.send(JSON.stringify({ event: 'send heartbeat', args: [] }));
console.log('💓 [WEBSOCKET] Heartbeat envoyé');
// Configurer un timeout pour détecter si le serveur ne répond plus
if (heartbeatTimeout) clearTimeout(heartbeatTimeout);
heartbeatTimeout = setTimeout(() => {
const timeSinceLastResponse = Date.now() - lastHeartbeatResponse;
if (timeSinceLastResponse > 60000) { // 1 minute sans réponse
console.error('💔 [WEBSOCKET] Timeout: aucune réponse du serveur depuis 1 minute');
console.log('🔄 [WEBSOCKET] Fermeture et reconnexion du WebSocket...');
if (ws) {
ws.close();
}
}
}, 60000);
// Aussi envoyer un ping WebSocket natif pour vérifier la connexion TCP
if (ws.ping) {
ws.ping();
console.log('🏓 [WEBSOCKET] Ping natif envoyé');
}
} catch (error) {
console.error('❌ [WEBSOCKET] Erreur lors de l\'envoi du heartbeat:', error.message);
missedHeartbeats++;
if (missedHeartbeats >= MAX_MISSED_HEARTBEATS && ws) {
console.error('💔 [WEBSOCKET] Erreurs répétées, reconnexion forcée...');
ws.terminate();
}
}
} else {
console.warn('⚠️ [WEBSOCKET] Heartbeat impossible: WebSocket non ouvert (état:', ws ? ws.readyState : 'null', ')');
missedHeartbeats++;
if (missedHeartbeats >= MAX_MISSED_HEARTBEATS) {
console.error('💔 [WEBSOCKET] WebSocket fermé, nettoyage...');
if (heartbeatInterval) {
clearInterval(heartbeatInterval);
heartbeatInterval = null;
}
}
}
}, 30000);
console.log('💓 [WEBSOCKET] Heartbeat configuré (30s avec timeout de 60s)');
}, 20000); // Vérifier toutes les 20s au lieu de 30s
console.log('💓 [WEBSOCKET] Heartbeat configuré (20s, max 2 manqués)');
}
// Détecter les réponses du serveur (console output, status, etc) pour mettre à jour le lastHeartbeatResponse

View File

@ -65,8 +65,10 @@ const sendToPalworld = async (palworldUsername, content) => {
// Format du message pour Palworld
const broadcastMessage = `[Discord] ${palworldUsername}: ${cleanContent}`;
// Envoyer via l'API Palworld
await axios({
console.log(`🚀 [BRIDGE] Envoi HTTP vers Palworld: ${broadcastMessage}`);
// Envoyer via l'API Palworld avec timeout
const response = await axios({
method: 'post',
url: 'http://play.louismazin.ovh:8212/v1/api/announce',
headers: {
@ -76,13 +78,22 @@ const sendToPalworld = async (palworldUsername, content) => {
},
data: {
message: broadcastMessage
}
},
timeout: 10000 // Timeout de 10s
});
console.log(`Message envoyé à Palworld: ${broadcastMessage}`);
console.log(`[BRIDGE] Message envoyé à Palworld (statut ${response.status}): ${broadcastMessage}`);
} catch (error) {
console.error('Erreur lors de l\'envoi vers Palworld:', error.message);
if (error.code === 'ECONNABORTED') {
console.error('❌ [BRIDGE] Timeout lors de l\'envoi vers Palworld (10s)');
} else if (error.code === 'ECONNREFUSED') {
console.error('❌ [BRIDGE] Connexion refusée par le serveur Palworld');
} else if (error.response) {
console.error(`❌ [BRIDGE] Erreur HTTP ${error.response.status}:`, error.response.data);
} else {
console.error('❌ [BRIDGE] Erreur lors de l\'envoi vers Palworld:', error.message);
}
throw error;
}
};