diff --git a/platform/protocol/server-deployment.md b/platform/protocol/server-deployment.md index 83dac08..74e3813 100644 --- a/platform/protocol/server-deployment.md +++ b/platform/protocol/server-deployment.md @@ -26,8 +26,9 @@ from command text. Empty `shell` means argv-oriented execution. Game plugins own concrete game policy: install/update commands, app ids, executable refs, default launch flags, stop-before-update behavior, and startup argument construction. For SCUM, the plugin action assets own the SteamCMD flow: -stop `SCUMServer.exe` when updating, run -`steamcmd.exe +force_install_dir +login anonymous +app_update 3792580 validate +quit`, +stop `SCUMServer.exe` when updating, keep SteamCMD outside the server install +root, run +`steamcmd.exe +force_install_dir +login anonymous +app_update 3792580 +quit`, and start `\\SCUM\\Binaries\\Win64\\SCUMServer.exe -port= -MaxPlayers= -log`. Generated Run distributions carry validated plugin lifecycle assets into the diff --git a/plugins/examples/scum-server-plugin/actions/install.json b/plugins/examples/scum-server-plugin/actions/install.json index ecdb8f2..287a2a5 100644 --- a/plugins/examples/scum-server-plugin/actions/install.json +++ b/plugins/examples/scum-server-plugin/actions/install.json @@ -9,8 +9,8 @@ "SERVER_TEMPLATE": "scum-server", "SERVER_STEAM_APP_ID": "3792580", "SERVER_STEAMCMD_INSTALL_DIR_ARG": "+force_install_dir", - "SERVER_STEAMCMD_UPDATE_ARGS": "+login anonymous +app_update 3792580 validate +quit", + "SERVER_STEAMCMD_UPDATE_ARGS": "+login anonymous +app_update 3792580 +quit", "SERVER_EXECUTABLE_REF": "SCUM/Binaries/Win64/SCUMServer.exe" }, - "timeoutMs": 300000 + "timeoutMs": 7200000 } diff --git a/plugins/examples/scum-server-plugin/bin/scum-install-update.cmd b/plugins/examples/scum-server-plugin/bin/scum-install-update.cmd index 21b45c2..40ee8e3 100755 --- a/plugins/examples/scum-server-plugin/bin/scum-install-update.cmd +++ b/plugins/examples/scum-server-plugin/bin/scum-install-update.cmd @@ -5,21 +5,24 @@ if "%SERVER_ROOT%"=="" set "SERVER_ROOT=C:\scumserver" set "SERVER_ROOT_WINDOWS=%SERVER_ROOT:/=\%" if "%SERVER_STEAM_APP_ID%"=="" set "SERVER_STEAM_APP_ID=3792580" if "%SERVER_STEAMCMD_INSTALL_DIR_ARG%"=="" set "SERVER_STEAMCMD_INSTALL_DIR_ARG=+force_install_dir" -if "%SERVER_STEAMCMD_UPDATE_ARGS%"=="" set "SERVER_STEAMCMD_UPDATE_ARGS=+login anonymous +app_update 3792580 validate +quit" +if "%SERVER_STEAMCMD_UPDATE_ARGS%"=="" set "SERVER_STEAMCMD_UPDATE_ARGS=+login anonymous +app_update 3792580 +quit" if "%SERVER_EXECUTABLE_REF%"=="" set "SERVER_EXECUTABLE_REF=SCUM\Binaries\Win64\SCUMServer.exe" if "%SERVER_INSTALL_DIR%"=="" set "SERVER_INSTALL_DIR=%SERVER_ROOT_WINDOWS%" set "SERVER_INSTALL_DIR_WINDOWS=%SERVER_INSTALL_DIR:/=\%" set "SERVER_INSTALL_DIR_STEAM=%SERVER_INSTALL_DIR_WINDOWS:\=/%" +if "%SERVER_STEAMCMD_DIR%"=="" call :default_steamcmd_dir +set "SERVER_STEAMCMD_DIR_WINDOWS=%SERVER_STEAMCMD_DIR:/=\%" if "%SERVER_STEAMCMD_MAX_ATTEMPTS%"=="" set "SERVER_STEAMCMD_MAX_ATTEMPTS=3" if "%SERVER_STEAMCMD_RETRY_SECONDS%"=="" set "SERVER_STEAMCMD_RETRY_SECONDS=30" -set "STEAMCMD_DIR=%SERVER_ROOT_WINDOWS%\steamcmd" +set "STEAMCMD_DIR=%SERVER_STEAMCMD_DIR_WINDOWS%" set "STEAMCMD_EXE=%STEAMCMD_DIR%\steamcmd.exe" set "SCUM_EXE_MARKER=%SERVER_ROOT_WINDOWS%\.scum-exe-path" if not exist "%SERVER_ROOT_WINDOWS%" mkdir "%SERVER_ROOT_WINDOWS%" if not exist "%STEAMCMD_DIR%" mkdir "%STEAMCMD_DIR%" if not exist "%SERVER_INSTALL_DIR_WINDOWS%" mkdir "%SERVER_INSTALL_DIR_WINDOWS%" +if /I "%STEAMCMD_DIR%"=="%SERVER_INSTALL_DIR_WINDOWS%" exit /b 3 if not exist "%STEAMCMD_EXE%" ( powershell -NoProfile -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; $zip=Join-Path $env:STEAMCMD_DIR 'steamcmd.zip'; Invoke-WebRequest -Uri 'https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip' -OutFile $zip; Expand-Archive -Path $zip -DestinationPath $env:STEAMCMD_DIR -Force; Remove-Item $zip -Force" @@ -54,3 +57,12 @@ set "SCUM_EXE=%SERVER_ROOT_WINDOWS%\steamcmd\steamapps\common\SCUM Server\%SERVE if exist "%SCUM_EXE%" exit /b 0 set "SCUM_EXE=%SERVER_ROOT_WINDOWS%\SCUM Server\%SERVER_EXECUTABLE_REF:/=\%" exit /b 0 + +:default_steamcmd_dir +for %%I in ("%SERVER_ROOT_WINDOWS%") do set "SERVER_ROOT_DRIVE=%%~dI" +if "%SERVER_ROOT_DRIVE%"=="" ( + set "SERVER_STEAMCMD_DIR=%SERVER_ROOT_WINDOWS%-steamcmd" +) else ( + set "SERVER_STEAMCMD_DIR=%SERVER_ROOT_DRIVE%\steamcmd" +) +exit /b 0 diff --git a/plugins/examples/scum-server-plugin/manifest.json b/plugins/examples/scum-server-plugin/manifest.json index e9af6cb..ee80efc 100644 --- a/plugins/examples/scum-server-plugin/manifest.json +++ b/plugins/examples/scum-server-plugin/manifest.json @@ -3,7 +3,7 @@ "id": "game.scum", "name": "SCUM Server", "description": "First-party SCUM game server operations plugin with platform-mediated lifecycle and companion bridge support.", - "version": "0.1.2", + "version": "0.1.4", "kind": "game-plugin", "tags": [ "scum", diff --git a/plugins/manifests/lifecycle-action.schema.json b/plugins/manifests/lifecycle-action.schema.json index 5d52ed1..633b8ac 100644 --- a/plugins/manifests/lifecycle-action.schema.json +++ b/plugins/manifests/lifecycle-action.schema.json @@ -21,7 +21,7 @@ "additionalProperties": { "type": "string", "maxLength": 512 }, "maxProperties": 32 }, - "timeoutMs": { "type": "integer", "minimum": 1, "maximum": 300000 }, + "timeoutMs": { "type": "integer", "minimum": 1, "maximum": 7200000 }, "stopTimeoutMs": { "type": "integer", "minimum": 1, "maximum": 60000 } } } diff --git a/plugins/tests/manifest-validation.test.ts b/plugins/tests/manifest-validation.test.ts index 707225a..68bd005 100644 --- a/plugins/tests/manifest-validation.test.ts +++ b/plugins/tests/manifest-validation.test.ts @@ -199,11 +199,14 @@ describe("plugin manifest validation", () => { const startScript = fs.readFileSync(path.join(pluginDir, startAction.executableKey), "utf8"); expect(manifest.runtimeProfiles.serverDeployments).toBeUndefined(); expect(assetPaths).toEqual(expect.arrayContaining(["actions/install.json", "actions/start.json", "bin/scum-install-update.cmd", "bin/scum-start.cmd"])); - expect(installAction).toMatchObject({ executableKey: "bin/scum-install-update.cmd", environment: { SERVER_STEAM_APP_ID: "3792580", SERVER_STEAMCMD_UPDATE_ARGS: "+login anonymous +app_update 3792580 validate +quit" } }); + expect(installAction).toMatchObject({ executableKey: "bin/scum-install-update.cmd", environment: { SERVER_STEAM_APP_ID: "3792580", SERVER_STEAMCMD_UPDATE_ARGS: "+login anonymous +app_update 3792580 +quit" } }); + expect(installAction.timeoutMs).toBe(7200000); expect(startAction).toMatchObject({ executableKey: "bin/scum-start.cmd", environment: { SERVER_LOG_FLAG: "-log" } }); expect(installScript).toContain("taskkill /IM SCUMServer.exe /F"); expect(installScript).toContain("SERVER_ROOT_WINDOWS=%SERVER_ROOT:/=\\%"); expect(installScript).toContain("SERVER_INSTALL_DIR=%SERVER_ROOT_WINDOWS%"); + expect(installScript).toContain("SERVER_STEAMCMD_DIR=%SERVER_ROOT_DRIVE%\\steamcmd"); + expect(installScript).toContain("STEAMCMD_DIR=%SERVER_STEAMCMD_DIR_WINDOWS%"); expect(installScript).toContain("SERVER_INSTALL_DIR_STEAM=%SERVER_INSTALL_DIR_WINDOWS:\\=/%"); expect(installScript).toContain("%SERVER_STEAMCMD_INSTALL_DIR_ARG% \"%SERVER_INSTALL_DIR_STEAM%\" %SERVER_STEAMCMD_UPDATE_ARGS%"); expect(installScript).toContain("SERVER_STEAMCMD_MAX_ATTEMPTS=3"); diff --git a/scripts/local-debug/env.sh b/scripts/local-debug/env.sh index 127ee73..6dae030 100755 --- a/scripts/local-debug/env.sh +++ b/scripts/local-debug/env.sh @@ -92,9 +92,29 @@ local_debug_seed_lifecycle_scope() { cp "$source_dir/actions/$action.json" "$scope/actions/$action.json" fi done - cp "$true_binary" "$scope/bin/install-server" - cp "$true_binary" "$scope/bin/game-server" - chmod +x "$scope/bin/install-server" "$scope/bin/game-server" + local executable_key + while IFS= read -r executable_key; do + [[ -n "$executable_key" ]] || continue + local executable_target="$scope/$executable_key" + mkdir -p "$(dirname "$executable_target")" + cp "$true_binary" "$executable_target" + chmod +x "$executable_target" + done < <(node - "$scope/actions" <<'NODE' +const fs = require("fs"); +const path = require("path"); +const actionsDir = process.argv[2]; +for (const entry of fs.readdirSync(actionsDir)) { + if (!entry.endsWith(".json")) continue; + const action = JSON.parse(fs.readFileSync(path.join(actionsDir, entry), "utf8")); + const key = action.executableKey; + if (typeof key !== "string" || key.length === 0) continue; + if (path.posix.isAbsolute(key) || key.includes("\\") || key.split("/").includes("..")) { + throw new Error(`unsafe local-debug executableKey: ${key}`); + } + console.log(key); +} +NODE + ) } local_debug_seed_plugin_lifecycle_template() { diff --git a/scripts/local-debug/smoke.sh b/scripts/local-debug/smoke.sh index f4cb84c..65934bc 100755 --- a/scripts/local-debug/smoke.sh +++ b/scripts/local-debug/smoke.sh @@ -296,14 +296,19 @@ json_id() { require_scum_manifest_contract() { local file="$1" - node - "$file" <<'NODE' + local expected_version="$2" + node - "$file" "$expected_version" <<'NODE' const fs = require("fs"); const file = process.argv[2]; +const expectedVersion = process.argv[3]; const plugin = JSON.parse(fs.readFileSync(file, "utf8")); const missing = []; const declaredPermissions = Array.isArray(plugin.declaredPermissions) ? plugin.declaredPermissions : []; const bridgeActions = Array.isArray(plugin.bridgeActions) ? plugin.bridgeActions : []; const lifecycleProfiles = plugin.runtimeProfiles?.lifecycleProfiles ?? []; +if (plugin.version !== expectedVersion) { + missing.push(`version ${expectedVersion}`); +} if (!declaredPermissions.includes("server.run.distribution")) { missing.push("server.run.distribution permission"); } @@ -781,7 +786,8 @@ NODE printf 'registering SCUM plugin through platform API\n' register_plugin_manifest "SCUM plugin" "game.scum" "$WORK_DIR/register-scum-plugin.request.json" "$WORK_DIR/register-scum-plugin.response.json" reject_forbidden_fragments "$WORK_DIR/register-scum-plugin.response.json" -require_scum_manifest_contract "$WORK_DIR/register-scum-plugin.response.json" +SCUM_PLUGIN_VERSION="$(node -e 'const fs=require("fs"); process.stdout.write(JSON.parse(fs.readFileSync(process.argv[1],"utf8")).version);' "$ROOT_DIR/plugins/examples/scum-server-plugin/manifest.json")" +require_scum_manifest_contract "$WORK_DIR/register-scum-plugin.response.json" "$SCUM_PLUGIN_VERSION" printf 'waiting for run endpoint heartbeat\n' for _ in $(seq 1 20); do