feat: declare and execute SCUM guided installs
This commit is contained in:
@@ -42,6 +42,15 @@ func scumDeploymentPlan(plugin domain.GamePlugin, definition domain.ServerDeploy
|
||||
if strings.TrimSpace(profile.Key) == "" || strings.TrimSpace(profile.Version) == "" || profile.SteamAppID == "" {
|
||||
return nil, validationError("SCUM deployment template is incomplete")
|
||||
}
|
||||
expectedPrerequisites := map[string]string{"steamcmd": "steamcmd", "vcredist-2012-x86": "windows-vcredist", "vcredist-2012-x64": "windows-vcredist", "vcredist-2013-x86": "windows-vcredist", "vcredist-2013-x64": "windows-vcredist", "vcredist-2015-2022-x86": "windows-vcredist", "vcredist-2015-2022-x64": "windows-vcredist", "directx-jun2010": "windows-directx"}
|
||||
if len(profile.Prerequisites) != len(expectedPrerequisites) {
|
||||
return nil, validationError("SCUM deployment template prerequisite list is incomplete")
|
||||
}
|
||||
for _, prerequisite := range profile.Prerequisites {
|
||||
if expectedPrerequisites[prerequisite.Key] != prerequisite.Kind {
|
||||
return nil, validationError("SCUM deployment template prerequisite is invalid")
|
||||
}
|
||||
}
|
||||
fieldKeys := make(map[string]struct{}, len(plugin.CreateFields))
|
||||
for _, field := range plugin.CreateFields {
|
||||
fieldKeys[field.Key] = struct{}{}
|
||||
@@ -70,6 +79,7 @@ func scumDeploymentPlan(plugin domain.GamePlugin, definition domain.ServerDeploy
|
||||
InstallRootKey: profile.InstallRootKey,
|
||||
ConfigKey: profile.ConfigKey,
|
||||
ConfigFormat: profile.ConfigFormat,
|
||||
Prerequisites: append([]domain.RuntimeServerPrerequisite(nil), profile.Prerequisites...),
|
||||
ConfigMappings: append([]domain.RuntimeServerConfigMapping(nil), profile.ConfigMappings...),
|
||||
DiscoveryMarkers: append([]domain.RuntimeServerDiscoveryMarker(nil), profile.DiscoveryMarkers...),
|
||||
VerificationChecks: append([]domain.RuntimeServerVerificationCheck(nil), profile.VerificationChecks...),
|
||||
|
||||
@@ -17,6 +17,7 @@ func scumDeploymentTestPlugin() domain.GamePlugin {
|
||||
},
|
||||
RuntimeProfiles: domain.GamePluginRuntimeProfiles{ServerDeployments: []domain.RuntimeServerDeploymentProfile{{
|
||||
Key: "scum-steamcmd-windows", Version: "1.0.0", SteamAppID: "3792580", ExecutableKey: "scum/server-executable", InstallRootKey: "server/install-root", ConfigKey: "scum/server-settings", ConfigFormat: "ini",
|
||||
Prerequisites: []domain.RuntimeServerPrerequisite{{Key: "steamcmd", Kind: "steamcmd"}, {Key: "vcredist-2012-x86", Kind: "windows-vcredist"}, {Key: "vcredist-2012-x64", Kind: "windows-vcredist"}, {Key: "vcredist-2013-x86", Kind: "windows-vcredist"}, {Key: "vcredist-2013-x64", Kind: "windows-vcredist"}, {Key: "vcredist-2015-2022-x86", Kind: "windows-vcredist"}, {Key: "vcredist-2015-2022-x64", Kind: "windows-vcredist"}, {Key: "directx-jun2010", Kind: "windows-directx"}},
|
||||
ConfigMappings: []domain.RuntimeServerConfigMapping{{FieldKey: "serverName", ConfigKey: "server-settings.server-name", ValueType: "text", Required: true}, {FieldKey: "gamePort", ConfigKey: "server-settings.game-port", ValueType: "port", Required: true}, {FieldKey: "queryPort", ConfigKey: "server-settings.query-port", ValueType: "port", Required: true}, {FieldKey: "maxPlayers", ConfigKey: "server-settings.max-players", ValueType: "integer", Required: true}},
|
||||
VerificationChecks: []domain.RuntimeServerVerificationCheck{{Key: "executable", Kind: "executable.present", TargetKey: "scum/server-executable", Required: true}, {Key: "version", Kind: "version.matches", TargetKey: "scum/server-executable", Required: true}, {Key: "game-port", Kind: "port.bound", TargetKey: "game-port", Required: true}, {Key: "config", Kind: "config.readable", TargetKey: "scum/server-settings", Required: true}, {Key: "process", Kind: "process.healthy", TargetKey: "scum/server-executable", Required: true}},
|
||||
}}},
|
||||
|
||||
@@ -38,6 +38,13 @@ func (svc *CoreService) UpdateServerDeploymentForSession(sessionID, serverInstan
|
||||
if instance.State == domain.ServerInstanceStateInstalling || instance.State == domain.ServerInstanceStateRunning || instance.State == domain.ServerInstanceStateDeleted {
|
||||
return domain.ServerDeploymentView{}, validationError("deployment definition cannot be changed while the server is active")
|
||||
}
|
||||
seenClearFields := map[string]bool{}
|
||||
for _, field := range update.ClearFields {
|
||||
if seenClearFields[field] || field != "serverRoot" && field != "workingDirectory" && field != "installCommand" && field != "startCommand" && field != "stopCommand" && field != "statusCommand" && field != "shell" {
|
||||
return domain.ServerDeploymentView{}, validationError("deployment clearFields contains an invalid field")
|
||||
}
|
||||
seenClearFields[field] = true
|
||||
}
|
||||
definition := mergeDeploymentDefinition(instance.Deployment, update)
|
||||
definition.Revision = instance.Deployment.Revision + 1
|
||||
definition.UpdatedAt = svc.now()
|
||||
@@ -194,6 +201,24 @@ func deploymentShellCapability(shell domain.ServerCommandShell) string {
|
||||
|
||||
func mergeDeploymentDefinition(current domain.ServerDeploymentDefinition, update domain.ServerDeploymentUpdate) domain.ServerDeploymentDefinition {
|
||||
definition := domain.CopyServerDeploymentDefinition(current)
|
||||
for _, field := range update.ClearFields {
|
||||
switch field {
|
||||
case "serverRoot":
|
||||
definition.ServerRoot = ""
|
||||
case "workingDirectory":
|
||||
definition.WorkingDirectory = ""
|
||||
case "installCommand":
|
||||
definition.InstallCommand = ""
|
||||
case "startCommand":
|
||||
definition.StartCommand = ""
|
||||
case "stopCommand":
|
||||
definition.StopCommand = ""
|
||||
case "statusCommand":
|
||||
definition.StatusCommand = ""
|
||||
case "shell":
|
||||
definition.Shell = domain.ServerCommandShellNone
|
||||
}
|
||||
}
|
||||
definition.Mode = update.Mode
|
||||
if update.ProfileKey != "" {
|
||||
definition.ProfileKey = update.ProfileKey
|
||||
@@ -222,7 +247,9 @@ func mergeDeploymentDefinition(current domain.ServerDeploymentDefinition, update
|
||||
if update.StatusCommand != "" {
|
||||
definition.StatusCommand = update.StatusCommand
|
||||
}
|
||||
definition.Shell = update.Shell
|
||||
if update.ShellSet {
|
||||
definition.Shell = update.Shell
|
||||
}
|
||||
return definition
|
||||
}
|
||||
|
||||
|
||||
@@ -64,3 +64,15 @@ func TestCoreServiceSavesDraftDeploymentRedactsReadsAndDispatchesOnlyToCompatibl
|
||||
t.Fatalf("expected safe dispatch evidence, view=%+v err=%v", view, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeDeploymentPreservesOmittedShellAndClearsOnlyExplicitFields(t *testing.T) {
|
||||
current := domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeCustom, ServerRoot: "/srv/game", StartCommand: "./start", StopCommand: "./stop", Shell: domain.ServerCommandShellCmd}
|
||||
preserved := mergeDeploymentDefinition(current, domain.ServerDeploymentUpdate{Mode: domain.ServerDeploymentModeCustom})
|
||||
if preserved.Shell != domain.ServerCommandShellCmd || preserved.StopCommand != "./stop" {
|
||||
t.Fatalf("omitted protected fields must be preserved: %+v", preserved)
|
||||
}
|
||||
cleared := mergeDeploymentDefinition(current, domain.ServerDeploymentUpdate{Mode: domain.ServerDeploymentModeCustom, ClearFields: []string{"stopCommand", "shell"}})
|
||||
if cleared.StopCommand != "" || cleared.Shell != domain.ServerCommandShellNone || cleared.StartCommand != "./start" {
|
||||
t.Fatalf("only explicitly cleared fields may be removed: %+v", cleared)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user