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 \\)\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
\n \\)}\n\n
\n\n
\n
\n
\n
\n \n\n {/* Main Content */}\n
\n {error && \\(\n \n {error}\n
\n \\)}\n\n {loading ? \\(\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)~~