Add Hytale server support with tmux runtime
All checks were successful
Deploy GSM / deploy (push) Successful in 29s
All checks were successful
Deploy GSM / deploy (push) Successful in 29s
- Add tmux runtime support to ssh.js (status, start, stop, logs, uptime) - Add Hytale server configuration to config.json - Add Hytale server info and logo to frontend (ServerCard, ServerDetail) - Add Hytale emoji to Discord notification mapping - Update documentation with Hytale server details Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ The homelab consists of:
|
||||
- **Palworld Server (192.168.2.53)**: Dedicated server with systemd (LXC)
|
||||
- **Project Zomboid Server (10.0.30.66)**: Dedicated server (external VM)
|
||||
- **Terraria Server (10.0.30.202)**: Vanilla server mit PM2 (external VM, VPN)
|
||||
- **Hytale Server (10.0.30.204)**: Dedicated server mit tmux (external VM, VPN)
|
||||
|
||||
## Key Technical Details
|
||||
|
||||
@@ -38,7 +39,7 @@ The homelab consists of:
|
||||
- Anzeigeeinstellungen: Superadmins können Verbindungsadresse und Hinweis pro Server anpassen
|
||||
- Activity Log: Protokolliert alle Aktionen mit Discord-Avatar
|
||||
|
||||
**Domain**: zeasy.dev with subdomains managed via Cloudflare DDNS (gsm.zeasy.dev, factorio.zeasy.dev, palworld.zeasy.dev, pz.zeasy.dev)
|
||||
**Domain**: zeasy.dev with subdomains managed via Cloudflare DDNS (gsm.zeasy.dev, factorio.zeasy.dev, palworld.zeasy.dev, pz.zeasy.dev, hytale.zeasy.dev)
|
||||
|
||||
**SSH Access**: Zugriff auf Server erfolgt über den Pi als Jump-Host:
|
||||
```bash
|
||||
|
||||
@@ -90,6 +90,19 @@
|
||||
"sshUser": "openttd",
|
||||
"workDir": "/opt/openttd",
|
||||
"port": 3979
|
||||
},
|
||||
{
|
||||
"id": "hytale",
|
||||
"name": "Hytale",
|
||||
"host": "10.0.30.204",
|
||||
"type": "hytale",
|
||||
"runtime": "tmux",
|
||||
"external": true,
|
||||
"tmuxName": "hytale",
|
||||
"sshUser": "beeck",
|
||||
"workDir": "/opt/hytale/Server",
|
||||
"startCmd": "./start-server.sh",
|
||||
"logCmd": "ls -t /opt/hytale/Server/logs/*.log | head -1 | xargs tail -n"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -841,7 +841,7 @@ router.post("/discord/send-update", authenticateToken, requireRole("superadmin")
|
||||
try {
|
||||
const serverIcons = {
|
||||
minecraft: '⛏️', factorio: '⚙️', zomboid: '🧟', vrising: '🧛',
|
||||
palworld: '🦎', terraria: '⚔️', openttd: '🚂'
|
||||
palworld: '🦎', terraria: '⚔️', openttd: '🚂', hytale: '🏰'
|
||||
};
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
@@ -94,6 +94,15 @@ export async function getServerStatus(server) {
|
||||
if (proc.pm2_env.status === "stopping") return "stopping";
|
||||
return "offline";
|
||||
} catch { return "offline"; }
|
||||
} else if (server.runtime === 'tmux') {
|
||||
const result = await ssh.execCommand(`tmux has-session -t ${server.tmuxName} 2>/dev/null && echo running || echo stopped`);
|
||||
if (result.stdout.trim() === 'running') {
|
||||
const uptimeResult = await ssh.execCommand(`ps -o etimes= -p $(tmux list-panes -t ${server.tmuxName} -F '#{pane_pid}' 2>/dev/null | head -1) 2>/dev/null | head -1`);
|
||||
const uptime = parseInt(uptimeResult.stdout.trim()) || 999;
|
||||
if (uptime < 60) return 'starting';
|
||||
return 'online';
|
||||
}
|
||||
return 'offline';
|
||||
} else {
|
||||
const result = await ssh.execCommand(`screen -ls | grep -E "\\.${server.screenName}[[:space:]]"`);
|
||||
if (result.code === 0) {
|
||||
@@ -142,6 +151,10 @@ export async function startServer(server, options = {}) {
|
||||
} else if (server.runtime === 'pm2') {
|
||||
const nvmPrefix = "source ~/.nvm/nvm.sh && ";
|
||||
await ssh.execCommand(nvmPrefix + "pm2 start " + server.serviceName);
|
||||
} else if (server.runtime === 'tmux') {
|
||||
await ssh.execCommand(`tmux kill-session -t ${server.tmuxName} 2>/dev/null || true`);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
await ssh.execCommand(`cd ${server.workDir} && tmux new-session -d -s ${server.tmuxName} '${server.startCmd}'`);
|
||||
} else {
|
||||
await ssh.execCommand(`screen -S ${server.screenName} -X quit 2>/dev/null`);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
@@ -159,6 +172,24 @@ export async function stopServer(server) {
|
||||
} else if (server.runtime === 'pm2') {
|
||||
const nvmPrefix = "source ~/.nvm/nvm.sh && ";
|
||||
await ssh.execCommand(nvmPrefix + "pm2 stop " + server.serviceName);
|
||||
} else if (server.runtime === 'tmux') {
|
||||
// Send stop command via tmux
|
||||
const stopCmd = server.stopCmd || 'stop';
|
||||
await ssh.execCommand(`tmux send-keys -t ${server.tmuxName} '${stopCmd}' Enter`);
|
||||
|
||||
for (let i = 0; i < 30; i++) {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
const check = await ssh.execCommand(`tmux has-session -t ${server.tmuxName} 2>/dev/null && echo running || echo stopped`);
|
||||
if (check.stdout.trim() === 'stopped') {
|
||||
console.log(`Server ${server.id} stopped after ${(i + 1) * 2} seconds`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Force killing ${server.id} after timeout`);
|
||||
await ssh.execCommand(`tmux kill-session -t ${server.tmuxName} 2>/dev/null || true`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
return;
|
||||
} else {
|
||||
// Different stop commands per server type
|
||||
const stopCmd = server.type === 'zomboid' ? 'quit' : 'stop';
|
||||
@@ -203,6 +234,19 @@ export async function getConsoleLog(server, lines = 50) {
|
||||
const nvmPrefix = "source ~/.nvm/nvm.sh && ";
|
||||
const result = await ssh.execCommand(nvmPrefix + "pm2 logs " + server.serviceName + " --lines " + lines + " --nostream");
|
||||
return result.stdout || result.stderr;
|
||||
} else if (server.runtime === 'tmux') {
|
||||
// For tmux, use logCmd, logFile, or capture pane content
|
||||
if (server.logCmd) {
|
||||
const result = await ssh.execCommand(`${server.logCmd} ${lines} 2>/dev/null || echo No log file found`);
|
||||
return result.stdout;
|
||||
} else if (server.logFile) {
|
||||
const result = await ssh.execCommand(`tail -n ${lines} ${server.logFile} 2>/dev/null || echo No log file found`);
|
||||
return result.stdout;
|
||||
} else {
|
||||
// Capture tmux pane content (last N lines)
|
||||
const result = await ssh.execCommand(`tmux capture-pane -t ${server.tmuxName} -p -S -${lines} 2>/dev/null || echo Session not found`);
|
||||
return result.stdout || 'No logs available';
|
||||
}
|
||||
} else if (server.logFile) {
|
||||
const result = await ssh.execCommand(`tail -n ${lines} ${server.logFile} 2>/dev/null || echo No log file found`);
|
||||
return result.stdout;
|
||||
@@ -241,6 +285,11 @@ export async function getProcessUptime(server) {
|
||||
}
|
||||
} catch {}
|
||||
return 0;
|
||||
} else if (server.runtime === "tmux") {
|
||||
const result = await ssh.execCommand(`ps -o etimes= -p $(tmux list-panes -t ${server.tmuxName} -F '#{pane_pid}' 2>/dev/null | head -1) 2>/dev/null | head -1`);
|
||||
const uptime = parseInt(result.stdout.trim());
|
||||
if (!isNaN(uptime)) return uptime;
|
||||
return 0;
|
||||
} else {
|
||||
const result = await ssh.execCommand(`ps -o etimes= -p $(pgrep -f "SCREEN.*${server.screenName}") 2>/dev/null | head -1`);
|
||||
const uptime = parseInt(result.stdout.trim());
|
||||
|
||||
BIN
gsm-frontend/public/hytale.png
Normal file
BIN
gsm-frontend/public/hytale.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 245 KiB |
@@ -53,6 +53,13 @@ const serverInfo = {
|
||||
links: [
|
||||
{ label: 'Steam', url: 'https://store.steampowered.com/app/1536610/OpenTTD/' }
|
||||
]
|
||||
},
|
||||
hytale: {
|
||||
address: 'hytale.zeasy.dev',
|
||||
logo: '/hytale.png',
|
||||
links: [
|
||||
{ label: 'Website', url: 'https://hytale.com/' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +72,7 @@ const getServerInfo = (serverName) => {
|
||||
if (name.includes('palworld')) return serverInfo.palworld
|
||||
if (name.includes('terraria')) return serverInfo.terraria
|
||||
if (name.includes('openttd')) return serverInfo.openttd
|
||||
if (name.includes('hytale')) return serverInfo.hytale
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ const getServerLogo = (serverName) => {
|
||||
if (name.includes("palworld")) return "/palworld.png"
|
||||
if (name.includes("terraria")) return "/terraria.png"
|
||||
if (name.includes("openttd")) return "/openttd.png"
|
||||
if (name.includes("hytale")) return "/hytale.png"
|
||||
return null
|
||||
}
|
||||
export default function ServerDetail() {
|
||||
|
||||
Reference in New Issue
Block a user