server.js aktualisiert
This commit is contained in:
227
server.js
227
server.js
@@ -1,167 +1,156 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const http = require('http');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// ── GameDig v4+ benötigt destructured import ──────────────────────────────────
|
||||||
const { GameDig } = require('gamedig');
|
const { GameDig } = require('gamedig');
|
||||||
const http = require('http');
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
// ─── Konfiguration ────────────────────────────────────────────────────────────
|
// ── 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) {
|
||||||
|
try {
|
||||||
|
const opts = {
|
||||||
|
type: srv.type,
|
||||||
|
host: srv.host,
|
||||||
|
port: srv.port,
|
||||||
|
};
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = await GameDig.query(opts);
|
||||||
|
return {
|
||||||
|
status: 'online',
|
||||||
|
players: state.players ? state.players.length : 0,
|
||||||
|
maxPlayers: state.maxplayers || 0,
|
||||||
|
map: state.map || '',
|
||||||
|
ping: state.ping || 0
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
status: 'offline',
|
||||||
|
error: err.message,
|
||||||
|
players: 0,
|
||||||
|
maxPlayers: 0,
|
||||||
|
map: '',
|
||||||
|
ping: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runQueries() {
|
||||||
|
console.log(`[QUERY] Starte Abfragen (${servers.length} Server)...`);
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
console.log(`[QUERY] Starte Abfragen (${config.servers.length} Server)...`);
|
|
||||||
|
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(servers.map(srv => queryServer(srv)));
|
||||||
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`);
|
results.forEach((res, i) => {
|
||||||
|
const srv = servers[i];
|
||||||
return {
|
cache.servers[i] = {
|
||||||
label: s.label,
|
label: srv.label,
|
||||||
type: s.type,
|
type: srv.type,
|
||||||
address: `${s.host}:${s.port}`,
|
address: `${srv.host}:${srv.port}`,
|
||||||
image: s.image || null,
|
image: srv.image || '',
|
||||||
status: 'online',
|
...res
|
||||||
players: state.numplayers,
|
};
|
||||||
maxPlayers: state.maxplayers,
|
const icon = res.status === 'online' ? '✓' : '✗';
|
||||||
map: state.map || null,
|
const info = res.status === 'online'
|
||||||
name: state.name || null,
|
? `${res.players}/${res.maxPlayers} Spieler, ${res.ping}ms`
|
||||||
ping: state.ping || null,
|
: res.error || 'offline';
|
||||||
connect: state.connect || null,
|
console.log(` ${icon} [${srv.label}] ${res.status} – ${info}`);
|
||||||
};
|
});
|
||||||
|
|
||||||
} 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
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));
|
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user