Add modular server update feature with Discord notifications
All checks were successful
Deploy GSM / deploy (push) Successful in 25s
All checks were successful
Deploy GSM / deploy (push) Successful in 25s
- Add serverUpdates.js service with handler registry for extensibility - Implement Factorio Docker image update (pull + container recreate) - Add GET/POST /servers/:id/update routes for check/perform - Add ServerUpdateButton component with auto-check and confirm dialog - Integrate update card in ServerDetail overview tab - Auto-send Discord notification on successful update Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'url';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { authenticateToken, optionalAuth, requireRole, rejectGuest } from '../middleware/auth.js';
|
||||
import { getServerStatus, startServer, stopServer, restartServer, getConsoleLog, getProcessUptime, listFactorioSaves, createFactorioWorld, deleteFactorioSave, getFactorioCurrentSave, isHostFailed, listZomboidConfigs, readZomboidConfig, writeZomboidConfig, listPalworldConfigs, readPalworldConfig, writePalworldConfig, readTerrariaConfig, writeTerrariaConfig, readOpenTTDConfig, writeOpenTTDConfig, readHytaleConfig, writeHytaleConfig } from '../services/ssh.js';
|
||||
import { supportsUpdate, checkForUpdate, performUpdate } from '../services/serverUpdates.js';
|
||||
import { sendRconCommand, getPlayers, getPlayerList } from '../services/rcon.js';
|
||||
import { getServerMetricsHistory, getCurrentMetrics } from '../services/prometheus.js';
|
||||
import { initWhitelistCache, getCachedWhitelist, setCachedWhitelist, initFactorioTemplates, getFactorioTemplates, createFactorioTemplate, deleteFactorioTemplate, initFactorioWorldSettings, getFactorioWorldSettings, saveFactorioWorldSettings, deleteFactorioWorldSettings, initAutoShutdownSettings, getAutoShutdownSettings, setAutoShutdownSettings, initActivityLog, logActivity, getActivityLog, initServerDisplaySettings, getServerDisplaySettings, getAllServerDisplaySettings, setServerDisplaySettings, initGuildSettings } from '../db/init.js';
|
||||
@@ -749,6 +750,75 @@ router.post('/:id/restart', authenticateToken, requireRole('moderator'), async (
|
||||
}
|
||||
});
|
||||
|
||||
// ============ SERVER UPDATE ROUTES ============
|
||||
|
||||
// Check for server updates (moderator+)
|
||||
router.get('/:id/update', authenticateToken, requireRole('moderator'), async (req, res) => {
|
||||
const config = loadConfig();
|
||||
const server = config.servers.find(s => s.id === req.params.id);
|
||||
if (!server) return res.status(404).json({ error: 'Server not found' });
|
||||
|
||||
// Check if this server type supports updates
|
||||
if (!supportsUpdate(server.type)) {
|
||||
return res.json({
|
||||
supported: false,
|
||||
hasUpdate: false,
|
||||
message: 'Updates für diesen Servertyp nicht unterstützt'
|
||||
});
|
||||
}
|
||||
|
||||
if (isHostFailed(server.host, server.sshUser)) {
|
||||
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await checkForUpdate(server);
|
||||
res.json(result);
|
||||
} 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 });
|
||||
}
|
||||
});
|
||||
|
||||
// Perform server update (moderator+)
|
||||
router.post('/:id/update', authenticateToken, requireRole('moderator'), async (req, res) => {
|
||||
const config = loadConfig();
|
||||
const server = config.servers.find(s => s.id === req.params.id);
|
||||
if (!server) return res.status(404).json({ error: 'Server not found' });
|
||||
|
||||
if (!supportsUpdate(server.type)) {
|
||||
return res.status(400).json({ error: 'Updates für diesen Servertyp nicht unterstützt' });
|
||||
}
|
||||
|
||||
if (isHostFailed(server.host, server.sshUser)) {
|
||||
return res.status(503).json({ error: 'Server host is unreachable', unreachable: true });
|
||||
}
|
||||
|
||||
// Check if server is running
|
||||
try {
|
||||
const status = await getServerStatus(server);
|
||||
if (status === 'online' || status === 'starting') {
|
||||
return res.status(400).json({ error: 'Server muss gestoppt sein um zu updaten' });
|
||||
}
|
||||
} catch (err) {
|
||||
// Continue if status check fails
|
||||
}
|
||||
|
||||
try {
|
||||
const sendDiscord = req.body?.sendDiscord !== false; // Default true
|
||||
const result = await performUpdate(server, sendDiscord);
|
||||
logActivity(req.user.id, req.user.username, 'server_update', server.id, result.newVersion || 'latest', req.user.discordId, req.user.avatar);
|
||||
res.json(result);
|
||||
} 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 });
|
||||
}
|
||||
});
|
||||
|
||||
// Get whitelist (with server-side caching, guests not allowed)
|
||||
router.get('/:id/whitelist', authenticateToken, rejectGuest, async (req, res) => {
|
||||
const config = loadConfig();
|
||||
|
||||
Reference in New Issue
Block a user