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