add step list for server creation
Some checks failed
Release and Deploy / build (push) Failing after 1m31s
Release and Deploy / deploy (push) Has been skipped

This commit is contained in:
Fran Jurmanović
2025-09-18 22:04:51 +02:00
parent 5e7c96697a
commit a41af26187
2 changed files with 231 additions and 327 deletions

View File

@@ -308,7 +308,9 @@ func (as *ServerService) GetById(ctx *fiber.Ctx, serverID uuid.UUID) (*model.Ser
}
func (s *ServerService) CreateServerAsync(ctx *fiber.Ctx, server *model.Server) error {
logging.Info("create server start")
if err := server.Validate(); err != nil {
logging.Info("create server validation failed")
return err
}
@@ -317,6 +319,7 @@ func (s *ServerService) CreateServerAsync(ctx *fiber.Ctx, server *model.Server)
bgCtx := context.Background()
go func() {
logging.Info("create server start background")
if err := s.createServerBackground(bgCtx, server); err != nil {
logging.Error("Async server creation failed for server %s: %v", server.ID, err)
s.webSocketService.BroadcastError(server.ID, "Server creation failed", err.Error())
@@ -327,105 +330,126 @@ func (s *ServerService) CreateServerAsync(ctx *fiber.Ctx, server *model.Server)
return nil
}
func (s *ServerService) CreateServer(ctx *fiber.Ctx, server *model.Server) error {
s.webSocketService.BroadcastStep(server.ID, model.StepValidation, model.StatusInProgress,
model.GetStepDescription(model.StepValidation), "")
type createServerStep struct {
stepType model.ServerCreationStep
important bool
callback func() (string, error)
description string
}
if err := server.Validate(); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepValidation, model.StatusFailed,
"", fmt.Sprintf("Validation failed: %v", err))
return err
func (s *ServerService) createServerBackground(ctx context.Context, server *model.Server) error {
var serverPort int
var tcpPorts, udpPorts []int
steps := []createServerStep{
{
stepType: model.StepValidation,
important: true,
description: "Server configuration validated successfully",
callback: func() (string, error) {
if err := server.Validate(); err != nil {
return "", fmt.Errorf("validation failed: %v", err)
}
return "Server configuration validated successfully", nil
},
},
{
stepType: model.StepDirectoryCreation,
important: true,
description: "Server directories prepared",
callback: func() (string, error) {
return "Server directories prepared", nil
},
},
{
stepType: model.StepSteamDownload,
important: true,
description: "Server files downloaded successfully",
callback: func() (string, error) {
if err := s.steamService.InstallServerWithWebSocket(ctx, server.Path, &server.ID, s.webSocketService); err != nil {
return "", fmt.Errorf("failed to install server: %v", err)
}
return "Server files downloaded successfully", nil
},
},
{
stepType: model.StepConfigGeneration,
important: true,
description: "",
callback: func() (string, error) {
ports, err := network.FindAvailablePortRange(DefaultStartPort, RequiredPortCount)
if err != nil {
return "", fmt.Errorf("failed to find available ports: %v", err)
}
serverPort = ports[0]
if err := s.updateServerPort(server, serverPort); err != nil {
return "", fmt.Errorf("failed to update server configuration: %v", err)
}
return fmt.Sprintf("Server configuration generated (Port: %d)", serverPort), nil
},
},
{
stepType: model.StepServiceCreation,
important: true,
description: "",
callback: func() (string, error) {
execPath := filepath.Join(server.GetServerPath(), "accServer.exe")
serverWorkingDir := filepath.Join(server.GetServerPath(), "server")
if err := s.windowsService.CreateService(ctx, server.ServiceName, execPath, serverWorkingDir, nil); err != nil {
return "", fmt.Errorf("failed to create Windows service: %v", err)
}
return fmt.Sprintf("Windows service '%s' created successfully", server.ServiceName), nil
},
},
{
stepType: model.StepFirewallRules,
important: false,
description: "",
callback: func() (string, error) {
s.configureFirewall(server)
tcpPorts = []int{serverPort}
udpPorts = []int{serverPort}
if err := s.firewallService.CreateServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil {
return "", fmt.Errorf("failed to create firewall rules: %v", err)
}
return fmt.Sprintf("Firewall rules created for port %d", serverPort), nil
},
},
{
stepType: model.StepDatabaseSave,
important: true,
description: "Server saved to database successfully",
callback: func() (string, error) {
if err := s.repository.Insert(ctx, server); err != nil {
return "", fmt.Errorf("failed to insert server into database: %v", err)
}
return "Server saved to database successfully", nil
},
},
}
s.webSocketService.BroadcastStep(server.ID, model.StepValidation, model.StatusCompleted,
"Server configuration validated successfully", "")
for i, step := range steps {
s.webSocketService.BroadcastStep(server.ID, step.stepType, model.StatusInProgress,
model.GetStepDescription(step.stepType), "")
s.webSocketService.BroadcastStep(server.ID, model.StepDirectoryCreation, model.StatusInProgress,
model.GetStepDescription(model.StepDirectoryCreation), "")
successMessage, err := step.callback()
if err != nil {
s.webSocketService.BroadcastStep(server.ID, step.stepType, model.StatusFailed,
"", err.Error())
s.webSocketService.BroadcastStep(server.ID, model.StepDirectoryCreation, model.StatusCompleted,
"Server directories prepared", "")
if step.important {
s.rollbackSteps(ctx, server, steps[:i], tcpPorts, udpPorts)
return err
}
}
s.webSocketService.BroadcastStep(server.ID, model.StepSteamDownload, model.StatusInProgress,
model.GetStepDescription(model.StepSteamDownload), "")
if err := s.steamService.InstallServerWithWebSocket(ctx.UserContext(), server.Path, &server.ID, s.webSocketService); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepSteamDownload, model.StatusFailed,
"", fmt.Sprintf("Steam installation failed: %v", err))
return fmt.Errorf("failed to install server: %v", err)
s.webSocketService.BroadcastStep(server.ID, step.stepType, model.StatusCompleted,
successMessage, "")
}
s.webSocketService.BroadcastStep(server.ID, model.StepSteamDownload, model.StatusCompleted,
"Server files downloaded successfully", "")
s.webSocketService.BroadcastStep(server.ID, model.StepConfigGeneration, model.StatusInProgress,
model.GetStepDescription(model.StepConfigGeneration), "")
ports, err := network.FindAvailablePortRange(DefaultStartPort, RequiredPortCount)
if err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepConfigGeneration, model.StatusFailed,
"", fmt.Sprintf("Failed to find available ports: %v", err))
return fmt.Errorf("failed to find available ports: %v", err)
}
serverPort := ports[0]
if err := s.updateServerPort(server, serverPort); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepConfigGeneration, model.StatusFailed,
"", fmt.Sprintf("Failed to update server configuration: %v", err))
return fmt.Errorf("failed to update server configuration: %v", err)
}
s.webSocketService.BroadcastStep(server.ID, model.StepConfigGeneration, model.StatusCompleted,
fmt.Sprintf("Server configuration generated (Port: %d)", serverPort), "")
s.webSocketService.BroadcastStep(server.ID, model.StepServiceCreation, model.StatusInProgress,
model.GetStepDescription(model.StepServiceCreation), "")
execPath := filepath.Join(server.GetServerPath(), "accServer.exe")
serverWorkingDir := filepath.Join(server.GetServerPath(), "server")
if err := s.windowsService.CreateService(ctx.UserContext(), server.ServiceName, execPath, serverWorkingDir, nil); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepServiceCreation, model.StatusFailed,
"", fmt.Sprintf("Failed to create Windows service: %v", err))
s.steamService.UninstallServer(server.Path)
return fmt.Errorf("failed to create Windows service: %v", err)
}
s.webSocketService.BroadcastStep(server.ID, model.StepServiceCreation, model.StatusCompleted,
fmt.Sprintf("Windows service '%s' created successfully", server.ServiceName), "")
s.webSocketService.BroadcastStep(server.ID, model.StepFirewallRules, model.StatusInProgress,
model.GetStepDescription(model.StepFirewallRules), "")
s.configureFirewall(server)
tcpPorts := []int{serverPort}
udpPorts := []int{serverPort}
if err := s.firewallService.CreateServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepFirewallRules, model.StatusFailed,
"", fmt.Sprintf("Failed to create firewall rules: %v", err))
s.windowsService.DeleteService(ctx.UserContext(), server.ServiceName)
s.steamService.UninstallServer(server.Path)
return fmt.Errorf("failed to create firewall rules: %v", err)
}
s.webSocketService.BroadcastStep(server.ID, model.StepFirewallRules, model.StatusCompleted,
fmt.Sprintf("Firewall rules created for port %d", serverPort), "")
s.webSocketService.BroadcastStep(server.ID, model.StepDatabaseSave, model.StatusInProgress,
model.GetStepDescription(model.StepDatabaseSave), "")
if err := s.repository.Insert(ctx.UserContext(), server); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepDatabaseSave, model.StatusFailed,
"", fmt.Sprintf("Failed to save server to database: %v", err))
s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts)
s.windowsService.DeleteService(ctx.UserContext(), server.ServiceName)
s.steamService.UninstallServer(server.Path)
return fmt.Errorf("failed to insert server into database: %v", err)
}
s.webSocketService.BroadcastStep(server.ID, model.StepDatabaseSave, model.StatusCompleted,
"Server saved to database successfully", "")
s.StartAccServerRuntime(server)
s.webSocketService.BroadcastStep(server.ID, model.StepCompleted, model.StatusCompleted,
@@ -437,114 +461,22 @@ func (s *ServerService) CreateServer(ctx *fiber.Ctx, server *model.Server) error
return nil
}
func (s *ServerService) createServerBackground(ctx context.Context, server *model.Server) error {
s.webSocketService.BroadcastStep(server.ID, model.StepValidation, model.StatusInProgress,
model.GetStepDescription(model.StepValidation), "")
if err := server.Validate(); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepValidation, model.StatusFailed,
"", fmt.Sprintf("Validation failed: %v", err))
return err
func (s *ServerService) rollbackSteps(ctx context.Context, server *model.Server, completedSteps []createServerStep, tcpPorts, udpPorts []int) {
for i := len(completedSteps) - 1; i >= 0; i-- {
step := completedSteps[i]
switch step.stepType {
case model.StepDatabaseSave:
s.repository.Delete(ctx, server.ID)
case model.StepFirewallRules:
if len(tcpPorts) > 0 && len(udpPorts) > 0 {
s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts)
}
case model.StepServiceCreation:
s.windowsService.DeleteService(ctx, server.ServiceName)
case model.StepSteamDownload:
s.steamService.UninstallServer(server.Path)
}
}
s.webSocketService.BroadcastStep(server.ID, model.StepValidation, model.StatusCompleted,
"Server configuration validated successfully", "")
s.webSocketService.BroadcastStep(server.ID, model.StepDirectoryCreation, model.StatusInProgress,
model.GetStepDescription(model.StepDirectoryCreation), "")
s.webSocketService.BroadcastStep(server.ID, model.StepDirectoryCreation, model.StatusCompleted,
"Server directories prepared", "")
s.webSocketService.BroadcastStep(server.ID, model.StepSteamDownload, model.StatusInProgress,
model.GetStepDescription(model.StepSteamDownload), "")
if err := s.steamService.InstallServerWithWebSocket(ctx, server.Path, &server.ID, s.webSocketService); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepSteamDownload, model.StatusFailed,
"", fmt.Sprintf("Steam installation failed: %v", err))
return fmt.Errorf("failed to install server: %v", err)
}
s.webSocketService.BroadcastStep(server.ID, model.StepSteamDownload, model.StatusCompleted,
"Server files downloaded successfully", "")
s.webSocketService.BroadcastStep(server.ID, model.StepConfigGeneration, model.StatusInProgress,
model.GetStepDescription(model.StepConfigGeneration), "")
ports, err := network.FindAvailablePortRange(DefaultStartPort, RequiredPortCount)
if err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepConfigGeneration, model.StatusFailed,
"", fmt.Sprintf("Failed to find available ports: %v", err))
return fmt.Errorf("failed to find available ports: %v", err)
}
serverPort := ports[0]
if err := s.updateServerPort(server, serverPort); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepConfigGeneration, model.StatusFailed,
"", fmt.Sprintf("Failed to update server configuration: %v", err))
return fmt.Errorf("failed to update server configuration: %v", err)
}
s.webSocketService.BroadcastStep(server.ID, model.StepConfigGeneration, model.StatusCompleted,
fmt.Sprintf("Server configuration generated (Port: %d)", serverPort), "")
s.webSocketService.BroadcastStep(server.ID, model.StepServiceCreation, model.StatusInProgress,
model.GetStepDescription(model.StepServiceCreation), "")
execPath := filepath.Join(server.GetServerPath(), "accServer.exe")
serverWorkingDir := filepath.Join(server.GetServerPath(), "server")
if err := s.windowsService.CreateService(ctx, server.ServiceName, execPath, serverWorkingDir, nil); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepServiceCreation, model.StatusFailed,
"", fmt.Sprintf("Failed to create Windows service: %v", err))
s.steamService.UninstallServer(server.Path)
return fmt.Errorf("failed to create Windows service: %v", err)
}
s.webSocketService.BroadcastStep(server.ID, model.StepServiceCreation, model.StatusCompleted,
fmt.Sprintf("Windows service '%s' created successfully", server.ServiceName), "")
s.webSocketService.BroadcastStep(server.ID, model.StepFirewallRules, model.StatusInProgress,
model.GetStepDescription(model.StepFirewallRules), "")
s.configureFirewall(server)
tcpPorts := []int{serverPort}
udpPorts := []int{serverPort}
if err := s.firewallService.CreateServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepFirewallRules, model.StatusFailed,
"", fmt.Sprintf("Failed to create firewall rules: %v", err))
s.windowsService.DeleteService(ctx, server.ServiceName)
s.steamService.UninstallServer(server.Path)
return fmt.Errorf("failed to create firewall rules: %v", err)
}
s.webSocketService.BroadcastStep(server.ID, model.StepFirewallRules, model.StatusCompleted,
fmt.Sprintf("Firewall rules created for port %d", serverPort), "")
s.webSocketService.BroadcastStep(server.ID, model.StepDatabaseSave, model.StatusInProgress,
model.GetStepDescription(model.StepDatabaseSave), "")
if err := s.repository.Insert(ctx, server); err != nil {
s.webSocketService.BroadcastStep(server.ID, model.StepDatabaseSave, model.StatusFailed,
"", fmt.Sprintf("Failed to save server to database: %v", err))
s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts)
s.windowsService.DeleteService(ctx, server.ServiceName)
s.steamService.UninstallServer(server.Path)
return fmt.Errorf("failed to insert server into database: %v", err)
}
s.webSocketService.BroadcastStep(server.ID, model.StepDatabaseSave, model.StatusCompleted,
"Server saved to database successfully", "")
s.StartAccServerRuntime(server)
s.webSocketService.BroadcastStep(server.ID, model.StepCompleted, model.StatusCompleted,
model.GetStepDescription(model.StepCompleted), "")
s.webSocketService.BroadcastComplete(server.ID, true,
fmt.Sprintf("Server '%s' created successfully on port %d", server.Name, serverPort))
return nil
}
func (s *ServerService) DeleteServer(ctx *fiber.Ctx, serverID uuid.UUID) error {