426 lines
16 KiB
JavaScript
426 lines
16 KiB
JavaScript
const { exec } = require('child_process');
|
|
const { promisify } = require('util');
|
|
const execAsync = promisify(exec);
|
|
const fs = require('fs').promises;
|
|
|
|
class ServerControlManager {
|
|
constructor() {
|
|
this.serverPath = process.env.MINECRAFT_SERVER_PATH || '/opt/minecraft/server';
|
|
this.serviceName = process.env.MINECRAFT_SERVICE_NAME || 'minecraft';
|
|
this.screenName = 'minecraft'; // Nom de la session screen
|
|
this.serverUser = 'minecraft'; // Utilisateur qui lance le serveur
|
|
this.whitelistPath = process.env.MINECRAFT_WHITELIST_PATH;
|
|
this.useScreen = process.env.USE_SCREEN === 'true'; // true pour screen, false pour systemctl
|
|
|
|
// Détecter le type de serveur et le fichier JAR
|
|
this.serverJar = this.detectServerJar();
|
|
this.javaArgs = process.env.JAVA_ARGS || '-Xmx2G -Xms1G';
|
|
}
|
|
|
|
detectServerJar() {
|
|
// Ordre de priorité pour détecter le fichier JAR
|
|
const possibleJars = [
|
|
'fabric-server.jar',
|
|
'fabric-server-launch.jar',
|
|
'server.jar',
|
|
'forge-server.jar',
|
|
'paper.jar',
|
|
'spigot.jar'
|
|
];
|
|
|
|
// Pour l'instant, on retourne fabric-server.jar puisque c'est ce que vous utilisez
|
|
// Plus tard, on pourrait faire une détection automatique
|
|
return process.env.SERVER_JAR || 'fabric-server.jar';
|
|
}
|
|
|
|
async executeCommand(command, description = '') {
|
|
try {
|
|
console.log(`🔧 Exécution: ${description || command}`);
|
|
const { stdout, stderr } = await execAsync(command);
|
|
|
|
// Log des détails pour debug
|
|
if (stdout) console.log(`📤 Sortie: ${stdout.trim()}`);
|
|
if (stderr && !stderr.includes('Warning')) {
|
|
console.warn(`⚠️ Erreur stderr: ${stderr.trim()}`);
|
|
return { success: false, error: stderr.trim(), output: stdout };
|
|
}
|
|
|
|
console.log(`✅ Succès: ${description || command}`);
|
|
return { success: true, output: stdout, error: stderr };
|
|
} catch (error) {
|
|
console.error(`❌ Erreur: ${description || command} - ${error.message}`);
|
|
return { success: false, error: error.message, output: '' };
|
|
}
|
|
}
|
|
|
|
async reloadWhitelist() {
|
|
console.log('🔄 Rechargement de la whitelist...');
|
|
|
|
if (this.useScreen) {
|
|
// Méthode screen : commande directe
|
|
const result = await this.executeCommand(
|
|
`sudo -u ${this.serverUser} screen -S ${this.screenName} -p 0 -X stuff "whitelist reload\n"`,
|
|
'Rechargement whitelist via commande'
|
|
);
|
|
|
|
if (result.success) {
|
|
return { success: true, message: 'Whitelist rechargée via commande serveur' };
|
|
}
|
|
}
|
|
|
|
// Fallback systemctl : redémarrage
|
|
const restartResult = await this.executeCommand(
|
|
`sudo systemctl restart ${this.serviceName}`,
|
|
'Redémarrage pour appliquer la whitelist'
|
|
);
|
|
|
|
if (restartResult.success) {
|
|
return { success: true, message: 'Whitelist rechargée (serveur redémarré)' };
|
|
}
|
|
|
|
return { success: false, message: 'Impossible de recharger la whitelist' };
|
|
}
|
|
|
|
async getServerStatus() {
|
|
if (this.useScreen) {
|
|
// Vérifier si la session screen existe (nom exact ou suffixé)
|
|
const screenResult = await this.executeCommand(
|
|
`screen -ls | grep -w "${this.screenName}"`,
|
|
'Vérification session screen'
|
|
);
|
|
const screenRunning = screenResult.success && screenResult.output.trim() !== '';
|
|
|
|
// Double vérification : chercher le processus Java avec le bon jar
|
|
const javaResult = await this.executeCommand(
|
|
`pgrep -f "${this.serverJar}" | wc -l`,
|
|
'Vérification processus Java Fabric'
|
|
);
|
|
const javaRunning = javaResult.success && parseInt(javaResult.output.trim()) > 0;
|
|
|
|
return {
|
|
running: screenRunning && javaRunning,
|
|
enabled: true,
|
|
status: screenRunning ?
|
|
(javaRunning ? 'Running in screen with Java process' : 'Screen session exists but no Java process') :
|
|
'No screen session found',
|
|
details: {
|
|
screen: screenRunning,
|
|
java: javaRunning,
|
|
screenOutput: screenResult.output.trim(),
|
|
javaOutput: javaResult.output.trim()
|
|
}
|
|
};
|
|
}
|
|
|
|
// Méthode systemctl avec vérification plus précise
|
|
const result = await this.executeCommand(
|
|
`sudo systemctl is-active ${this.serviceName}`,
|
|
'Vérification active systemctl'
|
|
);
|
|
|
|
const isActive = result.success && result.output.trim() === 'active';
|
|
|
|
const enabledResult = await this.executeCommand(
|
|
`sudo systemctl is-enabled ${this.serviceName}`,
|
|
'Vérification enabled systemctl'
|
|
);
|
|
const isEnabled = enabledResult.success && enabledResult.output.trim() === 'enabled';
|
|
|
|
return {
|
|
running: isActive,
|
|
enabled: isEnabled,
|
|
status: `Active: ${isActive ? 'active' : result.output.trim()}, Enabled: ${isEnabled ? 'enabled' : enabledResult.output.trim()}`
|
|
};
|
|
}
|
|
|
|
async startServer() {
|
|
if (this.useScreen) {
|
|
// Vérifier d'abord si le serveur n'est pas déjà lancé
|
|
const status = await this.getServerStatus();
|
|
if (status.running) {
|
|
return {
|
|
success: false,
|
|
message: 'Le serveur est déjà en cours d\'exécution'
|
|
};
|
|
}
|
|
|
|
// Forcer le nom de la session screen à "minecraft"
|
|
const result = await this.executeCommand(
|
|
`sudo -u ${this.serverUser} screen -dmS ${this.screenName} bash -c "cd ${this.serverPath} && java ${this.javaArgs} -jar ${this.serverJar} nogui"`,
|
|
'Démarrage du serveur avec screen (nom forcé)'
|
|
);
|
|
|
|
if (result.success) {
|
|
// Attendre un peu et vérifier que ça a vraiment démarré
|
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
const newStatus = await this.getServerStatus();
|
|
|
|
return {
|
|
success: newStatus.running,
|
|
message: newStatus.running ? 'Serveur démarré avec screen' : `Échec démarrage: ${result.error || 'Processus non trouvé'}`
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: false,
|
|
message: `Erreur: ${result.error}`
|
|
};
|
|
}
|
|
|
|
// Méthode systemctl avec vérification
|
|
const result = await this.executeCommand(
|
|
`sudo systemctl start ${this.serviceName}`,
|
|
'Démarrage du serveur'
|
|
);
|
|
|
|
if (result.success) {
|
|
// Vérifier que ça a vraiment démarré
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
const status = await this.getServerStatus();
|
|
|
|
return {
|
|
success: status.running,
|
|
message: status.running ? 'Serveur démarré avec succès' : `Échec: ${status.status}`
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: false,
|
|
message: `Erreur: ${result.error}`
|
|
};
|
|
}
|
|
|
|
async stopServer() {
|
|
if (this.useScreen) {
|
|
// Arrêter proprement avec la commande stop
|
|
const stopResult = await this.executeCommand(
|
|
`sudo -u ${this.serverUser} screen -S ${this.screenName} -p 0 -X stuff "stop\n"`,
|
|
'Arrêt propre du serveur'
|
|
);
|
|
|
|
// Attendre un peu puis forcer si nécessaire
|
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
|
|
const killResult = await this.executeCommand(
|
|
`sudo -u ${this.serverUser} screen -S ${this.screenName} -X quit`,
|
|
'Fermeture session screen'
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Serveur arrêté'
|
|
};
|
|
}
|
|
|
|
// Méthode systemctl
|
|
const result = await this.executeCommand(
|
|
`sudo systemctl stop ${this.serviceName}`,
|
|
'Arrêt du serveur'
|
|
);
|
|
|
|
return {
|
|
success: result.success,
|
|
message: result.success ? 'Serveur arrêté avec succès' : `Erreur: ${result.error}`
|
|
};
|
|
}
|
|
|
|
async restartServer() {
|
|
if (this.useScreen) {
|
|
// Arrêter puis redémarrer avec screen
|
|
console.log('🔄 Arrêt du serveur...');
|
|
await this.stopServer();
|
|
|
|
// Attendre un peu
|
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
|
|
console.log('🔄 Redémarrage du serveur...');
|
|
return await this.startServer();
|
|
}
|
|
|
|
// Méthode systemctl
|
|
const result = await this.executeCommand(
|
|
`sudo systemctl restart ${this.serviceName}`,
|
|
'Redémarrage du serveur'
|
|
);
|
|
|
|
return {
|
|
success: result.success,
|
|
message: result.success ? 'Serveur redémarré avec succès' : `Erreur: ${result.error}`
|
|
};
|
|
}
|
|
|
|
async getServerLogs(lines = 50) {
|
|
const result = await this.executeCommand(
|
|
`sudo journalctl -u ${this.serviceName} -n ${lines} --no-pager`,
|
|
`Récupération des ${lines} dernières lignes de logs`
|
|
);
|
|
|
|
return {
|
|
success: result.success,
|
|
logs: result.output,
|
|
error: result.error
|
|
};
|
|
}
|
|
|
|
async sendServerCommand(command) {
|
|
if (this.useScreen) {
|
|
// Envoyer directement via screen
|
|
const result = await this.executeCommand(
|
|
`sudo -u ${this.serverUser} screen -S ${this.screenName} -p 0 -X stuff "${command}\n"`,
|
|
`Envoi commande: ${command}`
|
|
);
|
|
|
|
return {
|
|
success: result.success,
|
|
message: result.success ? `Commande "${command}" envoyée` : `Erreur: ${result.error}`
|
|
};
|
|
}
|
|
|
|
// Pour systemctl, impossible d'envoyer des commandes directes
|
|
return {
|
|
success: false,
|
|
message: 'Envoi de commandes non supporté avec systemctl (utilisez screen)'
|
|
};
|
|
}
|
|
|
|
async backupWorld() {
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
const backupPath = `/opt/minecraft/backups/world_${timestamp}.tar.gz`;
|
|
|
|
const result = await this.executeCommand(
|
|
`sudo mkdir -p /opt/minecraft/backups && sudo tar -czf ${backupPath} -C ${this.serverPath} world`,
|
|
'Sauvegarde du monde'
|
|
);
|
|
|
|
return {
|
|
success: result.success,
|
|
message: result.success ? `Monde sauvegardé: ${backupPath}` : `Erreur: ${result.error}`,
|
|
backupPath: result.success ? backupPath : null
|
|
};
|
|
}
|
|
|
|
async getWhitelistFromFile() {
|
|
try {
|
|
const data = await fs.readFile(this.whitelistPath, 'utf8');
|
|
return JSON.parse(data);
|
|
} catch (error) {
|
|
console.error('Erreur lecture whitelist:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async saveWhitelistToFile(whitelist) {
|
|
try {
|
|
await fs.writeFile(this.whitelistPath, JSON.stringify(whitelist, null, 2));
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Erreur écriture whitelist:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async getServerProperties() {
|
|
try {
|
|
const propsPath = `${this.serverPath}/server.properties`;
|
|
const data = await fs.readFile(propsPath, 'utf8');
|
|
|
|
const properties = {};
|
|
data.split('\n').forEach(line => {
|
|
if (line.trim() && !line.startsWith('#')) {
|
|
const [key, value] = line.split('=');
|
|
if (key && value !== undefined) {
|
|
properties[key.trim()] = value.trim();
|
|
}
|
|
}
|
|
});
|
|
|
|
return { success: true, properties };
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async updateServerProperty(key, value) {
|
|
try {
|
|
const propsPath = `${this.serverPath}/server.properties`;
|
|
let data = await fs.readFile(propsPath, 'utf8');
|
|
|
|
// Remplacer ou ajouter la propriété
|
|
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
if (regex.test(data)) {
|
|
data = data.replace(regex, `${key}=${value}`);
|
|
} else {
|
|
data += `\n${key}=${value}\n`;
|
|
}
|
|
|
|
await fs.writeFile(propsPath, data);
|
|
return { success: true, message: `Propriété ${key} mise à jour: ${value}` };
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async setupServerForBot() {
|
|
console.log('🔧 Configuration automatique du serveur pour le bot...');
|
|
const results = [];
|
|
|
|
// Vérifier si l'utilisateur minecraft existe déjà
|
|
const userCheck = await this.executeCommand(
|
|
'id minecraft 2>/dev/null',
|
|
'Vérification utilisateur minecraft'
|
|
);
|
|
|
|
if (!userCheck.success && this.useScreen) {
|
|
const userResult = await this.executeCommand(
|
|
'sudo useradd -r -s /bin/bash minecraft',
|
|
'Création utilisateur minecraft'
|
|
);
|
|
results.push(userResult.success ? '✅ Utilisateur minecraft créé' : '❌ Échec création utilisateur');
|
|
} else if (this.useScreen) {
|
|
results.push('✅ Utilisateur minecraft existe déjà');
|
|
}
|
|
|
|
// Vérifier et créer les dossiers
|
|
try {
|
|
await fs.access(this.serverPath);
|
|
results.push('✅ Dossier serveur existe');
|
|
} catch (error) {
|
|
const dirResult = await this.executeCommand(
|
|
`sudo mkdir -p ${this.serverPath}`,
|
|
'Création dossier serveur'
|
|
);
|
|
results.push(dirResult.success ? '✅ Dossier serveur créé' : '❌ Échec création dossier');
|
|
}
|
|
|
|
// Configuration permissions si screen
|
|
if (this.useScreen) {
|
|
const permResult = await this.executeCommand(
|
|
`sudo chown -R minecraft:minecraft /opt/minecraft`,
|
|
'Configuration permissions'
|
|
);
|
|
results.push(permResult.success ? '✅ Permissions configurées' : '⚠️ Échec permissions');
|
|
}
|
|
|
|
// Configuration server.properties
|
|
const whitelistResult = await this.updateServerProperty('white-list', 'true');
|
|
const enforceResult = await this.updateServerProperty('enforce-whitelist', 'true');
|
|
|
|
results.push(whitelistResult.success ? '✅ Whitelist activée' : '⚠️ Échec whitelist');
|
|
results.push(enforceResult.success ? '✅ Enforce-whitelist activé' : '⚠️ Échec enforce-whitelist');
|
|
|
|
// Création dossier backups
|
|
const backupResult = await this.executeCommand(
|
|
'sudo mkdir -p /opt/minecraft/backups',
|
|
'Création dossier backups'
|
|
);
|
|
results.push(backupResult.success ? '✅ Dossier backups créé' : '⚠️ Échec dossier backups');
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Configuration terminée',
|
|
details: results
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = ServerControlManager; |