109 lines
4.9 KiB
JavaScript
109 lines
4.9 KiB
JavaScript
import { GameDig } from 'gamedig';
|
||
|
||
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);
|
||
});
|