168 lines
7.1 KiB
JavaScript
168 lines
7.1 KiB
JavaScript
'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));
|
||
});
|