From 1fdd8cd3a36ea179bc11050d98201aaff9735845 Mon Sep 17 00:00:00 2001 From: Cap Date: Thu, 26 Feb 2026 11:17:11 +0100 Subject: [PATCH] server.js aktualisiert --- server.js | 229 ++++++++++++++++++++++++++---------------------------- 1 file changed, 109 insertions(+), 120 deletions(-) diff --git a/server.js b/server.js index fa309b3..40a087c 100644 --- a/server.js +++ b/server.js @@ -1,167 +1,156 @@ 'use strict'; +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +// ── GameDig v4+ benötigt destructured import ────────────────────────────────── const { GameDig } = require('gamedig'); -const http = require('http'); -const fs = require('fs'); -const path = require('path'); -// ─── Konfiguration ──────────────────────────────────────────────────────────── -const CONFIG_PATH = path.join('/home/container', 'config.json'); -const PORT = parseInt(process.env.SERVER_PORT || '3000', 10); -const INTERVAL_SEC = parseInt(process.env.QUERY_INTERVAL || '60', 10); -const API_KEY = process.env.API_KEY || ''; +// ── Konfiguration ───────────────────────────────────────────────────────────── +const CONFIG_PATH = path.join('/home/container', 'config.json'); +const PORT = parseInt(process.env.SERVER_PORT || process.env.PORT || 21337, 10); +const QUERY_INTERVAL = Math.max(10, parseInt(process.env.QUERY_INTERVAL || '60', 10)) * 1000; +const API_KEY = process.env.API_KEY || ''; -// ─── Config laden ───────────────────────────────────────────────────────────── -let config; +// ── Config laden ────────────────────────────────────────────────────────────── +let servers = []; try { - config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); - console.log(`[INIT] config.json geladen – ${config.servers.length} Server konfiguriert.`); + const raw = fs.readFileSync(CONFIG_PATH, 'utf8'); + const cfg = JSON.parse(raw); + servers = cfg.servers || []; + console.log(`[INIT] config.json geladen – ${servers.length} Server konfiguriert.`); } catch (e) { - console.error(`[FEHLER] config.json konnte nicht gelesen werden: ${e.message}`); - console.error(`[FEHLER] Bitte config.json im Pterodactyl-Dateimanager anlegen.`); + console.error(`[FEHLER] config.json konnte nicht geladen werden: ${e.message}`); process.exit(1); } -// ─── Cache ──────────────────────────────────────────────────────────────────── +// ── Status-Cache ────────────────────────────────────────────────────────────── let cache = { - updated: null, - interval_seconds: INTERVAL_SEC, - servers: [] + updated: null, + interval_seconds: QUERY_INTERVAL / 1000, + servers: servers.map(s => ({ + label: s.label, + type: s.type, + address: `${s.host}:${s.port}`, + image: s.image || '', + status: 'pending', + players: 0, + maxPlayers: 0, + map: '', + ping: 0 + })) }; -// ─── Server-Abfrage ─────────────────────────────────────────────────────────── -async function queryAll() { +// ── GameDig Abfrage ─────────────────────────────────────────────────────────── +async function queryServer(srv) { + try { + const opts = { + type: srv.type, + host: srv.host, + port: srv.port, + }; + // DayZ: query port ist game port + 24714 + if (srv.type === 'dayz' && !srv.queryPort) { + opts.port = srv.queryPort || (srv.port + 24714); + } + if (srv.queryPort) { + opts.port = srv.queryPort; + } + + const state = await GameDig.query(opts); + return { + status: 'online', + players: state.players ? state.players.length : 0, + maxPlayers: state.maxplayers || 0, + map: state.map || '', + ping: state.ping || 0 + }; + } catch (err) { + return { + status: 'offline', + error: err.message, + players: 0, + maxPlayers: 0, + map: '', + ping: 0 + }; + } +} + +async function runQueries() { + console.log(`[QUERY] Starte Abfragen (${servers.length} Server)...`); const start = Date.now(); - console.log(`[QUERY] Starte Abfragen (${config.servers.length} Server)...`); - const results = await Promise.all( - config.servers.map(async (s) => { - try { - const state = await GameDig.query({ - type: s.type, - host: s.host, - port: s.port, - requestRules: s.type === 'dayz', - socketTimeout: 5000, - }); + const results = await Promise.all(servers.map(srv => queryServer(srv))); - console.log(` ✓ [${s.label}] online – ${state.numplayers}/${state.maxplayers} Spieler`); - - return { - label: s.label, - type: s.type, - address: `${s.host}:${s.port}`, - image: s.image || null, - status: 'online', - players: state.numplayers, - maxPlayers: state.maxplayers, - map: state.map || null, - name: state.name || null, - ping: state.ping || null, - connect: state.connect || null, - }; - - } catch (err) { - console.log(` ✗ [${s.label}] offline – ${err.message}`); - return { - label: s.label, - type: s.type, - address: `${s.host}:${s.port}`, - image: s.image || null, - status: 'offline', - error: err.message, - }; - } - }) - ); - - cache = { - updated: new Date().toISOString(), - interval_seconds: INTERVAL_SEC, - servers: results, - }; + results.forEach((res, i) => { + const srv = servers[i]; + cache.servers[i] = { + label: srv.label, + type: srv.type, + address: `${srv.host}:${srv.port}`, + image: srv.image || '', + ...res + }; + const icon = res.status === 'online' ? '✓' : '✗'; + const info = res.status === 'online' + ? `${res.players}/${res.maxPlayers} Spieler, ${res.ping}ms` + : res.error || 'offline'; + console.log(` ${icon} [${srv.label}] ${res.status} – ${info}`); + }); + cache.updated = new Date().toISOString(); console.log(`[QUERY] Abgeschlossen in ${Date.now() - start}ms.`); } -// ─── Auth-Prüfung ───────────────────────────────────────────────────────────── +// ── HTTP Server ─────────────────────────────────────────────────────────────── function checkAuth(req) { if (!API_KEY) return true; - const headerKey = req.headers['x-api-key']; - if (headerKey && headerKey === API_KEY) return true; - - const url = new URL(req.url, `http://localhost`); - const queryKey = url.searchParams.get('key'); - if (queryKey && queryKey === API_KEY) return true; - - return false; + const urlKey = new URL(req.url, `http://localhost`).searchParams.get('key'); + return headerKey === API_KEY || urlKey === API_KEY; } -// ─── HTTP Server ────────────────────────────────────────────────────────────── const server = http.createServer((req, res) => { - const url = new URL(req.url, `http://localhost`); + const url = req.url.split('?')[0]; - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Content-Type', 'application/json'); - - // Health-Endpoint (kein Auth erforderlich) - if (url.pathname === '/health') { - res.writeHead(200); - res.end(JSON.stringify({ status: 'ok', updated: cache.updated })); + if (url === '/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'ok', uptime: process.uptime() })); return; } - // API-Endpoint - if (url.pathname === '/api/servers') { + if (url === '/api/servers') { if (!checkAuth(req)) { - res.writeHead(401); - res.end(JSON.stringify({ - error: 'Unauthorized', - hint: 'X-API-Key Header oder ?key= Parameter erforderlich.' - })); + res.writeHead(401, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Unauthorized' })); return; } - - if (!cache.updated) { - res.writeHead(503); - res.end(JSON.stringify({ error: 'Noch keine Daten verfügbar. Bitte kurz warten.' })); - return; - } - - res.writeHead(200); + res.writeHead(200, { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }); res.end(JSON.stringify(cache, null, 2)); return; } - // 404 - res.writeHead(404); - res.end(JSON.stringify({ - error: 'Nicht gefunden.', - endpoints: ['/api/servers', '/health'] - })); + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Not found' })); }); -// ─── Start ──────────────────────────────────────────────────────────────────── server.listen(PORT, () => { - console.log(''); console.log('╔══════════════════════════════════════════╗'); console.log('║ GameDig Server Status API ║'); console.log('╠══════════════════════════════════════════╣'); console.log(`║ Port: ${String(PORT).padEnd(29)}║`); - console.log(`║ Interval: ${String(INTERVAL_SEC + 's').padEnd(29)}║`); + console.log(`║ Interval: ${String(QUERY_INTERVAL/1000+'s').padEnd(29)}║`); console.log(`║ API-Key: ${(API_KEY ? 'aktiv' : 'deaktiviert').padEnd(29)}║`); - console.log(`║ GET /api/servers${' '.repeat(24)}║`); - console.log(`║ GET /health${' '.repeat(29)}║`); + console.log('║ GET /api/servers ║'); + console.log('║ GET /health ║'); console.log('╚══════════════════════════════════════════╝'); - console.log(''); - queryAll(); - setInterval(queryAll, INTERVAL_SEC * 1000); -}); - -// ─── Graceful Shutdown ──────────────────────────────────────────────────────── -process.on('SIGTERM', () => { - console.log('[INFO] SIGTERM empfangen, Server wird beendet...'); - server.close(() => process.exit(0)); -}); + // Erste Abfrage sofort, dann im Intervall + runQueries(); + setInterval(runQueries, QUERY_INTERVAL); +}); \ No newline at end of file