Dateien nach "/" hochladen

This commit is contained in:
Cap
2026-02-26 10:16:14 +01:00
parent 0df2332d35
commit 079740a062
5 changed files with 424 additions and 1 deletions

148
README.md
View File

@@ -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
View 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
View 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
View 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
View 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));
});