From beb9b479dff5bdfc68d1f429e298d68798ed3e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20Jurmanovi=C4=87?= Date: Wed, 18 Mar 2026 19:16:49 +0100 Subject: [PATCH] add platform interface according to env --- local/platform/interfaces.go | 28 ++++++ local/service/file_log_streamer.go | 38 ++++++++ local/service/server.go | 150 +++++++++++++++++------------ local/service/service.go | 70 +++++++++++++- local/service/service_control.go | 52 ++++++---- local/service/steam_service.go | 17 +++- local/service/windows_runtime.go | 68 +++++++++++++ local/utl/db/db.go | 4 + 8 files changed, 339 insertions(+), 88 deletions(-) create mode 100644 local/platform/interfaces.go create mode 100644 local/service/file_log_streamer.go mode change 100644 => 100755 local/service/server.go mode change 100644 => 100755 local/service/service.go mode change 100644 => 100755 local/service/service_control.go mode change 100644 => 100755 local/service/steam_service.go create mode 100644 local/service/windows_runtime.go mode change 100644 => 100755 local/utl/db/db.go diff --git a/local/platform/interfaces.go b/local/platform/interfaces.go new file mode 100644 index 0000000..47de4ed --- /dev/null +++ b/local/platform/interfaces.go @@ -0,0 +1,28 @@ +package platform + +import ( + "acc-server-manager/local/model" + "context" + + "github.com/google/uuid" +) + +type ServerRuntime interface { + Create(ctx context.Context, server *model.Server) error + Delete(ctx context.Context, serverID uuid.UUID) error + Start(ctx context.Context, serverID uuid.UUID) (string, error) + Stop(ctx context.Context, serverID uuid.UUID) (string, error) + Restart(ctx context.Context, serverID uuid.UUID) (string, error) + Status(ctx context.Context, serverID uuid.UUID) (string, error) +} + +type FirewallManager interface { + CreateServerRules(serverName string, tcpPorts, udpPorts []int) error + DeleteServerRules(serverName string, tcpPorts, udpPorts []int) error + UpdateServerRules(serverName string, tcpPorts, udpPorts []int) error +} + +type LogStreamer interface { + Start(ctx context.Context, server *model.Server, handleLine func(string)) error + Stop(serverID uuid.UUID) +} diff --git a/local/service/file_log_streamer.go b/local/service/file_log_streamer.go new file mode 100644 index 0000000..62bd3e6 --- /dev/null +++ b/local/service/file_log_streamer.go @@ -0,0 +1,38 @@ +package service + +import ( + "acc-server-manager/local/model" + "acc-server-manager/local/utl/tracking" + "context" + "path/filepath" + "sync" + + "github.com/google/uuid" +) + +type FileLogStreamer struct { + tailers sync.Map // uuid.UUID → *tracking.LogTailer +} + +func NewFileLogStreamer() *FileLogStreamer { + return &FileLogStreamer{} +} + +func (s *FileLogStreamer) Start(_ context.Context, server *model.Server, handleLine func(string)) error { + if _, exists := s.tailers.Load(server.ID); exists { + return nil + } + + logPath := filepath.Join(server.GetLogPath(), "server.log") + tailer := tracking.NewLogTailer(logPath, handleLine) + s.tailers.Store(server.ID, tailer) + + go tailer.Start() + return nil +} + +func (s *FileLogStreamer) Stop(serverID uuid.UUID) { + if v, exists := s.tailers.LoadAndDelete(serverID); exists { + v.(*tracking.LogTailer).Stop() + } +} diff --git a/local/service/server.go b/local/service/server.go old mode 100644 new mode 100755 index 00adc45..2cf5555 --- a/local/service/server.go +++ b/local/service/server.go @@ -2,9 +2,11 @@ package service import ( "acc-server-manager/local/model" + "acc-server-manager/local/platform" "acc-server-manager/local/repository" "acc-server-manager/local/utl/env" "acc-server-manager/local/utl/logging" + "acc-server-manager/local/utl/network" "acc-server-manager/local/utl/tracking" "context" "fmt" @@ -12,8 +14,6 @@ import ( "sync" "time" - "acc-server-manager/local/utl/network" - "github.com/gofiber/fiber/v2" "github.com/google/uuid" ) @@ -29,14 +29,15 @@ type ServerService struct { apiService *ServiceControlService configService *ConfigService steamService *SteamService - windowsService *WindowsService - firewallService *FirewallService + runtime platform.ServerRuntime + firewall platform.FirewallManager + logStreamer platform.LogStreamer + portPool *network.PortPoolManager webSocketService *WebSocketService - instances sync.Map // Track instances per server - lastInsertTimes sync.Map // Track last insert time per server - debouncers sync.Map // Track debounce timers per server - logTailers sync.Map // Track log tailers per server - sessionIDs sync.Map // Track current session ID per server + instances sync.Map // uuid.UUID → *tracking.AccServerInstance + lastInsertTimes sync.Map // uuid.UUID → time.Time + debouncers sync.Map // uuid.UUID → *pendingState + sessionIDs sync.Map // uuid.UUID → uuid.UUID } type pendingState struct { @@ -45,16 +46,10 @@ type pendingState struct { } func (s *ServerService) ensureLogTailing(server *model.Server, instance *tracking.AccServerInstance) { - if _, exists := s.logTailers.Load(server.ID); exists { - return - } - go func() { - logPath := filepath.Join(server.GetLogPath(), "server.log") - tailer := tracking.NewLogTailer(logPath, instance.HandleLogLine) - s.logTailers.Store(server.ID, tailer) - - tailer.Start() + if err := s.logStreamer.Start(context.Background(), server, instance.HandleLogLine); err != nil { + logging.Error("Failed to start log streaming for server %s: %v", server.ID, err) + } }() } @@ -64,8 +59,10 @@ func NewServerService( apiService *ServiceControlService, configService *ConfigService, steamService *SteamService, - windowsService *WindowsService, - firewallService *FirewallService, + runtime platform.ServerRuntime, + firewall platform.FirewallManager, + logStreamer platform.LogStreamer, + portPool *network.PortPoolManager, webSocketService *WebSocketService, ) *ServerService { service := &ServerService{ @@ -74,8 +71,10 @@ func NewServerService( apiService: apiService, configService: configService, steamService: steamService, - windowsService: windowsService, - firewallService: firewallService, + runtime: runtime, + firewall: firewall, + logStreamer: logStreamer, + portPool: portPool, webSocketService: webSocketService, } @@ -195,9 +194,19 @@ func (s *ServerService) updateSessionDuration(server *model.Server, sessionType } func (s *ServerService) GenerateServerPath(server *model.Server) { - steamCMDPath := env.GetSteamCMDDirPath() - server.Path = server.GenerateServerPath(steamCMDPath) - server.FromSteamCMD = true + server.GenerateUUID() + if server.ServiceName == "" { + server.ServiceName = server.GenerateServiceName() + } + + if env.IsDockerPlatform() { + server.Path = filepath.Join(env.GetACCConfigBasePath(), server.ServiceName) + server.FromSteamCMD = false + } else { + steamCMDPath := env.GetSteamCMDDirPath() + server.Path = server.GenerateServerPath(steamCMDPath) + server.FromSteamCMD = true + } } func (s *ServerService) handleStateChange(server *model.Server, state *model.ServerState) { @@ -250,9 +259,6 @@ func (s *ServerService) StartAccServerRuntime(server *model.Server) { s.ensureLogTailing(server, instance) } -// context.Context: Application context -// Returns: -// string: Application version func (s *ServerService) GetAll(ctx *fiber.Ctx, filter *model.ServerFilter) (*[]model.Server, error) { servers, err := s.repository.GetAll(ctx.UserContext(), filter) if err != nil { @@ -281,9 +287,6 @@ func (s *ServerService) GetAll(ctx *fiber.Ctx, filter *model.ServerFilter) (*[]m return servers, nil } -// context.Context: Application context -// Returns: -// string: Application version func (as *ServerService) GetById(ctx *fiber.Ctx, serverID uuid.UUID) (*model.Server, error) { server, err := as.repository.GetByID(ctx.UserContext(), serverID) if err != nil { @@ -291,7 +294,7 @@ func (as *ServerService) GetById(ctx *fiber.Ctx, serverID uuid.UUID) (*model.Ser } status, err := as.apiService.GetCachedStatus(server.ServiceName) if err != nil { - logging.Error(err.Error()) + logging.Error("Failed to get cached status: %v", err) } server.Status = model.ParseServiceStatus(status) instance, ok := as.instances.Load(server.ID) @@ -366,7 +369,11 @@ func (s *ServerService) createServerBackground(ctx context.Context, server *mode 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 { + installPath := server.Path + if env.IsDockerPlatform() { + installPath = env.GetACCGameVolume() + } + if err := s.steamService.InstallServerWithWebSocket(ctx, installPath, &server.ID, s.webSocketService); err != nil { return "", fmt.Errorf("failed to install server: %v", err) } return "Server files downloaded successfully", nil @@ -377,12 +384,24 @@ func (s *ServerService) createServerBackground(ctx context.Context, server *mode 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) + var port int + var err error + + if env.IsDockerPlatform() { + port, err = s.portPool.Acquire(ctx) + if err != nil { + return "", fmt.Errorf("failed to acquire port from pool: %v", err) + } + } else { + ports, err := network.FindAvailablePortRange(DefaultStartPort, RequiredPortCount) + if err != nil { + return "", fmt.Errorf("failed to find available ports: %v", err) + } + port = ports[0] } - serverPort = ports[0] + serverPort = port + server.Port = port if err := s.updateServerPort(server, serverPort); err != nil { return "", fmt.Errorf("failed to update server configuration: %v", err) @@ -396,12 +415,11 @@ func (s *ServerService) createServerBackground(ctx context.Context, server *mode 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) + server.Platform = env.GetPlatform() + if err := s.runtime.Create(ctx, server); err != nil { + return "", fmt.Errorf("failed to create server instance: %v", err) } - return fmt.Sprintf("Windows service '%s' created successfully", server.ServiceName), nil + return fmt.Sprintf("Server instance '%s' created successfully", server.ServiceName), nil }, }, { @@ -409,13 +427,12 @@ func (s *ServerService) createServerBackground(ctx context.Context, server *mode 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 { + if err := s.firewall.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 + return fmt.Sprintf("Firewall rules configured for port %d", serverPort), nil }, }, { @@ -469,12 +486,18 @@ func (s *ServerService) rollbackSteps(ctx context.Context, server *model.Server, s.repository.Delete(ctx, server.ID) case model.StepFirewallRules: if len(tcpPorts) > 0 && len(udpPorts) > 0 { - s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts) + s.firewall.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts) } case model.StepServiceCreation: - s.windowsService.DeleteService(ctx, server.ServiceName) + s.runtime.Delete(ctx, server.ID) case model.StepSteamDownload: - s.steamService.UninstallServer(server.Path) + if !env.IsDockerPlatform() { + s.steamService.UninstallServer(server.Path) + } + case model.StepConfigGeneration: + if env.IsDockerPlatform() && server.Port > 0 { + s.portPool.Release(ctx, server.Port) + } } } } @@ -485,32 +508,37 @@ func (s *ServerService) DeleteServer(ctx *fiber.Ctx, serverID uuid.UUID) error { return fmt.Errorf("failed to get server details: %v", err) } - if err := s.windowsService.DeleteService(ctx.UserContext(), server.ServiceName); err != nil { - logging.Error("Failed to delete Windows service: %v", err) + if err := s.runtime.Delete(ctx.UserContext(), server.ID); err != nil { + logging.Error("Failed to delete server instance: %v", err) } configuration, err := s.configService.GetConfiguration(server) if err != nil { logging.Error("Failed to get configuration for server %d: %v", server.ID, err) } - tcpPorts := []int{configuration.TcpPort.ToInt()} - udpPorts := []int{configuration.UdpPort.ToInt()} - if err := s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil { - logging.Error("Failed to delete firewall rules: %v", err) + if configuration != nil { + tcpPorts := []int{configuration.TcpPort.ToInt()} + udpPorts := []int{configuration.UdpPort.ToInt()} + if err := s.firewall.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil { + logging.Error("Failed to delete firewall rules: %v", err) + } } - if err := s.steamService.UninstallServer(server.Path); err != nil { - logging.Error("Failed to uninstall server: %v", err) + if server.Platform != "docker" { + if err := s.steamService.UninstallServer(server.Path); err != nil { + logging.Error("Failed to uninstall server: %v", err) + } + } + + if env.IsDockerPlatform() && server.Port > 0 { + s.portPool.Release(ctx.UserContext(), server.Port) } if err := s.repository.Delete(ctx.UserContext(), serverID); err != nil { return fmt.Errorf("failed to delete server from database: %v", err) } - if tailer, exists := s.logTailers.Load(server.ID); exists { - tailer.(*tracking.LogTailer).Stop() - s.logTailers.Delete(server.ID) - } + s.logStreamer.Stop(server.ID) s.instances.Delete(server.ID) s.lastInsertTimes.Delete(server.ID) s.debouncers.Delete(server.ID) @@ -533,7 +561,7 @@ func (s *ServerService) configureFirewall(server *model.Server) error { logging.Info("Configuring firewall for server %d with port %d", server.ID, serverPort) - if err := s.firewallService.UpdateServerRules(server.Name, tcpPorts, udpPorts); err != nil { + if err := s.firewall.UpdateServerRules(server.Name, tcpPorts, udpPorts); err != nil { return fmt.Errorf("failed to configure firewall: %v", err) } diff --git a/local/service/service.go b/local/service/service.go old mode 100644 new mode 100755 index 64f3e9e..d95639f --- a/local/service/service.go +++ b/local/service/service.go @@ -1,13 +1,22 @@ package service import ( + "acc-server-manager/local/platform" "acc-server-manager/local/repository" + "acc-server-manager/local/utl/docker" + "acc-server-manager/local/utl/env" "acc-server-manager/local/utl/logging" + "acc-server-manager/local/utl/network" + "fmt" + dockerclient "github.com/docker/docker/client" "go.uber.org/dig" + "gorm.io/gorm" ) -// *dig.Container: Dig Container +// InitializeServices registers all service providers into the DI container. +// Platform-specific components (ServerRuntime, FirewallManager, LogStreamer) +// are conditionally provided based on the PLATFORM environment variable. func InitializeServices(c *dig.Container) { logging.Debug("Initializing repositories") repository.InitializeRepositories(c) @@ -19,21 +28,74 @@ func InitializeServices(c *dig.Container) { c.Provide(NewServiceControlService) c.Provide(NewConfigService) c.Provide(NewLookupService) - c.Provide(NewWindowsService) - c.Provide(NewFirewallService) c.Provide(NewMembershipService) c.Provide(NewWebSocketService) c.Provide(NewLeaderboardService) + // Always provide the PortPoolManager; in Windows mode it seeds from an + // empty result set and sits idle. + c.Provide(func(db *gorm.DB) (*network.PortPoolManager, error) { + return network.NewPortPoolManager(db) + }) + + if env.IsDockerPlatform() { + registerDockerProviders(c) + } else { + registerWindowsProviders(c) + } + logging.Debug("Initializing service dependencies") err := c.Invoke(func(server *ServerService, api *ServiceControlService, config *ConfigService) { logging.Debug("Setting up service cross-references") api.SetServerService(server) config.SetServerService(server) - }) if err != nil { logging.Panic("unable to initialize services: " + err.Error()) } logging.Debug("Completed service initialization") } + +func registerWindowsProviders(c *dig.Container) { + logging.Debug("Registering Windows platform providers") + + c.Provide(NewWindowsService) + + c.Provide(func(ws *WindowsService, repo *repository.ServerRepository) platform.ServerRuntime { + return NewWindowsRuntime(ws, repo) + }) + + c.Provide(func() platform.FirewallManager { + return NewFirewallService() + }) + + c.Provide(func() platform.LogStreamer { + return NewFileLogStreamer() + }) +} + +func registerDockerProviders(c *dig.Container) { + logging.Debug("Registering Docker platform providers") + + if err := c.Provide(func() (*dockerclient.Client, error) { + client, err := docker.NewDockerClient() + if err != nil { + return nil, fmt.Errorf("failed to connect to Docker daemon: %v", err) + } + return client, nil + }); err != nil { + logging.Panic("failed to provide Docker client: " + err.Error()) + } + + c.Provide(func(client *dockerclient.Client, repo *repository.ServerRepository) platform.ServerRuntime { + return NewDockerRuntime(client, repo) + }) + + c.Provide(func() platform.FirewallManager { + return &NoOpFirewall{} + }) + + c.Provide(func(client *dockerclient.Client) platform.LogStreamer { + return NewDockerLogStreamer(client) + }) +} diff --git a/local/service/service_control.go b/local/service/service_control.go old mode 100644 new mode 100755 index b1794d5..7641357 --- a/local/service/service_control.go +++ b/local/service/service_control.go @@ -2,6 +2,7 @@ package service import ( "acc-server-manager/local/model" + "acc-server-manager/local/platform" "acc-server-manager/local/repository" "context" "errors" @@ -15,20 +16,23 @@ type ServiceControlService struct { serverRepository *repository.ServerRepository serverService *ServerService statusCache *model.ServerStatusCache - windowsService *WindowsService + runtime platform.ServerRuntime } -func NewServiceControlService(repository *repository.ServiceControlRepository, - serverRepository *repository.ServerRepository) *ServiceControlService { +func NewServiceControlService( + repository *repository.ServiceControlRepository, + serverRepository *repository.ServerRepository, + runtime platform.ServerRuntime, +) *ServiceControlService { return &ServiceControlService{ repository: repository, serverRepository: serverRepository, + runtime: runtime, statusCache: model.NewServerStatusCache(model.CacheConfig{ ExpirationTime: 30 * time.Second, ThrottleTime: 5 * time.Second, DefaultStatus: model.StatusRunning, }), - windowsService: NewWindowsService(), } } @@ -123,7 +127,11 @@ func (as *ServiceControlService) ServiceControlRestartServer(ctx *fiber.Ctx) (st } func (as *ServiceControlService) StatusServer(serviceName string) (string, error) { - return as.windowsService.Status(context.Background(), serviceName) + server, err := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName) + if err != nil { + return "", err + } + return as.runtime.Status(context.Background(), server.ID) } func (as *ServiceControlService) GetCachedStatus(serviceName string) (string, error) { @@ -142,44 +150,46 @@ func (as *ServiceControlService) GetCachedStatus(serviceName string) (string, er } func (as *ServiceControlService) StartServer(serviceName string) (string, error) { - status, err := as.windowsService.Start(context.Background(), serviceName) - if err != nil { - return "", err - } - server, err := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName) if err != nil { return "", err } + + status, err := as.runtime.Start(context.Background(), server.ID) + if err != nil { + return "", err + } + as.serverService.StartAccServerRuntime(server) return status, err } func (as *ServiceControlService) StopServer(serviceName string) (string, error) { - status, err := as.windowsService.Stop(context.Background(), serviceName) - if err != nil { - return "", err - } - server, err := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName) if err != nil { return "", err } - as.serverService.instances.Delete(server.ID) + status, err := as.runtime.Stop(context.Background(), server.ID) + if err != nil { + return "", err + } + + as.serverService.instances.Delete(server.ID) return status, err } func (as *ServiceControlService) RestartServer(serviceName string) (string, error) { - status, err := as.windowsService.Restart(context.Background(), serviceName) - if err != nil { - return "", err - } - server, err := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName) if err != nil { return "", err } + + status, err := as.runtime.Restart(context.Background(), server.ID) + if err != nil { + return "", err + } + as.serverService.StartAccServerRuntime(server) return status, err } diff --git a/local/service/steam_service.go b/local/service/steam_service.go old mode 100644 new mode 100755 index f6ef459..2af06e4 --- a/local/service/steam_service.go +++ b/local/service/steam_service.go @@ -44,6 +44,14 @@ func NewSteamService(repository *repository.SteamCredentialsRepository, tfaManag } } +// getSteamCMDPath returns the platform-appropriate SteamCMD binary path. +func getSteamCMDPath() string { + if env.IsDockerPlatform() { + return env.GetLinuxSteamCMDPath() + } + return env.GetSteamCMDPath() +} + func (s *SteamService) GetCredentials(ctx context.Context) (*model.SteamCredentials, error) { return s.repository.GetCurrent(ctx) } @@ -56,6 +64,11 @@ func (s *SteamService) SaveCredentials(ctx context.Context, creds *model.SteamCr } func (s *SteamService) ensureSteamCMD(_ context.Context) error { + // In Docker mode, SteamCMD is pre-installed in the manager container image. + if env.IsDockerPlatform() { + return nil + } + steamCMDPath := env.GetSteamCMDPath() steamCMDDir := filepath.Dir(steamCMDPath) @@ -117,7 +130,7 @@ func (s *SteamService) InstallServerWithWebSocket(ctx context.Context, installPa return fmt.Errorf("failed to get Steam credentials: %v", err) } - steamCMDPath := env.GetSteamCMDPath() + steamCMDPath := getSteamCMDPath() steamCMDArgs := []string{ "+force_install_dir", absPath, @@ -241,7 +254,7 @@ func (s *SteamService) InstallServerWithCallbacks(ctx context.Context, installPa return fmt.Errorf("failed to get Steam credentials: %v", err) } - steamCMDPath := env.GetSteamCMDPath() + steamCMDPath := getSteamCMDPath() steamCMDArgs := []string{ "+force_install_dir", absPath, diff --git a/local/service/windows_runtime.go b/local/service/windows_runtime.go new file mode 100644 index 0000000..9eb13c5 --- /dev/null +++ b/local/service/windows_runtime.go @@ -0,0 +1,68 @@ +package service + +import ( + "acc-server-manager/local/model" + "acc-server-manager/local/repository" + "context" + "path/filepath" + + "github.com/google/uuid" +) + +type WindowsRuntime struct { + windowsService *WindowsService + repo *repository.ServerRepository +} + +func NewWindowsRuntime(windowsService *WindowsService, repo *repository.ServerRepository) *WindowsRuntime { + return &WindowsRuntime{ + windowsService: windowsService, + repo: repo, + } +} + +func (r *WindowsRuntime) Create(ctx context.Context, server *model.Server) error { + execPath := filepath.Join(server.GetServerPath(), "accServer.exe") + serverWorkingDir := server.GetServerPath() + return r.windowsService.CreateService(ctx, server.ServiceName, execPath, serverWorkingDir, nil) +} + +func (r *WindowsRuntime) Delete(ctx context.Context, serverID uuid.UUID) error { + server, err := r.repo.GetByID(ctx, serverID) + if err != nil { + return err + } + return r.windowsService.DeleteService(ctx, server.ServiceName) +} + +func (r *WindowsRuntime) Start(ctx context.Context, serverID uuid.UUID) (string, error) { + server, err := r.repo.GetByID(ctx, serverID) + if err != nil { + return "", err + } + return r.windowsService.Start(ctx, server.ServiceName) +} + +func (r *WindowsRuntime) Stop(ctx context.Context, serverID uuid.UUID) (string, error) { + server, err := r.repo.GetByID(ctx, serverID) + if err != nil { + return "", err + } + return r.windowsService.Stop(ctx, server.ServiceName) +} + +func (r *WindowsRuntime) Restart(ctx context.Context, serverID uuid.UUID) (string, error) { + server, err := r.repo.GetByID(ctx, serverID) + if err != nil { + return "", err + } + return r.windowsService.Restart(ctx, server.ServiceName) +} + +func (r *WindowsRuntime) Status(ctx context.Context, serverID uuid.UUID) (string, error) { + server, err := r.repo.GetByID(ctx, serverID) + if err != nil { + return "", err + } + return r.windowsService.Status(ctx, server.ServiceName) +} diff --git a/local/utl/db/db.go b/local/utl/db/db.go old mode 100644 new mode 100755 index 55ca071..0500181 --- a/local/utl/db/db.go +++ b/local/utl/db/db.go @@ -76,6 +76,10 @@ func runMigrations(db *gorm.DB) { logging.Error("Failed to run password security migration: %v", err) } + if err := migrations.RunAddPlatformColumnsMigration(db); err != nil { + logging.Error("Failed to run platform columns migration: %v", err) + } + logging.Info("Custom database migrations completed") }