import { spawn } from 'node:child_process'; import { existsSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { serverInstallDir } from './paths.js'; const STEAMCMD_PATH = process.env.STEAMCMD_PATH || '/opt/steamcmd/steamcmd.sh'; const WINDROSE_APP_ID = Number(process.env.WINDROSE_APP_ID || '4129620'); function getSteamLoginArgs() { const username = process.env.STEAM_USERNAME; const password = process.env.STEAM_PASSWORD; const gslt = process.env.STEAM_GSLT; if (!username) { throw new Error("STEAM_USERNAME is required but not set."); } // Prefer GSLT if provided (safer for headless servers) if (gslt) { return ['+login', username, gslt]; } if (!password) { throw new Error("STEAM_PASSWORD is required but not set (or provide STEAM_GSLT)."); } return ['+login', username, password]; } export function runSteamCmd(args) { // === ENV DEBUG START === console.log("=== ENV DUMP START ==="); console.log("STEAM_USERNAME =", process.env.STEAM_USERNAME); console.log("STEAM_PASSWORD =", process.env.STEAM_PASSWORD); console.log("STEAM_GSLT =", process.env.STEAM_GSLT); console.log("=== ENV DUMP END ==="); // === ENV DEBUG END === return new Promise((resolve, reject) => { const child = spawn(STEAMCMD_PATH, args, { env: process.env, }); child.stdout.on('data', (d) => process.stdout.write(d)); child.stderr.on('data', (d) => process.stderr.write(d)); child.on('exit', (code) => { if (code === 0) resolve(); else reject(new Error(`SteamCMD exited with code ${code}`)); }); child.on('error', reject); }); } export async function installOrUpdateServer(id, appId) { const installDir = serverInstallDir(id); const steamArgs = [ '+force_install_dir', installDir, ...getSteamLoginArgs(), '+app_update', String(appId), 'validate', '+quit', ]; await runSteamCmd(steamArgs); } export async function readInstalledVersion(id) { const installDir = serverInstallDir(id); const manifestPath = join(installDir, 'steamapps', `appmanifest_${WINDROSE_APP_ID}.acf`); if (!existsSync(manifestPath)) return null; const raw = readFileSync(manifestPath, 'utf-8'); const buildIdMatch = raw.match(/"buildid"\s+"(\d+)"/); const timeUpdatedMatch = raw.match(/"LastUpdated"\s+"(\d+)"/); return { buildId: buildIdMatch ? Number(buildIdMatch[1]) : null, lastUpdated: timeUpdatedMatch ? Number(timeUpdatedMatch[1]) : null, }; }