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'
|
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)
|
// SSH connection helper (reuse existing pattern)
|
||||||
const sshConnections = new Map();
|
const sshConnections = new Map();
|
||||||
const SSH_TIMEOUT = 30000; // Longer timeout for update operations
|
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 = {
|
const updateHandlers = {
|
||||||
@@ -223,22 +331,56 @@ const updateHandlers = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Add more game handlers here in the future
|
// V Rising (SteamCMD)
|
||||||
// Example template for other games:
|
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: {
|
minecraft: {
|
||||||
supportsUpdate: true,
|
supportsUpdate: false, // Modpacks need manual update
|
||||||
gameName: "Minecraft",
|
gameName: "Minecraft ATM10"
|
||||||
async checkForUpdate(server) {
|
|
||||||
// Implementation for Minecraft updates
|
|
||||||
return { hasUpdate: false, message: "Not implemented" };
|
|
||||||
},
|
},
|
||||||
async performUpdate(server) {
|
|
||||||
// Implementation for Minecraft updates
|
// OpenTTD - Manual update required
|
||||||
return { success: false, message: "Not implemented" };
|
openttd: {
|
||||||
|
supportsUpdate: false,
|
||||||
|
gameName: "OpenTTD"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hytale - Manual update required
|
||||||
|
hytale: {
|
||||||
|
supportsUpdate: false,
|
||||||
|
gameName: "Hytale"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
@@ -495,8 +495,8 @@ const formatUptime = (seconds) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Server Update */}
|
{/* Server Update - for supported server types */}
|
||||||
{isModerator && server.type === 'factorio' && (
|
{isModerator && ['factorio', 'vrising', 'palworld', 'zomboid', 'terraria'].includes(server.type) && (
|
||||||
<div className="card p-4">
|
<div className="card p-4">
|
||||||
<h3 className="text-sm font-medium text-neutral-300 mb-3">Server Update</h3>
|
<h3 className="text-sm font-medium text-neutral-300 mb-3">Server Update</h3>
|
||||||
<p className="text-neutral-500 text-sm mb-4">
|
<p className="text-neutral-500 text-sm mb-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user