Files
gameserver-status-api/server.js
2026-02-26 13:16:09 +01:00

112 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import pkg from 'gamedig';
// pkg.query ist direkt verfügbar in gamedig v4
const GameDig = pkg;
import http from 'http';
import fs from 'fs';
const CONFIG_PATH = '/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 || '';
let servers = [];
try {
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 geladen werden: ${e.message}`);
process.exit(1);
}
let cache = {
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
}))
};
async function queryServer(srv) {
try {
const opts = { type: srv.type, host: srv.host, port: srv.port };
if (srv.type === 'dayz') opts.port = srv.queryPort || (srv.port + 24714);
if (srv.queryPort) opts.port = srv.queryPort;
const state = await GameDig.query(opts);
return {
status: 'online',
players: Array.isArray(state.players) ? state.players.length : 0,
maxPlayers: state.maxplayers || 0,
map: state.map || '',
ping: Math.round(state.ping || 0)
};
} catch (err) {
return { status: 'offline', error: String(err.message || err).substring(0, 200), players: 0, maxPlayers: 0, map: '', ping: 0 };
}
}
async function runQueries() {
console.log(`[QUERY] Starte Abfragen (${servers.length} Server)...`);
const start = Date.now();
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, Map: ${res.map}, ${res.ping}ms` : `FEHLER: ${res.error}`;
console.log(` ${icon} [${srv.label}] ${res.status} ${info}`);
});
cache.updated = new Date().toISOString();
console.log(`[QUERY] Abgeschlossen in ${Date.now() - start}ms.`);
}
function checkAuth(req) {
if (!API_KEY) return true;
const headerKey = req.headers['x-api-key'];
const url = new URL(req.url, `http://localhost`);
return headerKey === API_KEY || url.searchParams.get('key') === API_KEY;
}
const server = http.createServer((req, res) => {
const url = req.url.split('?')[0];
if (url === '/health') { res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify({status:'ok',uptime:process.uptime()})); return; }
if (url === '/api/servers') {
if (!checkAuth(req)) { res.writeHead(401, {'Content-Type':'application/json'}); res.end(JSON.stringify({error:'Unauthorized'})); return; }
res.writeHead(200, {'Content-Type':'application/json','Access-Control-Allow-Origin':'*'});
res.end(JSON.stringify(cache, null, 2)); return;
}
res.writeHead(404, {'Content-Type':'application/json'}); res.end(JSON.stringify({error:'Not found'}));
});
// ── Sauberes Beenden bei SIGINT / SIGTERM (Pterodactyl Stop) ─────────────────
function shutdown(signal) {
console.log(`\n[INFO] Signal ${signal} empfangen beende Server...`);
server.close(() => {
console.log('[INFO] Server gestoppt.');
process.exit(0);
});
// Fallback: nach 3s hart beenden
setTimeout(() => process.exit(0), 3000).unref();
}
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
server.listen(PORT, () => {
console.log('╔══════════════════════════════════════════╗');
console.log('║ GameDig Server Status API ║');
console.log('╠══════════════════════════════════════════╣');
console.log(`║ Port: ${String(PORT).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 ║');
console.log('║ GET /health ║');
console.log('╚══════════════════════════════════════════╝');
runQueries();
setInterval(runQueries, QUERY_INTERVAL);
});