Add SteamCMD-based updates for V Rising, Palworld, Zomboid, Terraria
All checks were successful
Deploy GSM / deploy (push) Successful in 24s

- Create generic SteamCMD handler factory for reusable update logic
- Check build IDs via appmanifest files and Steam API
- Support V Rising (1829350), Palworld (2394010), Zomboid (380870), tModLoader (1281930)
- Show update button for all supported server types in UI
- Minecraft, OpenTTD, Hytale marked as manual-update-only

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alexander Zielonka
2026-01-23 11:50:19 +01:00
parent 68de66b0fd
commit 6771722cdd
2 changed files with 159 additions and 17 deletions

View File

@@ -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"
}
};
// =============================================================================

View File

@@ -495,8 +495,8 @@ const formatUptime = (seconds) => {
</div>
)}
{/* Server Update */}
{isModerator && server.type === 'factorio' && (
{/* Server Update - for supported server types */}
{isModerator && ['factorio', 'vrising', 'palworld', 'zomboid', 'terraria'].includes(server.type) && (
<div className="card p-4">
<h3 className="text-sm font-medium text-neutral-300 mb-3">Server Update</h3>
<p className="text-neutral-500 text-sm mb-4">