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

168 lines
7.1 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.
'use strict';
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 || '';
// ─── Config laden ─────────────────────────────────────────────────────────────
let config;
try {
config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
console.log(`[INIT] config.json geladen ${config.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.`);
process.exit(1);
}
// ─── Cache ────────────────────────────────────────────────────────────────────
let cache = {
updated: null,
interval_seconds: INTERVAL_SEC,
servers: []
};
// ─── Server-Abfrage ───────────────────────────────────────────────────────────
async function queryAll() {
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,
});
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,
};
console.log(`[QUERY] Abgeschlossen in ${Date.now() - start}ms.`);
}
// ─── Auth-Prüfung ─────────────────────────────────────────────────────────────
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;
}
// ─── HTTP Server ──────────────────────────────────────────────────────────────
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://localhost`);
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 }));
return;
}
// API-Endpoint
if (url.pathname === '/api/servers') {
if (!checkAuth(req)) {
res.writeHead(401);
res.end(JSON.stringify({
error: 'Unauthorized',
hint: 'X-API-Key Header oder ?key= Parameter erforderlich.'
}));
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.end(JSON.stringify(cache, null, 2));
return;
}
// 404
res.writeHead(404);
res.end(JSON.stringify({
error: 'Nicht gefunden.',
endpoints: ['/api/servers', '/health']
}));
});
// ─── 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(`║ API-Key: ${(API_KEY ? 'aktiv' : 'deaktiviert').padEnd(29)}`);
console.log(`║ GET /api/servers${' '.repeat(24)}`);
console.log(`║ GET /health${' '.repeat(29)}`);
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));
});