diff --git a/gsm-backend/services/serverUpdates.js b/gsm-backend/services/serverUpdates.js index ab48f49..6b2eeae 100644 --- a/gsm-backend/services/serverUpdates.js +++ b/gsm-backend/services/serverUpdates.js @@ -23,6 +23,14 @@ const serverLogos = { hytale: 'https://gsm.zeasy.dev/hytale.png' }; +// Steam App IDs +const STEAM_APP_IDS = { + vrising: 1829350, + palworld: 2394010, + zomboid: 380870, + terraria: 1281930 // tModLoader +}; + // SSH connection helper (reuse existing pattern) const sshConnections = new Map(); const SSH_TIMEOUT = 30000; // Longer timeout for update operations @@ -78,7 +86,107 @@ async function sendUpdateNotification(server, updateInfo) { } // ============================================================================= -// Update Handler Registry - Add new game handlers here +// SteamCMD Helper Functions +// ============================================================================= + +/** + * Get the current installed build ID from Steam appmanifest + */ +async function getSteamBuildId(ssh, installDir, appId) { + const manifestPath = `${installDir}/steamapps/appmanifest_${appId}.acf`; + const result = await ssh.execCommand(`grep -oP '"buildid"\\s*"\\K[^"]+' ${manifestPath} 2>/dev/null`); + return result.stdout.trim() || null; +} + +/** + * Get the latest available build ID from Steam + */ +async function getLatestSteamBuildId(ssh, appId, steamcmdPath = "steamcmd") { + // Query Steam for app info + const result = await ssh.execCommand( + `${steamcmdPath} +login anonymous +app_info_update 1 +app_info_print ${appId} +quit 2>/dev/null | grep -A2 '"public"' | grep '"buildid"' | head -1 | grep -oP '"buildid"\\s*"\\K[^"]+'`, + { execOptions: { timeout: 60000 } } + ); + return result.stdout.trim() || null; +} + +/** + * Create a generic SteamCMD update handler + */ +function createSteamHandler(config) { + const { appId, installDir, steamcmdPath = "steamcmd", gameName, sudoUser = null } = config; + + return { + supportsUpdate: true, + gameName, + + async checkForUpdate(server) { + const ssh = await getConnection(server.host, server.sshUser); + const actualInstallDir = installDir || server.workDir; + + console.log(`[Update] Checking for ${gameName} updates...`); + + // Get current build ID + const currentBuildId = await getSteamBuildId(ssh, actualInstallDir, appId); + if (!currentBuildId) { + return { hasUpdate: false, error: "Konnte aktuelle Version nicht ermitteln" }; + } + + // Get latest build ID from Steam + const sudoPrefix = sudoUser ? `sudo -u ${sudoUser} ` : ""; + const latestBuildId = await getLatestSteamBuildId(ssh, appId, sudoPrefix + steamcmdPath); + + if (!latestBuildId) { + return { hasUpdate: false, error: "Konnte Steam-Version nicht abrufen" }; + } + + const hasUpdate = currentBuildId !== latestBuildId; + + return { + hasUpdate, + currentVersion: currentBuildId, + newVersion: latestBuildId, + message: hasUpdate ? "Neues Update verfügbar!" : "Server ist auf dem neuesten Stand" + }; + }, + + async performUpdate(server) { + const ssh = await getConnection(server.host, server.sshUser); + const actualInstallDir = installDir || server.workDir; + + // Get old build ID + const oldBuildId = await getSteamBuildId(ssh, actualInstallDir, appId); + + console.log(`[Update] Starting ${gameName} update...`); + + // Run SteamCMD update + const sudoPrefix = sudoUser ? `sudo -u ${sudoUser} ` : ""; + const updateResult = await ssh.execCommand( + `${sudoPrefix}${steamcmdPath} +login anonymous +force_install_dir "${actualInstallDir}" +app_update ${appId} validate +quit`, + { execOptions: { timeout: 600000 } } // 10 min timeout for large updates + ); + + console.log(`[Update] SteamCMD output:`, updateResult.stdout); + + if (updateResult.code !== 0 && !updateResult.stdout.includes("Success")) { + throw new Error("Update fehlgeschlagen: " + (updateResult.stderr || "Unbekannter Fehler")); + } + + // Get new build ID + const newBuildId = await getSteamBuildId(ssh, actualInstallDir, appId); + + return { + success: true, + message: "Update erfolgreich! Server kann jetzt gestartet werden.", + oldVersion: oldBuildId || "unbekannt", + newVersion: newBuildId || "aktuell" + }; + } + }; +} + +// ============================================================================= +// Update Handler Registry // ============================================================================= const updateHandlers = { @@ -223,22 +331,56 @@ const updateHandlers = { } }, - // Add more game handlers here in the future - // Example template for other games: - /* + // V Rising (SteamCMD) + vrising: createSteamHandler({ + appId: STEAM_APP_IDS.vrising, + installDir: "/home/steam/vrising", + steamcmdPath: "/home/steam/steamcmd/steamcmd.sh", + gameName: "V Rising" + }), + + // Palworld (SteamCMD) + palworld: createSteamHandler({ + appId: STEAM_APP_IDS.palworld, + installDir: "/opt/palworld", + steamcmdPath: "/usr/games/steamcmd", + gameName: "Palworld" + }), + + // Project Zomboid (SteamCMD) + zomboid: createSteamHandler({ + appId: STEAM_APP_IDS.zomboid, + installDir: "/opt/pzserver", + steamcmdPath: "/home/pzuser/steamcmd/steamcmd.sh", + gameName: "Project Zomboid", + sudoUser: "pzuser" + }), + + // Terraria / tModLoader (SteamCMD) + terraria: createSteamHandler({ + appId: STEAM_APP_IDS.terraria, + installDir: "/home/terraria/tModLoader", + steamcmdPath: "/home/terraria/steamcmd/steamcmd.sh", + gameName: "Terraria (tModLoader)" + }), + + // Minecraft ATM10 - Manual update required (modpack) minecraft: { - supportsUpdate: true, - gameName: "Minecraft", - async checkForUpdate(server) { - // Implementation for Minecraft updates - return { hasUpdate: false, message: "Not implemented" }; - }, - async performUpdate(server) { - // Implementation for Minecraft updates - return { success: false, message: "Not implemented" }; - } + supportsUpdate: false, // Modpacks need manual update + gameName: "Minecraft ATM10" }, - */ + + // OpenTTD - Manual update required + openttd: { + supportsUpdate: false, + gameName: "OpenTTD" + }, + + // Hytale - Manual update required + hytale: { + supportsUpdate: false, + gameName: "Hytale" + } }; // ============================================================================= diff --git a/gsm-frontend/src/pages/ServerDetail.jsx b/gsm-frontend/src/pages/ServerDetail.jsx index cc77b8d..b950319 100644 --- a/gsm-frontend/src/pages/ServerDetail.jsx +++ b/gsm-frontend/src/pages/ServerDetail.jsx @@ -495,8 +495,8 @@ const formatUptime = (seconds) => { )} - {/* Server Update */} - {isModerator && server.type === 'factorio' && ( + {/* Server Update - for supported server types */} + {isModerator && ['factorio', 'vrising', 'palworld', 'zomboid', 'terraria'].includes(server.type) && (

Server Update