Add SteamCMD-based updates for V Rising, Palworld, Zomboid, Terraria
All checks were successful
Deploy GSM / deploy (push) Successful in 24s
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:
@@ -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"
|
||||
}
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user