server.js aktualisiert

This commit is contained in:
Cap
2026-02-26 11:17:11 +01:00
parent 079740a062
commit 1fdd8cd3a3

203
server.js
View File

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