From a695aafd0f7d6700d6bba10866382cb255ed15bd Mon Sep 17 00:00:00 2001 From: npc0-hue Date: Fri, 4 Sep 2026 12:37:39 +0800 Subject: [PATCH] Remove legacy steamcmd and SCUM runtime coupling --- cmd/run/main_test.go | 2 +- config/config_test.go | 4 ++-- protocol/autonomous_lifecycle.go | 11 ++++++++- protocol/autonomous_lifecycle_test.go | 9 +++++++ protocol/job.go | 22 ++++++++--------- protocol/job.md | 2 +- protocol/job_validation.go | 2 +- protocol/job_validation_test.go | 30 +++++++++++------------ runtime/autonomous_lifecycle_test.go | 4 ++-- runtime/dependencies.go | 34 +++++++++++---------------- runtime/dependencies_test.go | 10 ++++++++ runtime/distribution_build_test.go | 14 +++++------ runtime/execution_test.go | 4 ++-- runtime/lifecycle_test.go | 4 ++-- runtime/ue4ss_dll_extension.go | 20 ++++++++-------- runtime/ue4ss_dll_extension_test.go | 32 ++++++++++++------------- 16 files changed, 113 insertions(+), 91 deletions(-) diff --git a/cmd/run/main_test.go b/cmd/run/main_test.go index 0e2c792..24bc4f0 100644 --- a/cmd/run/main_test.go +++ b/cmd/run/main_test.go @@ -3,7 +3,7 @@ package main import "testing" func TestDiagnosticPlatformAddressOmitsCredentials(t *testing.T) { - if got := diagnosticPlatformAddress("https://token:secret@scum.npc0.com/api"); got != "https://scum.npc0.com/api" { + if got := diagnosticPlatformAddress("https://token:secret@platform.example.test/api"); got != "https://platform.example.test/api" { t.Fatalf("unexpected diagnostic address %q", got) } if got := diagnosticPlatformAddress("not a URL"); got != "invalid" { diff --git a/config/config_test.go b/config/config_test.go index 668b00c..3c4d0e1 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -65,7 +65,7 @@ func TestLoadUsesPackagedIdentityOverEnvironment(t *testing.T) { }() BuildMode = "worker" - BuildPlatformURL = "https://scum.npc0.com" + BuildPlatformURL = "https://platform.example.test" BuildRunEndpointID = "run-server-1" BuildDisplayName = "Run-server-1" BuildRegistrationToken = "compiled-run-key" @@ -80,7 +80,7 @@ func TestLoadUsesPackagedIdentityOverEnvironment(t *testing.T) { t.Setenv(key, "") } cfg := Load() - if cfg.Mode != "worker" || cfg.PlatformURL != "https://scum.npc0.com" || cfg.RunEndpointID != "run-server-1" || cfg.RegistrationToken != "compiled-run-key" || cfg.ServerInstanceID != "server-1" || cfg.PluginID != "game.scum" || cfg.ComponentKind != "run" || cfg.KeyGeneration != 5 || cfg.Version != "run-dist-1" || cfg.WorkspaceSeed != "seed-payload" { + if cfg.Mode != "worker" || cfg.PlatformURL != "https://platform.example.test" || cfg.RunEndpointID != "run-server-1" || cfg.RegistrationToken != "compiled-run-key" || cfg.ServerInstanceID != "server-1" || cfg.PluginID != "game.scum" || cfg.ComponentKind != "run" || cfg.KeyGeneration != 5 || cfg.Version != "run-dist-1" || cfg.WorkspaceSeed != "seed-payload" { t.Fatalf("expected compiled defaults, got %+v", cfg) } diff --git a/protocol/autonomous_lifecycle.go b/protocol/autonomous_lifecycle.go index 954ee79..2cbee9b 100644 --- a/protocol/autonomous_lifecycle.go +++ b/protocol/autonomous_lifecycle.go @@ -106,7 +106,7 @@ func ValidateRunAutonomousLifecyclePlan(plan RunAutonomousLifecyclePlan) error { return ValidationError("autonomous install plan is invalid") } for _, step := range installPlan.Steps { - if !ValidLogicalFileKey(step.TargetKey) || !validAutonomousToken(step.Type, 80) || step.PackageManager != "" && !validAutonomousToken(step.PackageManager, 80) || step.PackageName != "" && !validAutonomousToken(step.PackageName, 160) || step.Version != "" && !validAutonomousToken(step.Version, 120) { + if !validAutonomousInstallStepType(step.Type) || !ValidLogicalFileKey(step.TargetKey) || step.PackageManager != "" && !validAutonomousToken(step.PackageManager, 80) || step.PackageName != "" && !validAutonomousToken(step.PackageName, 160) || step.Version != "" && !validAutonomousToken(step.Version, 120) { return ValidationError("autonomous install step is invalid") } } @@ -139,6 +139,15 @@ func ValidateRunAutonomousLifecyclePlan(plan RunAutonomousLifecyclePlan) error { return nil } +func validAutonomousInstallStepType(value string) bool { + switch value { + case "package", "verified-download", "manual": + return true + default: + return false + } +} + func validateAutonomousDataTarget(target RunAutonomousDataTarget) error { if !ValidLogicalFileKey(target.Key) || !ValidLogicalFileKey(target.TransportKey) || !ValidLogicalFileKey(target.SourceRootKey) || !ValidLogicalFileKey(target.SourcePath) || !ValidLogicalFileKey(target.WorkspaceKey) { return ValidationError("autonomous data target is invalid") diff --git a/protocol/autonomous_lifecycle_test.go b/protocol/autonomous_lifecycle_test.go index 75b815a..76db210 100644 --- a/protocol/autonomous_lifecycle_test.go +++ b/protocol/autonomous_lifecycle_test.go @@ -34,6 +34,15 @@ func TestValidateRunAutonomousLifecyclePlanRejectsUnsafeDataTargets(t *testing.T } } +func TestValidateRunAutonomousLifecyclePlanRejectsLegacySteamCMDAppStep(t *testing.T) { + plan := validAutonomousLifecyclePlanForTest() + plan.InstallPlans = []DependencyInstallPlan{{Key: "install-game", Title: "Install game", Steps: []DependencyInstallStep{{Type: "steamcmd-app", TargetKey: "server/install-root", PackageManager: "steamcmd", PackageName: "123456"}}}} + + if err := ValidateRunAutonomousLifecyclePlan(plan); err == nil { + t.Fatal("expected legacy steamcmd-app install step to be rejected") + } +} + func validAutonomousLifecyclePlanForTest() RunAutonomousLifecyclePlan { return RunAutonomousLifecyclePlan{SchemaVersion: "1", ServerInstanceID: "server-1", PluginID: "game.example", PluginVersion: "1.0.0", RunEndpointID: "run-1", ProfileKey: "run-local", TargetOS: "windows", TargetArch: "amd64", TargetRelease: "release-1", Bootstrap: &RunAutonomousLifecycleAction{Action: "start", Operation: "start", Capability: RunCapabilityProcessStart, TargetKey: "actions/start.json"}} } diff --git a/protocol/job.go b/protocol/job.go index 373f29c..1afc8fa 100644 --- a/protocol/job.go +++ b/protocol/job.go @@ -229,17 +229,17 @@ type ServerDeploymentEvidence struct { // accepted as part of a scoped process.start job; Run never fetches a mutable // plugin declaration on its own. type RuntimeDLLExtensionPlan struct { - Key string `json:"key"` - Version string `json:"version"` - ReleaseURL string `json:"releaseUrl"` - Checksum string `json:"checksum"` - SizeBytes int64 `json:"sizeBytes"` - TargetKey string `json:"targetKey"` - ModKey string `json:"modKey"` - DLLRef string `json:"dllRef"` - SCUMExecutableChecksum string `json:"scumExecutableChecksum"` - UE4SSABI string `json:"ue4ssAbi"` - RCONPort int `json:"rconPort"` + Key string `json:"key"` + Version string `json:"version"` + ReleaseURL string `json:"releaseUrl"` + Checksum string `json:"checksum"` + SizeBytes int64 `json:"sizeBytes"` + TargetKey string `json:"targetKey"` + ModKey string `json:"modKey"` + DLLRef string `json:"dllRef"` + TargetExecutableChecksum string `json:"targetExecutableChecksum"` + UE4SSABI string `json:"ue4ssAbi"` + RCONPort int `json:"rconPort"` } // RuntimeLogSourcePlan is a Platform-frozen, logical file log declaration. It diff --git a/protocol/job.md b/protocol/job.md index ed6ec89..68901d9 100644 --- a/protocol/job.md +++ b/protocol/job.md @@ -59,7 +59,7 @@ Run distribution and runtime support jobs use the same lightweight job lifecycle - `run.self-update`: downloads an approved same-server target-matched distribution in bounded resumable ranges, verifies the final checksum, safely extracts exactly the expected executable, preserves config, and reports a rollback-safe staged result. A helper activates only after result acceptance, then waits for health and restores the previous binary on timeout. - `dependencies.check`: runs a plugin-declared typed dependency probe addressed by a logical `dependencies/...` key. -- `dependencies.install`: runs only an approved typed install plan addressed by `dependencies/install/...`; package, verified HTTPS download, SteamCMD, and manual steps are closed adapters, and arbitrary shell snippets, unsafe URLs/tokens, and unsupported targets are rejected. +- `dependencies.install`: runs only an approved typed install plan addressed by `dependencies/install/...`; package, verified HTTPS download, and manual steps are closed adapters, and arbitrary shell snippets, unsafe URLs/tokens, and unsupported targets are rejected. - `logs.backfill`: advances historical log cursors for declared process, file, FTP, SQL, or plugin-specific sources and returns bounded cursor/result refs instead of log bodies. The executor resolves lifecycle action templates under the scoped server workspace and runs direct command/argument vectors through the process supervisor. It does not run unrestricted shell strings, execute arbitrary plugin code, expose host paths, return raw credentials, open direct sockets, or embed logs/artifacts in job result payloads. diff --git a/protocol/job_validation.go b/protocol/job_validation.go index a0802d3..580ea04 100644 --- a/protocol/job_validation.go +++ b/protocol/job_validation.go @@ -315,7 +315,7 @@ func validateRuntimeDLLExtensionPlan(plan RuntimeDLLExtensionPlan) error { if !ValidLogicalFileKey(plan.Key) || !ValidLogicalFileKey(plan.TargetKey) || !validExtensionVersion(plan.Version) { return ValidationError("DLL extension identity is not allowed") } - if !validRuntimeDLLURL(plan.ReleaseURL) || !validSHA256(plan.Checksum) || !validSHA256(plan.SCUMExecutableChecksum) || plan.SizeBytes < 1 || plan.SizeBytes > maxRunDLLExtensionBytes { + if !validRuntimeDLLURL(plan.ReleaseURL) || !validSHA256(plan.Checksum) || !validSHA256(plan.TargetExecutableChecksum) || plan.SizeBytes < 1 || plan.SizeBytes > maxRunDLLExtensionBytes { return ValidationError("DLL extension release integrity is not allowed") } if !validDLLModKey(plan.ModKey) || plan.DLLRef != "ue4ss/Mods/"+plan.ModKey+"/dlls/main.dll" { diff --git a/protocol/job_validation_test.go b/protocol/job_validation_test.go index 9abde91..c20a02a 100644 --- a/protocol/job_validation_test.go +++ b/protocol/job_validation_test.go @@ -64,14 +64,14 @@ func TestValidateRunJobAssignmentRequiresBoundedSQLiteSchemaProbe(t *testing.T) func TestValidateRunJobAssignmentRejectsLegacyGameSpecificDeploymentPlan(t *testing.T) { assignment := RunJobAssignment{ - JobID: "job-legacy-scum-plan", + JobID: "job-legacy-game-plan", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityProcessInstall, - IdempotencyKey: "idem-legacy-scum", + IdempotencyKey: "idem-legacy-game-plan", ExecutionInput: RunJobExecutionInput{ - Deployment: &ServerDeploymentExecution{SchemaVersion: "1", Mode: "guided-install", ServerRoot: "C:/scumserver", Revision: 1}, - ServerDeploymentPlan: &ServerDeploymentPlan{SchemaVersion: "1", PluginID: "game.scum", TemplateKey: "scum-steamcmd-windows"}, + Deployment: &ServerDeploymentExecution{SchemaVersion: "1", Mode: "guided-install", ServerRoot: "C:/game-server", Revision: 1}, + ServerDeploymentPlan: &ServerDeploymentPlan{SchemaVersion: "1", PluginID: "game.example", TemplateKey: "legacy-game-template"}, }, } @@ -361,16 +361,16 @@ func TestValidateProtectedRequestAssignmentAndOneTimeInput(t *testing.T) { func validRuntimeDLLExtensionPlan() RuntimeDLLExtensionPlan { return RuntimeDLLExtensionPlan{ - Key: "scum-simple-rcon", - Version: "1.0.0", - ReleaseURL: "https://cdn.npc0.com/scum_simple_rcon_ue4s.dll", - Checksum: "sha256:" + strings.Repeat("a", 64), - SizeBytes: 1024, - TargetKey: "ue4ss/scum-simple-rcon", - ModKey: "scum_simple_rcon", - DLLRef: "ue4ss/Mods/scum_simple_rcon/dlls/main.dll", - SCUMExecutableChecksum: "sha256:" + strings.Repeat("b", 64), - UE4SSABI: "ue4ss-3.0", - RCONPort: 27015, + Key: "scum-simple-rcon", + Version: "1.0.0", + ReleaseURL: "https://cdn.npc0.com/scum_simple_rcon_ue4s.dll", + Checksum: "sha256:" + strings.Repeat("a", 64), + SizeBytes: 1024, + TargetKey: "ue4ss/scum-simple-rcon", + ModKey: "scum_simple_rcon", + DLLRef: "ue4ss/Mods/scum_simple_rcon/dlls/main.dll", + TargetExecutableChecksum: "sha256:" + strings.Repeat("b", 64), + UE4SSABI: "ue4ss-3.0", + RCONPort: 27015, } } diff --git a/runtime/autonomous_lifecycle_test.go b/runtime/autonomous_lifecycle_test.go index b6c0783..41be45b 100644 --- a/runtime/autonomous_lifecycle_test.go +++ b/runtime/autonomous_lifecycle_test.go @@ -170,8 +170,8 @@ func TestWorkerDefersUnmatchedRequiredDependencyToBootstrap(t *testing.T) { TargetArch: runtime.GOARCH, TargetRelease: "run-dist-test", Bootstrap: &protocol.RunAutonomousLifecycleAction{Action: "start", Operation: "start", Capability: protocol.RunCapabilityProcessStart, TargetKey: "actions/start.json"}, - DependencyProbes: []protocol.DependencyProbe{{Key: "steamcmd", Kind: "command.version", TargetKey: "steamcmd", Required: true, Platforms: []string{runtime.GOOS}}}, - InstallPlans: []protocol.DependencyInstallPlan{{Key: "install-game-server", Title: "Install game server", Platforms: []string{runtime.GOOS}, Steps: []protocol.DependencyInstallStep{{Type: "steamcmd-app", TargetKey: "server/install-root", PackageManager: "steamcmd", PackageName: "3792580"}}}}, + DependencyProbes: []protocol.DependencyProbe{{Key: "game-runtime", Kind: "command.version", TargetKey: "game-runtime", Required: true, Platforms: []string{runtime.GOOS}}}, + InstallPlans: []protocol.DependencyInstallPlan{{Key: "install-game-server", Title: "Install game server", Platforms: []string{runtime.GOOS}, Steps: []protocol.DependencyInstallStep{{Type: "package", TargetKey: "game-server", PackageManager: "apt", PackageName: "game-server-runtime"}}}}, }) managed := &recordingManagedSupervisor{} worker, err := NewWorker(cfg, client, WithManagedProcessSupervisor(managed), WithDependencyCommandRunner(missingCommandRunner{})) diff --git a/runtime/dependencies.go b/runtime/dependencies.go index 49e4add..6c6b3c9 100644 --- a/runtime/dependencies.go +++ b/runtime/dependencies.go @@ -31,7 +31,6 @@ const ( var ( dependencyTokenPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_.:+@/-]{0,119}$`) dependencyVersionPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_.:+~-]{0,79}$`) - steamAppPattern = regexp.MustCompile(`^[0-9]{1,12}$`) ) type DependencyDownloader interface { @@ -216,9 +215,23 @@ func validateDependencyInput(assignment protocol.RunJobAssignment, input protoco if assignment.Capability != protocol.RunCapabilityDependenciesInstall || assignment.TargetKey != "dependencies/install/"+input.Plan.Key || !protocol.ValidLogicalFileKey(input.Plan.Key) || len(input.Plan.Steps) == 0 || len(input.Plan.Steps) > 64 { return fmt.Errorf("dependency install declaration does not match job") } + for _, step := range input.Plan.Steps { + if !validDependencyInstallStepType(step.Type) { + return fmt.Errorf("dependency install step type is unsupported") + } + } return nil } +func validDependencyInstallStepType(value string) bool { + switch value { + case "package", "verified-download", "manual": + return true + default: + return false + } +} + func (executor LifecycleExecutor) runDependencyProbe(ctx context.Context, probe protocol.DependencyProbe, bindings map[string]string) (string, string, error) { target := strings.TrimSpace(bindings[probe.TargetKey]) if target == "" { @@ -308,25 +321,6 @@ func (executor LifecycleExecutor) runDependencyInstallStep(ctx context.Context, return fmt.Errorf("typed package adapter failed") } return nil - case "steamcmd-app": - if !steamAppPattern.MatchString(step.PackageName) { - return fmt.Errorf("Steam app identifier is unsafe") - } - executable := strings.TrimSpace(input.Bindings[step.TargetKey]) - if executable == "" { - executable = "steamcmd" - } - if err := validateDependencyExecutable(executable); err != nil { - return err - } - result, runErr := executor.dependencyRunner.Run(ctx, ProcessCommand{Args: []string{executable, "+login", "anonymous", "+app_update", step.PackageName, "validate", "+quit"}, Timeout: dependencyCommandTimeout, JobID: assignment.JobID, Capability: assignment.Capability, Action: "dependency.steamcmd-app"}) - if runErr != nil || result.ExitCode != 0 { - if ctx.Err() != nil { - return ctx.Err() - } - return fmt.Errorf("typed SteamCMD adapter failed") - } - return nil case "verified-download": if !validSHA256(step.Checksum) { return fmt.Errorf("verified download checksum is required") diff --git a/runtime/dependencies_test.go b/runtime/dependencies_test.go index 709b913..e5428f5 100644 --- a/runtime/dependencies_test.go +++ b/runtime/dependencies_test.go @@ -155,6 +155,16 @@ func TestVerifiedDependencyDownloadUsesHTTPSChecksumAndScopedDestination(t *test } } +func TestDependencyInstallRejectsLegacySteamCMDAppAdapter(t *testing.T) { + executor := NewLifecycleExecutor(WithLifecycleWorkspaceRoot(t.TempDir()), WithDependencyCommandRunner(&dependencyTestSupervisor{})) + assignment := dependencyAssignment(protocol.RunCapabilityDependenciesInstall) + input := dependencyInputForAssignment(assignment) + step := protocol.DependencyInstallStep{Type: "steamcmd-app", TargetKey: "server/install-root", PackageManager: "steamcmd", PackageName: "123456"} + if err := executor.runDependencyInstallStep(context.Background(), assignment, input, step, 0); err == nil || !strings.Contains(err.Error(), "unsupported") { + t.Fatalf("expected legacy SteamCMD app adapter rejection, got %v", err) + } +} + func dependencyAssignment(capability string) protocol.RunJobAssignment { assignment := workerJobAssignment(capability) assignment.LeaseToken = "lease-dependency" diff --git a/runtime/distribution_build_test.go b/runtime/distribution_build_test.go index 2b506ee..ff5cfb7 100644 --- a/runtime/distribution_build_test.go +++ b/runtime/distribution_build_test.go @@ -51,7 +51,7 @@ func TestWorkerDistributionBuildCompilesAndUploadsRawRunExecutable(t *testing.T) TargetOS: runtime.GOOS, TargetArch: runtime.GOARCH, TargetRelease: "run-release-test", - PlatformURL: "https://scum.npc0.com", + PlatformURL: "https://platform.example.test", PackageFormat: "raw-executable", ArtifactID: "artifact-built-run", OutputFilename: output, @@ -85,7 +85,7 @@ func TestWorkerDistributionBuildCompilesAndUploadsRawRunExecutable(t *testing.T) if summary["status"] != "ok" || summary["mode"] != "smoke" { t.Fatalf("expected generated package to run smoke mode, got %+v", summary) } - if summary["platformUrl"] != "https://scum.npc0.com" { + if summary["platformUrl"] != "https://platform.example.test" { t.Fatalf("expected generated executable to use compiled platform URL, got %+v", summary) } joinedProgress := make([]string, 0, len(client.progressRequests)) @@ -111,7 +111,7 @@ func TestWorkerDistributionBuildCrossCompilesWindowsAMD64Run(t *testing.T) { worker.state.SessionToken = "session-token" assignment := protocol.RunJobAssignment{JobID: "job-distribution-build-windows", ServerInstanceID: "server-build-windows", RunEndpointID: "run-builder", Capability: protocol.RunCapabilityDistributionBuild, TargetKey: "distribution/run", InputRef: "input://distribution-build/windows", IdempotencyKey: "distribution-build:windows", State: "running", LeaseToken: "lease-windows", Attempt: 1} client.claimJob = assignment - client.buildInput = protocol.DistributionBuildInputResponse{JobID: assignment.JobID, ComponentKind: "run", ServerInstanceID: assignment.ServerInstanceID, PluginID: "game.scum", RunEndpointID: "server-run-server-build-windows", TargetOS: "windows", TargetArch: "amd64", TargetRelease: "run-release-windows", PlatformURL: "https://scum.npc0.com", PackageFormat: "raw-executable", ArtifactID: "artifact-built-windows-run", OutputFilename: "run.exe", SecretRef: "secret://runtime-keys/server-build-windows/run/current", KeyGeneration: 1, AuthKey: "test-component-key"} + client.buildInput = protocol.DistributionBuildInputResponse{JobID: assignment.JobID, ComponentKind: "run", ServerInstanceID: assignment.ServerInstanceID, PluginID: "game.scum", RunEndpointID: "server-run-server-build-windows", TargetOS: "windows", TargetArch: "amd64", TargetRelease: "run-release-windows", PlatformURL: "https://platform.example.test", PackageFormat: "raw-executable", ArtifactID: "artifact-built-windows-run", OutputFilename: "run.exe", SecretRef: "secret://runtime-keys/server-build-windows/run/current", KeyGeneration: 1, AuthKey: "test-component-key"} result := worker.executeDistributionBuild(context.Background(), assignment) if result.State != lifecycleResultStateSucceeded || result.ResultRef != "artifact://artifact-built-windows-run" { @@ -135,7 +135,7 @@ func TestDistributionBuildIsolationUsesPluginJobWorkspaceAndDistinctPackageState ProfileKey: "run-local", TargetOS: "linux", TargetArch: "amd64", - PlatformURL: "https://scum.npc0.com", + PlatformURL: "https://platform.example.test", PackageFormat: "raw-executable", ArtifactID: "artifact-run-dist-scum-alpha", OutputFilename: "run", @@ -158,8 +158,8 @@ func TestDistributionBuildIsolationUsesPluginJobWorkspaceAndDistinctPackageState if filepath.Dir(firstWorkspace) != filepath.Dir(secondWorkspace) || filepath.Base(filepath.Dir(firstWorkspace)) != "game-scum" { t.Fatalf("expected workspaces under the same plugin queue directory, first=%s second=%s", firstWorkspace, secondWorkspace) } - firstFlags := buildRunLDFlags(firstInput, "https://scum.npc0.com") - secondFlags := buildRunLDFlags(secondInput, "https://scum.npc0.com") + firstFlags := buildRunLDFlags(firstInput, "https://platform.example.test") + secondFlags := buildRunLDFlags(secondInput, "https://platform.example.test") if !strings.Contains(firstFlags, "BuildServerInstanceID=scum-alpha") || !strings.Contains(firstFlags, "BuildRegistrationToken=alpha-component-key") { t.Fatalf("expected first build flags to carry first server identity, got %q", firstFlags) } @@ -255,7 +255,7 @@ func TestValidateDistributionBuildInputRejectsLegacyClientManagerBuilds(t *testi func TestValidateDistributionBuildInputAllowsDedicatedRunIdentity(t *testing.T) { assignment := protocol.RunJobAssignment{JobID: "job-build", ServerInstanceID: "server-build", RunEndpointID: "run-local-debug"} - input := protocol.DistributionBuildInputResponse{JobID: assignment.JobID, ComponentKind: "run", ServerInstanceID: assignment.ServerInstanceID, RunEndpointID: "server-run-server-build", PluginID: "game.scum", TargetOS: "windows", TargetArch: "amd64", TargetRelease: "release-1", PlatformURL: "https://scum.npc0.com", PackageFormat: "raw-executable", ArtifactID: "artifact-build", OutputFilename: "run.exe", AuthKey: "key"} + input := protocol.DistributionBuildInputResponse{JobID: assignment.JobID, ComponentKind: "run", ServerInstanceID: assignment.ServerInstanceID, RunEndpointID: "server-run-server-build", PluginID: "game.scum", TargetOS: "windows", TargetArch: "amd64", TargetRelease: "release-1", PlatformURL: "https://platform.example.test", PackageFormat: "raw-executable", ArtifactID: "artifact-build", OutputFilename: "run.exe", AuthKey: "key"} if err := validateDistributionBuildInput(assignment, input); err != nil { t.Fatalf("dedicated Run identity must be accepted for a builder job: %v", err) } diff --git a/runtime/execution_test.go b/runtime/execution_test.go index 9aa895f..895d96e 100644 --- a/runtime/execution_test.go +++ b/runtime/execution_test.go @@ -639,7 +639,7 @@ func TestDeploymentFileExecutorUsesServerRoot(t *testing.T) { if err := os.WriteFile(filepath.Join(workspaceRoot, ".platform"), []byte("workspace"), 0o600); err != nil { t.Fatalf("write workspace marker: %v", err) } - if err := os.WriteFile(filepath.Join(serverRoot, "SCUMServer.exe"), []byte("server"), 0o600); err != nil { + if err := os.WriteFile(filepath.Join(serverRoot, "GameServer.exe"), []byte("server"), 0o600); err != nil { t.Fatalf("write server marker: %v", err) } executor, err := NewFileExecutor(workspaceRoot) @@ -658,7 +658,7 @@ func TestDeploymentFileExecutorUsesServerRoot(t *testing.T) { if err := json.Unmarshal([]byte(result.ExecutionResult.Content), &envelope); err != nil { t.Fatalf("decode deployment listing: %v", err) } - if len(envelope.Entries) != 1 || envelope.Entries[0].Name != "SCUMServer.exe" { + if len(envelope.Entries) != 1 || envelope.Entries[0].Name != "GameServer.exe" { t.Fatalf("expected only deployment root entry, got %+v", envelope.Entries) } } diff --git a/runtime/lifecycle_test.go b/runtime/lifecycle_test.go index 7728806..5fb605c 100644 --- a/runtime/lifecycle_test.go +++ b/runtime/lifecycle_test.go @@ -60,8 +60,8 @@ func TestGeneratedRunOmitsBuildOnlyCapabilities(t *testing.T) { func TestLifecycleExecutorRejectsLegacyGameSpecificDeploymentPlan(t *testing.T) { assignment := lifecycleAssignment(protocol.RunCapabilityProcessInstall) - assignment.ExecutionInput.Deployment = &protocol.ServerDeploymentExecution{SchemaVersion: "1", Mode: "guided-install", ServerRoot: "C:/scumserver", Revision: 1} - assignment.ExecutionInput.ServerDeploymentPlan = &protocol.ServerDeploymentPlan{SchemaVersion: "1", PluginID: "game.scum", TemplateKey: "scum-steamcmd-windows"} + assignment.ExecutionInput.Deployment = &protocol.ServerDeploymentExecution{SchemaVersion: "1", Mode: "guided-install", ServerRoot: "C:/game-server", Revision: 1} + assignment.ExecutionInput.ServerDeploymentPlan = &protocol.ServerDeploymentPlan{SchemaVersion: "1", PluginID: "game.example", TemplateKey: "legacy-game-template"} result := NewLifecycleExecutor().Execute(assignment) diff --git a/runtime/ue4ss_dll_extension.go b/runtime/ue4ss_dll_extension.go index a82d704..c713032 100644 --- a/runtime/ue4ss_dll_extension.go +++ b/runtime/ue4ss_dll_extension.go @@ -24,7 +24,7 @@ const ( ue4ssExtensionMarkerVersion = 2 maxUE4SSMetadataBytes int64 = 16 * 1024 maxUE4SSDLLBytes int64 = 128 * 1024 * 1024 - maxSCUMExecutableBytes int64 = 2 * 1024 * 1024 * 1024 + maxTargetExecutableBytes int64 = 2 * 1024 * 1024 * 1024 managedRCONConfigMarker = "; managed by Run UE4SS DLL extension" ) @@ -56,7 +56,7 @@ func (executor LifecycleExecutor) synchronizeUE4SSDLLExtensions(ctx context.Cont } executableKey := template.TargetExecutableKey // Older generated plugin packages do not have targetExecutableKey yet, - // but SCUM's existing start action already carries the same logical path + // but the existing start action already carries the same logical path // in SERVER_EXECUTABLE_REF. Keep those packages forward-compatible. if executableKey == "" && template.Environment != nil { executableKey = template.Environment["SERVER_EXECUTABLE_REF"] @@ -68,7 +68,7 @@ func (executor LifecycleExecutor) synchronizeUE4SSDLLExtensions(ctx context.Cont executableKey = template.ExecutableKey } if executableKey == "" || !strings.HasSuffix(strings.ToLower(executableKey), ".exe") { - return dllExtensionError{code: "extension_scum_executable_invalid", message: "UE4SS DLL extensions require a declared SCUM executable"} + return dllExtensionError{code: "extension_target_executable_invalid", message: "UE4SS DLL extensions require a declared target executable"} } resolver := NewWorkspaceResolver(executor.workspaceRoot) @@ -76,28 +76,28 @@ func (executor LifecycleExecutor) synchronizeUE4SSDLLExtensions(ctx context.Cont if deployment := assignment.ExecutionInput.Deployment; deployment != nil && deployment.ServerRoot != "" { root := filepath.Clean(deployment.ServerRoot) if root == "." || !filepath.IsAbs(root) { - return dllExtensionError{code: "extension_scum_executable_invalid", message: "declared executable root is unsafe"} + return dllExtensionError{code: "extension_target_executable_invalid", message: "declared executable root is unsafe"} } targetResolver = NewWorkspaceResolver(filepath.Dir(root)) targetScope = root } executable, err := declaredLifecycleExecutable(targetResolver, targetScope, executableKey) if err != nil { - return dllExtensionError{code: "extension_scum_executable_invalid", message: "declared SCUM executable is unavailable"} + return dllExtensionError{code: "extension_target_executable_invalid", message: "declared target executable is unavailable"} } - executableChecksum, _, err := checksumRegularFile(executable, maxSCUMExecutableBytes) + executableChecksum, _, err := checksumRegularFile(executable, maxTargetExecutableBytes) if err != nil { - return dllExtensionError{code: "extension_scum_executable_invalid", message: "declared SCUM executable cannot be verified"} + return dllExtensionError{code: "extension_target_executable_invalid", message: "declared target executable cannot be verified"} } for _, plan := range assignment.ExecutionInput.DLLExtensions { - if !strings.EqualFold(executableChecksum, plan.SCUMExecutableChecksum) { - return dllExtensionError{code: "extension_scum_checksum_mismatch", message: "declared SCUM executable does not match the extension release"} + if !strings.EqualFold(executableChecksum, plan.TargetExecutableChecksum) { + return dllExtensionError{code: "extension_target_checksum_mismatch", message: "declared target executable does not match the extension release"} } } gameRootKey, err := gameRootKeyForExecutable(template.ExecutableKey) if err != nil { - return dllExtensionError{code: "extension_scum_executable_invalid", message: "declared SCUM executable location is unsafe"} + return dllExtensionError{code: "extension_target_executable_invalid", message: "declared target executable location is unsafe"} } if err := verifyUE4SSBootstrap(targetResolver, targetScope, gameRootKey); err != nil { return err diff --git a/runtime/ue4ss_dll_extension_test.go b/runtime/ue4ss_dll_extension_test.go index fc10c75..8cf6b51 100644 --- a/runtime/ue4ss_dll_extension_test.go +++ b/runtime/ue4ss_dll_extension_test.go @@ -164,8 +164,8 @@ func TestUE4SSDLLExtensionSynchronizesNoOpsUpdatesAndKeepsPriorRelease(t *testin t.Fatalf("expected exactly one enabled managed mod entry, mods=%q", mods) } command := supervisor.Command() - if len(command.Args) != 1 || filepath.Base(command.Args[0]) != "SCUMServer.exe" || strings.HasSuffix(strings.ToLower(command.Args[0]), ".dll") { - t.Fatalf("expected normal SCUM executable start without a DLL loader, command=%+v", command) + if len(command.Args) != 1 || filepath.Base(command.Args[0]) != "GameServer.exe" || strings.HasSuffix(strings.ToLower(command.Args[0]), ".dll") { + t.Fatalf("expected normal game executable start without a DLL loader, command=%+v", command) } second := executor.Execute(fixture.assignment) @@ -368,26 +368,26 @@ func newUE4SSExtensionFixture(t *testing.T, root string, bootstrap bool) ue4ssEx if err != nil { t.Fatalf("create workspace scope: %v", err) } - writeUE4SSFixtureFile(t, filepath.Join(scope, "actions", "start.json"), []byte(`{"version":1,"action":"start","mode":"supervised","executableKey":"bin/SCUMServer.exe"}`), 0o600) - executable := []byte("SCUM server executable fixture") - writeUE4SSFixtureFile(t, filepath.Join(scope, "bin", "SCUMServer.exe"), executable, 0o700) + writeUE4SSFixtureFile(t, filepath.Join(scope, "actions", "start.json"), []byte(`{"version":1,"action":"start","mode":"supervised","executableKey":"bin/GameServer.exe"}`), 0o600) + executable := []byte("game server executable fixture") + writeUE4SSFixtureFile(t, filepath.Join(scope, "bin", "GameServer.exe"), executable, 0o700) if bootstrap { writeUE4SSFixtureFile(t, filepath.Join(scope, "bin", "dwmapi.dll"), []byte("UE4SS proxy fixture"), 0o600) writeUE4SSFixtureFile(t, filepath.Join(scope, "bin", "UE4SS.dll"), []byte("UE4SS loader fixture"), 0o600) writeUE4SSFixtureFile(t, filepath.Join(scope, "bin", "ue4ss", "Mods", "mods.txt"), []byte("OtherMod : 1\nscum_simple_rcon : 0\nscum_simple_rcon : 1\n"), 0o600) } plan := protocol.RuntimeDLLExtensionPlan{ - Key: "scum-simple-rcon", - Version: "1.0.0", - ReleaseURL: "https://cdn.npc0.com/scum_simple_rcon_ue4s.dll", - Checksum: bytesChecksum([]byte("scum-simple-rcon DLL release one")), - SizeBytes: int64(len([]byte("scum-simple-rcon DLL release one"))), - TargetKey: "ue4ss/scum-simple-rcon", - ModKey: "scum_simple_rcon", - DLLRef: "ue4ss/Mods/scum_simple_rcon/dlls/main.dll", - SCUMExecutableChecksum: bytesChecksum(executable), - UE4SSABI: "ue4ss-3.0", - RCONPort: 27015, + Key: "scum-simple-rcon", + Version: "1.0.0", + ReleaseURL: "https://cdn.npc0.com/scum_simple_rcon_ue4s.dll", + Checksum: bytesChecksum([]byte("scum-simple-rcon DLL release one")), + SizeBytes: int64(len([]byte("scum-simple-rcon DLL release one"))), + TargetKey: "ue4ss/scum-simple-rcon", + ModKey: "scum_simple_rcon", + DLLRef: "ue4ss/Mods/scum_simple_rcon/dlls/main.dll", + TargetExecutableChecksum: bytesChecksum(executable), + UE4SSABI: "ue4ss-3.0", + RCONPort: 27015, } assignment.ExecutionInput.DLLExtensions = []protocol.RuntimeDLLExtensionPlan{plan} return ue4ssExtensionFixture{