Dateien nach "/" hochladen
This commit is contained in:
148
README.md
148
README.md
@@ -1,2 +1,148 @@
|
|||||||
# gameserver-status-api
|
# GameDig Server Status API
|
||||||
|
|
||||||
|
Pterodactyl Egg das Gameserver per [GameDig](https://github.com/gamedig/node-gamedig) abfragt
|
||||||
|
und die Ergebnisse als JSON-REST-API bereitstellt.
|
||||||
|
|
||||||
|
## Unterstützte Spiele (Auswahl)
|
||||||
|
|
||||||
|
| Spiel | `type` in config.json |
|
||||||
|
|---|---|
|
||||||
|
| DayZ | `dayz` |
|
||||||
|
| Counter-Strike 2 | `cs2` |
|
||||||
|
| Rust | `rust` |
|
||||||
|
| ARK: Survival Evolved | `arkse` |
|
||||||
|
| + 320 weitere | [GameDig Games-Liste](https://github.com/gamedig/node-gamedig/blob/master/GAMES_LIST.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. `egg.json` im Pterodactyl-Panel importieren *(Admin → Nests → Import Egg)*
|
||||||
|
2. Neuen Server anlegen und einen **Port** zuweisen
|
||||||
|
3. Server starten → Installationsscript lädt automatisch alle Dateien von diesem Repo
|
||||||
|
4. `config.json` im **Dateimanager** mit deinen Serveradressen anpassen
|
||||||
|
5. Server neu starten
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
### config.json (im Pterodactyl-Dateimanager bearbeiten)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"label": "DayZ Main Server",
|
||||||
|
"type": "dayz",
|
||||||
|
"host": "1.2.3.4",
|
||||||
|
"port": 2302,
|
||||||
|
"image": "https://example.com/images/dayz.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "CS2 Server",
|
||||||
|
"type": "cs2",
|
||||||
|
"host": "1.2.3.4",
|
||||||
|
"port": 27015,
|
||||||
|
"image": "https://example.com/images/cs2.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Egg-Variablen (im Pterodactyl-Panel konfigurierbar)
|
||||||
|
|
||||||
|
| Variable | Standard | Beschreibung |
|
||||||
|
|---|---|---|
|
||||||
|
| `QUERY_INTERVAL` | `60` | Abfrageintervall in Sekunden (min. 10) |
|
||||||
|
| `API_KEY` | *(leer)* | Optionaler API-Schlüssel – leer = offen |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API-Endpunkte
|
||||||
|
|
||||||
|
### `GET /api/servers`
|
||||||
|
|
||||||
|
**Ohne API-Key:**
|
||||||
|
```
|
||||||
|
GET http://dein-server:PORT/api/servers
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mit API-Key via Header:**
|
||||||
|
```
|
||||||
|
GET http://dein-server:PORT/api/servers
|
||||||
|
X-API-Key: dein-key
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mit API-Key via URL-Parameter:**
|
||||||
|
```
|
||||||
|
GET http://dein-server:PORT/api/servers?key=dein-key
|
||||||
|
```
|
||||||
|
|
||||||
|
**Beispiel-Antwort:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"updated": "2026-02-26T14:32:00.000Z",
|
||||||
|
"interval_seconds": 60,
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"label": "DayZ Main Server",
|
||||||
|
"type": "dayz",
|
||||||
|
"address": "1.2.3.4:2302",
|
||||||
|
"image": "https://example.com/images/dayz.jpg",
|
||||||
|
"status": "online",
|
||||||
|
"players": 12,
|
||||||
|
"maxPlayers": 60,
|
||||||
|
"map": "ChernarusPlus",
|
||||||
|
"name": "My DayZ Server",
|
||||||
|
"ping": 42,
|
||||||
|
"connect": "1.2.3.4:2302"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "CS2 Server",
|
||||||
|
"type": "cs2",
|
||||||
|
"address": "1.2.3.4:27015",
|
||||||
|
"status": "offline",
|
||||||
|
"error": "Connection timed out"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `GET /health`
|
||||||
|
|
||||||
|
Kein API-Key erforderlich. Geeignet für Uptime-Monitoring.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "status": "ok", "updated": "2026-02-26T14:32:00.000Z" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## DayZ Besonderheit
|
||||||
|
|
||||||
|
DayZ nutzt einen separaten Query-Port (`Game-Port + 24714`).
|
||||||
|
Falls die Abfrage fehlschlägt, den Query-Port direkt angeben:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "type": "dayz", "host": "1.2.3.4", "port": 27016 }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dateien im Container
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/container/
|
||||||
|
├── server.js ← Hauptscript (API + Query-Loop)
|
||||||
|
├── entrypoint.sh ← Startscript
|
||||||
|
├── config.json ← Serverkonfiguration (selbst bearbeiten)
|
||||||
|
├── package.json ← npm-Konfiguration
|
||||||
|
└── node_modules/ ← Abhängigkeiten (automatisch installiert)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Update
|
||||||
|
|
||||||
|
Um `server.js` oder `entrypoint.sh` zu aktualisieren, einfach die Dateien in
|
||||||
|
diesem Repo anpassen und den Server im Pterodactyl-Panel **neu installieren**
|
||||||
|
*(Server → Settings → Reinstall Server)*.
|
||||||
|
|||||||
32
config.json
Normal file
32
config.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"label": "DayZ Main Server",
|
||||||
|
"type": "dayz",
|
||||||
|
"host": "1.2.3.4",
|
||||||
|
"port": 2302,
|
||||||
|
"image": "https://example.com/images/dayz.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Counter Strike 2",
|
||||||
|
"type": "cs2",
|
||||||
|
"host": "1.2.3.4",
|
||||||
|
"port": 27015,
|
||||||
|
"image": "https://example.com/images/cs2.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Rust Server",
|
||||||
|
"type": "rust",
|
||||||
|
"host": "1.2.3.4",
|
||||||
|
"port": 28015,
|
||||||
|
"image": "https://example.com/images/rust.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "ARK Server",
|
||||||
|
"type": "arkse",
|
||||||
|
"host": "1.2.3.4",
|
||||||
|
"port": 7777,
|
||||||
|
"image": "https://example.com/images/ark.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
54
egg.json
Normal file
54
egg.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.pterodactyl.io/egg.v1.json",
|
||||||
|
"meta": {
|
||||||
|
"version": "PTDL_v2",
|
||||||
|
"update_url": null
|
||||||
|
},
|
||||||
|
"exported_at": "2026-02-26T12:00:00+00:00",
|
||||||
|
"name": "GameDig Server Status API",
|
||||||
|
"author": "cap@cap-net.ch",
|
||||||
|
"description": "Fragt Gameserver (DayZ, CS2, Rust, ARK u.v.m.) per GameDig ab und stellt die Ergebnisse als JSON-REST-API zur Verfügung. Optionaler API-Key-Schutz. Konfiguration über config.json im Dateimanager.",
|
||||||
|
"features": null,
|
||||||
|
"docker_images": {
|
||||||
|
"Node.js 20": "ghcr.io/pterodactyl/yolks:nodejs_20"
|
||||||
|
},
|
||||||
|
"file_denylist": [],
|
||||||
|
"startup": "bash /home/container/entrypoint.sh",
|
||||||
|
"config": {
|
||||||
|
"files": "{}",
|
||||||
|
"startup": {
|
||||||
|
"done": "GameDig Server Status API"
|
||||||
|
},
|
||||||
|
"logs": "{}",
|
||||||
|
"stop": "^C"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"installation": {
|
||||||
|
"script": "#!/bin/bash\napt-get update -y && apt-get install -y curl\n\ncd /mnt/server\n\nGITEA_RAW=\"https://gitea.cap-net.ch/Cap/gameserver-status-api/raw/branch/main\"\n\necho \"[INFO] Lade Dateien von Gitea herunter...\"\ncurl -sSL \"${GITEA_RAW}/server.js\" -o server.js || { echo '[FEHLER] server.js konnte nicht geladen werden.'; exit 1; }\ncurl -sSL \"${GITEA_RAW}/entrypoint.sh\" -o entrypoint.sh || { echo '[FEHLER] entrypoint.sh konnte nicht geladen werden.'; exit 1; }\nchmod +x entrypoint.sh\n\necho \"[INFO] Erstelle package.json...\"\ncat > package.json << 'EOF'\n{\n \"name\": \"gameserver-status-api\",\n \"version\": \"1.0.0\",\n \"description\": \"GameDig Server Status API\",\n \"main\": \"server.js\",\n \"dependencies\": {\n \"gamedig\": \"^4.1.0\"\n }\n}\nEOF\n\necho \"[INFO] Installiere npm-Pakete...\"\nnpm install --omit=dev\n\nif [ ! -f 'config.json' ]; then\n echo \"[INFO] Lege Beispiel-config.json an...\"\n curl -sSL \"${GITEA_RAW}/config.json\" -o config.json\n echo \"[WICHTIG] Bitte config.json im Dateimanager mit deinen Serveradressen anpassen!\"\nfi\n\necho \"\"\necho \"[DONE] Installation abgeschlossen.\"\necho \"[INFO] Nächste Schritte:\"\necho \" 1. config.json im Dateimanager mit deinen Serveradressen anpassen\"\necho \" 2. Server starten\"",
|
||||||
|
"container": "ghcr.io/pterodactyl/installers:debian",
|
||||||
|
"entrypoint": "bash"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"name": "Query-Intervall (Sekunden)",
|
||||||
|
"description": "Wie oft sollen die Gameserver abgefragt werden? Empfohlen: 60. Minimum: 10.",
|
||||||
|
"env_variable": "QUERY_INTERVAL",
|
||||||
|
"default_value": "60",
|
||||||
|
"user_viewable": true,
|
||||||
|
"user_editable": true,
|
||||||
|
"rules": "required|numeric|min:10",
|
||||||
|
"field_type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "API Key (optional)",
|
||||||
|
"description": "Schützt die API mit einem Key. Abfrage via Header 'X-API-Key' oder URL-Parameter '?key='. Leer lassen = kein Schutz.",
|
||||||
|
"env_variable": "API_KEY",
|
||||||
|
"default_value": "",
|
||||||
|
"user_viewable": true,
|
||||||
|
"user_editable": true,
|
||||||
|
"rules": "nullable|string|max:128",
|
||||||
|
"field_type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
24
entrypoint.sh
Normal file
24
entrypoint.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cd /home/container
|
||||||
|
|
||||||
|
echo "=== GameDig Server Status API ==="
|
||||||
|
|
||||||
|
# config.json prüfen
|
||||||
|
if [ ! -f "config.json" ]; then
|
||||||
|
echo "[FEHLER] config.json nicht gefunden!"
|
||||||
|
echo "[INFO] Bitte config.json über den Pterodactyl-Dateimanager anlegen."
|
||||||
|
echo "[INFO] Vorlage: https://gitea.cap-net.ch/Cap/gameserver-status-api/raw/branch/main/config.json"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# node_modules prüfen
|
||||||
|
if [ ! -d "node_modules/gamedig" ]; then
|
||||||
|
echo "[INFO] Installiere Node-Abhängigkeiten..."
|
||||||
|
npm install --omit=dev
|
||||||
|
echo "[INFO] Installation abgeschlossen."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Starte server.js..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
node /home/container/server.js
|
||||||
167
server.js
Normal file
167
server.js
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
'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));
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user