Cleanup repo, add Gitea CI/CD workflow, improve error handling
All checks were successful
Deploy GSM / deploy (push) Successful in 1m25s

- Remove temp files and reorganize docs
- Add .gitea/workflows/deploy.yml for automated deployment
- Add unreachable host checks to server routes (/:id, logs, start/stop/restart)
- Add unreachable checks to config routes (zomboid, terraria, openttd)
- Return HTTP 503 with unreachable flag instead of crashing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alexander Zielonka
2026-01-09 12:15:32 +01:00
parent f2f9e02fb2
commit 2d9a5910fa
22 changed files with 181 additions and 2861 deletions

View File

@@ -200,9 +200,16 @@ router.get("/zomboid/config", authenticateToken, requireRole("moderator"), async
const server = config.servers.find(s => s.type === "zomboid");
if (!server) return res.status(404).json({ error: "Zomboid server not configured" });
if (isHostFailed(server.host, server.sshUser)) {
return res.status(503).json({ error: "Server host is unreachable", unreachable: true });
}
const files = await listZomboidConfigs(server);
res.json({ files });
} catch (err) {
if (err.message.includes('unreachable') || err.message.includes('ECONNREFUSED') || err.message.includes('ETIMEDOUT')) {
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
}
res.status(500).json({ error: err.message });
}
});
@@ -419,10 +426,18 @@ router.get("/terraria/config", authenticateToken, requireRole("moderator"), asyn
const config = loadConfig();
const server = config.servers.find(s => s.id === "terraria");
if (!server) return res.status(404).json({ error: "Server not found" });
if (isHostFailed(server.host, server.sshUser)) {
return res.status(503).json({ error: "Server host is unreachable", unreachable: true });
}
const content = await readTerrariaConfig(server);
res.json({ content });
} catch (error) {
console.error("Error reading Terraria config:", error);
if (error.message.includes('unreachable') || error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) {
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
}
res.status(500).json({ error: error.message });
}
});
@@ -452,10 +467,18 @@ router.get("/openttd/config", authenticateToken, requireRole("moderator"), async
const config = loadConfig();
const server = config.servers.find(s => s.id === "openttd");
if (!server) return res.status(404).json({ error: "Server not found" });
if (isHostFailed(server.host, server.sshUser)) {
return res.status(503).json({ error: "Server host is unreachable", unreachable: true });
}
const content = await readOpenTTDConfig(server);
res.json({ content });
} catch (error) {
console.error("Error reading OpenTTD config:", error);
if (error.message.includes('unreachable') || error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) {
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
}
res.status(500).json({ error: error.message });
}
});
@@ -487,11 +510,41 @@ router.get('/:id', optionalAuth, async (req, res) => {
}
try {
// Check if host is unreachable
const hostUnreachable = isHostFailed(server.host, server.sshUser);
if (hostUnreachable) {
const metrics = await getCurrentMetrics(server.id).catch(() => ({
cpu: 0, cpuCores: 1, memory: 0, memoryUsed: 0, memoryTotal: 0, uptime: 0
}));
const memTotal = formatBytes(metrics.memoryTotal);
const memUsed = formatBytes(metrics.memoryUsed, memTotal.unit);
return res.json({
id: server.id,
name: server.name,
type: server.type,
status: "unreachable",
running: false,
metrics: {
cpu: metrics.cpu,
cpuCores: metrics.cpuCores,
memory: metrics.memory,
memoryUsed: memUsed.value,
memoryTotal: memTotal.value,
memoryUnit: memTotal.unit,
uptime: 0
},
players: { online: 0, max: null, list: [] },
hasRcon: !!server.rconPassword
});
}
const [status, metrics, players, playerList, processUptime] = await Promise.all([
getServerStatus(server),
getCurrentMetrics(server.id),
server.rconPassword ? getPlayers(server) : { online: 0, max: null },
server.rconPassword ? getPlayerList(server) : { players: [] },
getCurrentMetrics(server.id).catch(() => ({
cpu: 0, cpuCores: 1, memory: 0, memoryUsed: 0, memoryTotal: 0, uptime: 0
})),
server.rconPassword ? getPlayers(server).catch(() => ({ online: 0, max: null })) : { online: 0, max: null },
server.rconPassword ? getPlayerList(server).catch(() => ({ players: [] })) : { players: [] },
getProcessUptime(server).catch(() => 0)
]);
@@ -520,6 +573,7 @@ router.get('/:id', optionalAuth, async (req, res) => {
hasRcon: !!server.rconPassword
});
} catch (err) {
console.error(`Error fetching server ${req.params.id}:`, err.message);
res.status(500).json({ error: err.message });
}
});
@@ -571,11 +625,18 @@ router.get('/:id/logs', authenticateToken, requireRole('moderator'), async (req,
const server = config.servers.find(s => s.id === req.params.id);
if (!server) return res.status(404).json({ error: 'Server not found' });
if (isHostFailed(server.host, server.sshUser)) {
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
}
try {
const lines = parseInt(req.query.lines) || 100;
const logs = await getConsoleLog(server, lines);
res.json({ logs });
} catch (err) {
if (err.message.includes('unreachable') || err.message.includes('ECONNREFUSED') || err.message.includes('ETIMEDOUT')) {
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
}
res.status(500).json({ error: err.message });
}
});
@@ -586,12 +647,19 @@ router.post('/:id/start', authenticateToken, requireRole('moderator'), async (re
const server = config.servers.find(s => s.id === req.params.id);
if (!server) return res.status(404).json({ error: 'Server not found' });
if (isHostFailed(server.host, server.sshUser)) {
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
}
try {
const { save } = req.body || {};
await startServer(server, { save });
logActivity(req.user.id, req.user.username, 'server_start', server.id, save ? 'Save: ' + save : null, req.user.discordId, req.user.avatar);
res.json({ message: 'Server starting' });
} catch (err) {
if (err.message.includes('unreachable') || err.message.includes('ECONNREFUSED') || err.message.includes('ETIMEDOUT')) {
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
}
res.status(500).json({ error: err.message });
}
});
@@ -601,11 +669,18 @@ router.post('/:id/stop', authenticateToken, requireRole('moderator'), async (req
const server = config.servers.find(s => s.id === req.params.id);
if (!server) return res.status(404).json({ error: 'Server not found' });
if (isHostFailed(server.host, server.sshUser)) {
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
}
try {
await stopServer(server);
logActivity(req.user.id, req.user.username, 'server_stop', server.id, null, req.user.discordId, req.user.avatar);
res.json({ message: 'Server stopping' });
} catch (err) {
if (err.message.includes('unreachable') || err.message.includes('ECONNREFUSED') || err.message.includes('ETIMEDOUT')) {
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
}
res.status(500).json({ error: err.message });
}
});
@@ -615,11 +690,18 @@ router.post('/:id/restart', authenticateToken, requireRole('moderator'), async (
const server = config.servers.find(s => s.id === req.params.id);
if (!server) return res.status(404).json({ error: 'Server not found' });
if (isHostFailed(server.host, server.sshUser)) {
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
}
try {
await restartServer(server);
logActivity(req.user.id, req.user.username, 'server_restart', server.id, null, req.user.discordId, req.user.avatar);
res.json({ message: 'Server restarting' });
} catch (err) {
if (err.message.includes('unreachable') || err.message.includes('ECONNREFUSED') || err.message.includes('ETIMEDOUT')) {
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
}
res.status(500).json({ error: err.message });
}
});