From c74ac543aa52b63b860a5d1209ab70c14d9cd3fe Mon Sep 17 00:00:00 2001 From: Alexander Zielonka Date: Wed, 7 Jan 2026 08:03:19 +0100 Subject: [PATCH] aa --- .claude/settings.local.json | 12 ++++++++- infrastructure.md | 51 ++++++++++++++++++++++++++++++++++--- todo.md | 24 ----------------- 3 files changed, 58 insertions(+), 29 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2c41e8e..ee37eb4 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,17 @@ "Bash(findstr:*)", "Bash(cat:*)", "Bash(powershell -Command @'\n$content = @\"\"\nimport { useState, useEffect } from 'react'\nimport { useNavigate } from 'react-router-dom'\nimport { getServers } from '../api'\nimport { useUser } from '../context/UserContext'\nimport ServerCard from '../components/ServerCard'\nimport SettingsModal from '../components/SettingsModal'\nimport UserManagement from '../components/UserManagement'\n\nexport default function Dashboard\\({ onLogout }\\) {\n const navigate = useNavigate\\(\\)\n const { user, token, loading: userLoading, isSuperadmin, role } = useUser\\(\\)\n const [servers, setServers] = useState\\([]\\)\n const [loading, setLoading] = useState\\(true\\)\n const [error, setError] = useState\\(''\\)\n const [showSettings, setShowSettings] = useState\\(false\\)\n const [showUserMgmt, setShowUserMgmt] = useState\\(false\\)\n\n const fetchServers = async \\(\\) => {\n try {\n const data = await getServers\\(token\\)\n setServers\\(data\\)\n setError\\(''\\)\n } catch \\(err\\) {\n setError\\('Failed to connect to server'\\)\n if \\(err.message.includes\\('401'\\) || err.message.includes\\('403'\\)\\) {\n onLogout\\(\\)\n }\n } finally {\n setLoading\\(false\\)\n }\n }\n\n useEffect\\(\\(\\) => {\n if \\(!userLoading\\) {\n fetchServers\\(\\)\n const interval = setInterval\\(fetchServers, 10000\\)\n return \\(\\) => clearInterval\\(interval\\)\n }\n }, [token, userLoading]\\)\n\n const roleLabels = {\n user: 'Viewer',\n moderator: 'Operator',\n superadmin: 'Admin'\n }\n\n if \\(userLoading\\) {\n return \\(\n
\n
Loading...
\n
\n \\)\n }\n\n const onlineCount = servers.filter\\(s => s.running\\).length\n const totalPlayers = servers.reduce\\(\\(sum, s\\) => sum + \\(s.players?.online || 0\\), 0\\)\n\n return \\(\n
\n {/* Header */}\n
\n
\n
\n
\n

\n Gameserver Monitor\n

\n
\n \n {onlineCount}/{servers.length} online\n \n |\n \n {totalPlayers} players\n \n
\n
\n\n
\n
\n
{user?.username}
\n
{roleLabels[role]}
\n
\n\n {isSuperadmin && \\(\n setShowUserMgmt\\(true\\)}\n className=\"\"btn btn-ghost\"\"\n >\n Users\n \n \\)}\n\n setShowSettings\\(true\\)}\n className=\"\"btn btn-ghost\"\"\n >\n Settings\n \n\n \n Sign out\n \n
\n
\n
\n
\n\n {/* Main Content */}\n
\n {error && \\(\n
\n {error}\n
\n \\)}\n\n {loading ? \\(\n
\n
Loading servers...
\n
\n \\) : \\(\n
\n {servers.map\\(\\(server, index\\) => \\(\n \n navigate\\('/server/' + server.id\\)}\n />\n
\n \\)\\)}\n
\n \\)}\n \n\n {/* Modals */}\n {showSettings && \\(\n setShowSettings\\(false\\)} />\n \\)}\n\n {showUserMgmt && \\(\n setShowUserMgmt\\(false\\)} />\n \\)}\n \n \\)\n}\n\"\"@\n$content | Out-File -FilePath \"\"Dashboard.jsx\"\" -Encoding UTF8\n'@)", - "Bash(git add:*)" + "Bash(git add:*)", + "Bash(dir:*)", + "Bash(ssh-keygen:*)", + "Bash(ssh alex@192.168.2.10 \"ssh root@192.168.2.30 ''cat /etc/nginx/nginx.conf''\")", + "Bash(ssh alex@192.168.2.10 \"ssh root@192.168.2.30 ''curl -s --max-time 5 http://localhost:3000/api/health''\")", + "Bash(ssh alex@192.168.2.10 \"ssh root@192.168.2.30 ''sleep 3 && curl -s --max-time 10 http://localhost:3000/api/servers 2>&1''\")", + "Bash(ssh alex@192.168.2.10 \"ssh root@192.168.2.30 ''cat > /opt/gameserver-monitor/backend/config.json << \"\"CONFIGEOF\"\"\n{\n \"\"servers\"\": [\n {\n \"\"id\"\": \"\"minecraft\"\",\n \"\"name\"\": \"\"All the Mods 10 | Minecraft\"\",\n \"\"host\"\": \"\"192.168.2.51\"\",\n \"\"type\"\": \"\"minecraft\"\",\n \"\"runtime\"\": \"\"screen\"\",\n \"\"rconPort\"\": 25575,\n \"\"rconPassword\"\": \"\"gsm-mc-2026\"\",\n \"\"screenName\"\": \"\"minecraft\"\",\n \"\"workDir\"\": \"\"/opt/minecraft\"\",\n \"\"startCmd\"\": \"\"./run.sh\"\"\n },\n {\n \"\"id\"\": \"\"factorio\"\",\n \"\"name\"\": \"\"Factorio\"\",\n \"\"host\"\": \"\"192.168.2.50\"\",\n \"\"type\"\": \"\"factorio\"\",\n \"\"runtime\"\": \"\"docker\"\",\n \"\"containerName\"\": \"\"factorio\"\",\n \"\"rconPort\"\": 27015,\n \"\"rconPassword\"\": \"\"jieTig6IkixaKuu\"\"\n },\n {\n \"\"id\"\": \"\"vrising\"\",\n \"\"name\"\": \"\"V Rising\"\",\n \"\"host\"\": \"\"192.168.2.52\"\",\n \"\"type\"\": \"\"vrising\"\",\n \"\"runtime\"\": \"\"systemd\"\",\n \"\"serviceName\"\": \"\"vrising\"\",\n \"\"rconPort\"\": 25575,\n \"\"rconPassword\"\": \"\"changeme\"\",\n \"\"workDir\"\": \"\"/home/steam/vrising\"\"\n },\n {\n \"\"id\"\": \"\"zomboid\"\",\n \"\"name\"\": \"\"Project Zomboid\"\",\n \"\"host\"\": \"\"10.0.30.66\"\",\n \"\"type\"\": \"\"zomboid\"\",\n \"\"runtime\"\": \"\"screen\"\",\n \"\"rconPort\"\": 27015,\n \"\"rconPassword\"\": \"\"ShkeloAufNettoParkplatzSchlagen47139\"\",\n \"\"screenName\"\": \"\"zomboid\"\",\n \"\"workDir\"\": \"\"/opt/pzserver\"\",\n \"\"startCmd\"\": \"\"./start-server.sh -servername Project\"\",\n \"\"sshUser\"\": \"\"pzuser\"\",\n \"\"logFile\"\": \"\"/home/pzuser/Zomboid/server-console.txt\"\"\n }\n ]\n}\nCONFIGEOF\npkill -f \"\"node server.js\"\" 2>/dev/null; cd /opt/gameserver-monitor/backend && node server.js >> /var/log/gsm-backend.log 2>&1 &''\")", + "Bash(ssh alex@192.168.2.10 \"ssh root@192.168.2.30 ''cat /opt/gameserver-monitor/backend/config.json | head -5''\")", + "Bash(ssh alex@192.168.2.10 \"ssh root@192.168.2.30 ''cat > /tmp/routefix.js << \"\"FIXEOF\"\"\nconst fs = require\\(\"\"fs\"\"\\);\nconst file = \"\"/opt/gameserver-monitor/backend/routes/servers.js\"\";\nlet content = fs.readFileSync\\(file, \"\"utf8\"\"\\);\n\n// Add import for isHostFailed\ncontent = content.replace\\(\n \"\"import { getServerStatus, startServer, stopServer, restartServer, getConsoleLog, getProcessUptime, listFactorioSaves, createFactorioWorld, deleteFactorioSave, getFactorioCurrentSave } from \\\\\"\"../services/ssh.js\\\\\"\";\"\",\n \"\"import { getServerStatus, startServer, stopServer, restartServer, getConsoleLog, getProcessUptime, listFactorioSaves, createFactorioWorld, deleteFactorioSave, getFactorioCurrentSave, isHostFailed } from \\\\\"\"../services/ssh.js\\\\\"\";\"\"\n\\);\n\n// Find and update the server list endpoint\nconst oldMapCode = \"\"const servers = await Promise.all\\(config.servers.map\\(async \\(server\\) => {\"\";\nconst newMapCode = \\\\`const servers = await Promise.all\\(config.servers.map\\(async \\(server\\) => {\n // Quick check if host is unreachable - skip expensive operations\n const hostUnreachable = isHostFailed\\(server.host, server.sshUser\\);\\\\`;\n\ncontent = content.replace\\(oldMapCode, newMapCode\\);\n\n// Update the status call to use the quick check\nconst oldStatusCall = \"\"const [status, metrics, players, playerList, processUptime] = await Promise.all\\([\"\";\nconst newStatusCall = \\\\`// If host is unreachable, return immediately with minimal data\n if \\(hostUnreachable\\) {\n const metrics = await getCurrentMetrics\\(server.id\\).catch\\(\\(\\) => \\({\n cpu: 0, cpuCores: 1, memory: 0, memoryUsed: 0, memoryTotal: 0, uptime: 0\n }\\)\\);\n const memTotal = formatBytes\\(metrics.memoryTotal\\);\n const memUsed = formatBytes\\(metrics.memoryUsed, memTotal.unit\\);\n return {\n id: server.id,\n name: server.name,\n type: server.type,\n status: \"\"unreachable\"\",\n running: false,\n metrics: {\n cpu: metrics.cpu,\n cpuCores: metrics.cpuCores,\n memory: metrics.memory,\n memoryUsed: memUsed.value,\n memoryTotal: memTotal.value,\n memoryUnit: memTotal.unit,\n uptime: 0\n },\n players: { online: 0, max: null, list: [] },\n hasRcon: !!server.rconPassword\n };\n }\n\n const [status, metrics, players, playerList, processUptime] = await Promise.all\\([\\\\`;\n\ncontent = content.replace\\(oldStatusCall, newStatusCall\\);\n\nfs.writeFileSync\\(file, content\\);\nconsole.log\\(\"\"Done\"\"\\);\nFIXEOF\nnode /tmp/routefix.js''\")", + "Bash(ssh alex@192.168.2.10 \"ssh root@192.168.2.30 ''cat > /opt/gameserver-monitor/backend/config.json << \"\"CONFIGEOF\"\"\n{\n \"\"servers\"\": [\n {\n \"\"id\"\": \"\"minecraft\"\",\n \"\"name\"\": \"\"All the Mods 10 | Minecraft\"\",\n \"\"host\"\": \"\"192.168.2.51\"\",\n \"\"type\"\": \"\"minecraft\"\",\n \"\"runtime\"\": \"\"screen\"\",\n \"\"rconPort\"\": 25575,\n \"\"rconPassword\"\": \"\"gsm-mc-2026\"\",\n \"\"screenName\"\": \"\"minecraft\"\",\n \"\"workDir\"\": \"\"/opt/minecraft\"\",\n \"\"startCmd\"\": \"\"./run.sh\"\"\n },\n {\n \"\"id\"\": \"\"factorio\"\",\n \"\"name\"\": \"\"Factorio\"\",\n \"\"host\"\": \"\"192.168.2.50\"\",\n \"\"type\"\": \"\"factorio\"\",\n \"\"runtime\"\": \"\"docker\"\",\n \"\"containerName\"\": \"\"factorio\"\",\n \"\"rconPort\"\": 27015,\n \"\"rconPassword\"\": \"\"jieTig6IkixaKuu\"\"\n },\n {\n \"\"id\"\": \"\"vrising\"\",\n \"\"name\"\": \"\"V Rising\"\",\n \"\"host\"\": \"\"192.168.2.52\"\",\n \"\"type\"\": \"\"vrising\"\",\n \"\"runtime\"\": \"\"systemd\"\",\n \"\"serviceName\"\": \"\"vrising\"\",\n \"\"rconPort\"\": 25575,\n \"\"rconPassword\"\": \"\"changeme\"\",\n \"\"workDir\"\": \"\"/home/steam/vrising\"\"\n },\n {\n \"\"id\"\": \"\"zomboid\"\",\n \"\"name\"\": \"\"Project Zomboid\"\",\n \"\"host\"\": \"\"10.0.30.66\"\",\n \"\"type\"\": \"\"zomboid\"\",\n \"\"runtime\"\": \"\"screen\"\",\n \"\"rconPort\"\": 27015,\n \"\"rconPassword\"\": \"\"ShkeloAufNettoParkplatzSchlagen47139\"\",\n \"\"screenName\"\": \"\"zomboid\"\",\n \"\"workDir\"\": \"\"/opt/pzserver\"\",\n \"\"startCmd\"\": \"\"./start-server.sh -servername Project\"\",\n \"\"sshUser\"\": \"\"pzuser\"\",\n \"\"logFile\"\": \"\"/home/pzuser/Zomboid/server-console.txt\"\"\n }\n ]\n}\nCONFIGEOF''\")", + "Bash(ssh alex@192.168.2.10 \"ssh root@192.168.2.30 ''wg show 2>&1 || echo \"\"WireGuard nicht installiert/aktiv\"\"''\")" ] } } diff --git a/infrastructure.md b/infrastructure.md index 376c367..ddf4221 100644 --- a/infrastructure.md +++ b/infrastructure.md @@ -109,6 +109,9 @@ Internet - Minecraft Whitelist-Verwaltung mit serverseitigem Caching - Rollensystem: user, moderator, superadmin +**Todos:** +- Auto-Shutdown: Gameserver automatisch abschalten wenn zu lange kein Spieler online war + **Prometheus Targets:** - localhost:9100 (monitor) - 192.168.2.50:9100 (factorio) @@ -156,6 +159,39 @@ Internet --- +### Project Zomboid Server (pzuser@10.0.30.66) - Extern via WireGuard + +| Eigenschaft | Wert | +|-------------|------| +| Runtime | screen | +| Screen Name | zomboid | +| RCON Port | 27015 | +| Pfad | /opt/pzserver | +| Log | /home/pzuser/Zomboid/server-console.txt | +| Netzwerk | Erreichbar via WireGuard-Tunnel "melih" | + +--- + +## WireGuard VPN + +Der Gameserver-Monitor (.30) nutzt einen WireGuard-Tunnel um externe Server (z.B. Zomboid auf 10.0.30.66) zu erreichen. + +**Interface:** melih +**Lokale IP:** 10.0.200.201/32 + +### WireGuard Watchdog + +Ein Cronjob prueft alle 2 Minuten ob der Tunnel aktiv ist und startet ihn bei Bedarf neu. + +| Komponente | Wert | +|------------|------| +| Script | /usr/local/bin/wg-watchdog.sh | +| Cronjob | */2 * * * * | +| Timeout | 5 Minuten ohne Handshake | +| Log | /var/log/wg-watchdog.log | + +--- + ## SSH-Zugang Der Gameserver-Monitor (.30) hat SSH-Key-Zugang zu: @@ -170,12 +206,19 @@ Key: /root/.ssh/id_ed25519 ## Wartung -### Backend neu starten -``` -ssh root@192.168.2.30 "pkill -f 'node server.js'; cd /opt/gameserver-monitor/backend && node server.js &" +### Backend (PM2) +```bash +# Status pruefen +ssh root@192.168.2.30 "pm2 status" + +# Logs anschauen +ssh root@192.168.2.30 "pm2 logs gameserver-backend --lines 50" + +# Neu starten +ssh root@192.168.2.30 "pm2 restart gameserver-backend" ``` ### Frontend neu bauen -``` +```bash ssh root@192.168.2.30 "cd /opt/gameserver-monitor/frontend && npm run build && nginx -s reload" ``` diff --git a/todo.md b/todo.md index 5d6d3ad..77d4a9a 100644 --- a/todo.md +++ b/todo.md @@ -2,15 +2,6 @@ ## Prioritaet Hoch -- [ ] **Pentest fuer Server durchfuehren** - - [ ] Portscan aller Server (nmap) - - [ ] SSH-Konfiguration pruefen (fail2ban, Key-Only) - - [ ] RCON-Passwoerter auf Staerke pruefen - - [ ] Firewall-Regeln auditieren - - [ ] SSL/TLS-Konfiguration testen - - [ ] Nginx-Sicherheitsheader pruefen - - [ ] JWT-Secret Rotation implementieren - - [ ] **GSM Modularisierung & Wiederverwendbarkeit** - [ ] Server-Typen als Plugins auslagern (minecraft, factorio, vrising, ...) - [ ] Generisches Interface fuer neue Gameserver-Typen @@ -44,18 +35,3 @@ - [ ] Player-Statistiken (Spielzeit, Join-History) - [ ] Changelog/Audit-Log fuer Admin-Aktionen -## Erledigt - -- [x] ~~Admin-Passwort im GSM aenderbar~~ (UI) -- [x] ~~JWT_SECRET sicher setzen~~ -- [x] ~~Prometheus + Grafana installieren~~ -- [x] ~~Grafana extern erreichbar~~ -- [x] ~~Benutzer-Verwaltung (Rollensystem)~~ -- [x] ~~Ressourcen-Graphen (CPU/RAM Historie)~~ -- [x] ~~Oeffentliches Dashboard~~ -- [x] ~~Whitelist-Caching serverseitig~~ -- [x] ~~Gameserver-Uptime statt Host-Uptime~~ -- [x] ~~Game-Logos in UI~~ -- [x] ~~Navbar-Logo mit Hover-Effekt~~ -- [x] ~~V Rising Server hinzugefuegt~~ -- [x] ~~Factorio World Management (Save-Auswahl, Welt erstellen, Templates, Settings anzeigen)~~