add platform interface according to env
This commit is contained in:
28
local/platform/interfaces.go
Normal file
28
local/platform/interfaces.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
38
local/service/file_log_streamer.go
Normal file
38
local/service/file_log_streamer.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
126
local/service/server.go
Normal file → Executable file
126
local/service/server.go
Normal file → Executable file
@@ -2,9 +2,11 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"acc-server-manager/local/model"
|
"acc-server-manager/local/model"
|
||||||
|
"acc-server-manager/local/platform"
|
||||||
"acc-server-manager/local/repository"
|
"acc-server-manager/local/repository"
|
||||||
"acc-server-manager/local/utl/env"
|
"acc-server-manager/local/utl/env"
|
||||||
"acc-server-manager/local/utl/logging"
|
"acc-server-manager/local/utl/logging"
|
||||||
|
"acc-server-manager/local/utl/network"
|
||||||
"acc-server-manager/local/utl/tracking"
|
"acc-server-manager/local/utl/tracking"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -12,8 +14,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"acc-server-manager/local/utl/network"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@@ -29,14 +29,15 @@ type ServerService struct {
|
|||||||
apiService *ServiceControlService
|
apiService *ServiceControlService
|
||||||
configService *ConfigService
|
configService *ConfigService
|
||||||
steamService *SteamService
|
steamService *SteamService
|
||||||
windowsService *WindowsService
|
runtime platform.ServerRuntime
|
||||||
firewallService *FirewallService
|
firewall platform.FirewallManager
|
||||||
|
logStreamer platform.LogStreamer
|
||||||
|
portPool *network.PortPoolManager
|
||||||
webSocketService *WebSocketService
|
webSocketService *WebSocketService
|
||||||
instances sync.Map // Track instances per server
|
instances sync.Map // uuid.UUID → *tracking.AccServerInstance
|
||||||
lastInsertTimes sync.Map // Track last insert time per server
|
lastInsertTimes sync.Map // uuid.UUID → time.Time
|
||||||
debouncers sync.Map // Track debounce timers per server
|
debouncers sync.Map // uuid.UUID → *pendingState
|
||||||
logTailers sync.Map // Track log tailers per server
|
sessionIDs sync.Map // uuid.UUID → uuid.UUID
|
||||||
sessionIDs sync.Map // Track current session ID per server
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type pendingState struct {
|
type pendingState struct {
|
||||||
@@ -45,16 +46,10 @@ type pendingState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) ensureLogTailing(server *model.Server, instance *tracking.AccServerInstance) {
|
func (s *ServerService) ensureLogTailing(server *model.Server, instance *tracking.AccServerInstance) {
|
||||||
if _, exists := s.logTailers.Load(server.ID); exists {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
logPath := filepath.Join(server.GetLogPath(), "server.log")
|
if err := s.logStreamer.Start(context.Background(), server, instance.HandleLogLine); err != nil {
|
||||||
tailer := tracking.NewLogTailer(logPath, instance.HandleLogLine)
|
logging.Error("Failed to start log streaming for server %s: %v", server.ID, err)
|
||||||
s.logTailers.Store(server.ID, tailer)
|
}
|
||||||
|
|
||||||
tailer.Start()
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,8 +59,10 @@ func NewServerService(
|
|||||||
apiService *ServiceControlService,
|
apiService *ServiceControlService,
|
||||||
configService *ConfigService,
|
configService *ConfigService,
|
||||||
steamService *SteamService,
|
steamService *SteamService,
|
||||||
windowsService *WindowsService,
|
runtime platform.ServerRuntime,
|
||||||
firewallService *FirewallService,
|
firewall platform.FirewallManager,
|
||||||
|
logStreamer platform.LogStreamer,
|
||||||
|
portPool *network.PortPoolManager,
|
||||||
webSocketService *WebSocketService,
|
webSocketService *WebSocketService,
|
||||||
) *ServerService {
|
) *ServerService {
|
||||||
service := &ServerService{
|
service := &ServerService{
|
||||||
@@ -74,8 +71,10 @@ func NewServerService(
|
|||||||
apiService: apiService,
|
apiService: apiService,
|
||||||
configService: configService,
|
configService: configService,
|
||||||
steamService: steamService,
|
steamService: steamService,
|
||||||
windowsService: windowsService,
|
runtime: runtime,
|
||||||
firewallService: firewallService,
|
firewall: firewall,
|
||||||
|
logStreamer: logStreamer,
|
||||||
|
portPool: portPool,
|
||||||
webSocketService: webSocketService,
|
webSocketService: webSocketService,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,10 +194,20 @@ func (s *ServerService) updateSessionDuration(server *model.Server, sessionType
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GenerateServerPath(server *model.Server) {
|
func (s *ServerService) GenerateServerPath(server *model.Server) {
|
||||||
|
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()
|
steamCMDPath := env.GetSteamCMDDirPath()
|
||||||
server.Path = server.GenerateServerPath(steamCMDPath)
|
server.Path = server.GenerateServerPath(steamCMDPath)
|
||||||
server.FromSteamCMD = true
|
server.FromSteamCMD = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ServerService) handleStateChange(server *model.Server, state *model.ServerState) {
|
func (s *ServerService) handleStateChange(server *model.Server, state *model.ServerState) {
|
||||||
s.updateSessionDuration(server, state.Session)
|
s.updateSessionDuration(server, state.Session)
|
||||||
@@ -250,9 +259,6 @@ func (s *ServerService) StartAccServerRuntime(server *model.Server) {
|
|||||||
s.ensureLogTailing(server, instance)
|
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) {
|
func (s *ServerService) GetAll(ctx *fiber.Ctx, filter *model.ServerFilter) (*[]model.Server, error) {
|
||||||
servers, err := s.repository.GetAll(ctx.UserContext(), filter)
|
servers, err := s.repository.GetAll(ctx.UserContext(), filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -281,9 +287,6 @@ func (s *ServerService) GetAll(ctx *fiber.Ctx, filter *model.ServerFilter) (*[]m
|
|||||||
return servers, nil
|
return servers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// context.Context: Application context
|
|
||||||
// Returns:
|
|
||||||
// string: Application version
|
|
||||||
func (as *ServerService) GetById(ctx *fiber.Ctx, serverID uuid.UUID) (*model.Server, error) {
|
func (as *ServerService) GetById(ctx *fiber.Ctx, serverID uuid.UUID) (*model.Server, error) {
|
||||||
server, err := as.repository.GetByID(ctx.UserContext(), serverID)
|
server, err := as.repository.GetByID(ctx.UserContext(), serverID)
|
||||||
if err != nil {
|
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)
|
status, err := as.apiService.GetCachedStatus(server.ServiceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Error(err.Error())
|
logging.Error("Failed to get cached status: %v", err)
|
||||||
}
|
}
|
||||||
server.Status = model.ParseServiceStatus(status)
|
server.Status = model.ParseServiceStatus(status)
|
||||||
instance, ok := as.instances.Load(server.ID)
|
instance, ok := as.instances.Load(server.ID)
|
||||||
@@ -366,7 +369,11 @@ func (s *ServerService) createServerBackground(ctx context.Context, server *mode
|
|||||||
important: true,
|
important: true,
|
||||||
description: "Server files downloaded successfully",
|
description: "Server files downloaded successfully",
|
||||||
callback: func() (string, error) {
|
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 "", fmt.Errorf("failed to install server: %v", err)
|
||||||
}
|
}
|
||||||
return "Server files downloaded successfully", nil
|
return "Server files downloaded successfully", nil
|
||||||
@@ -377,12 +384,24 @@ func (s *ServerService) createServerBackground(ctx context.Context, server *mode
|
|||||||
important: true,
|
important: true,
|
||||||
description: "",
|
description: "",
|
||||||
callback: func() (string, error) {
|
callback: func() (string, error) {
|
||||||
|
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)
|
ports, err := network.FindAvailablePortRange(DefaultStartPort, RequiredPortCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to find available ports: %v", err)
|
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 {
|
if err := s.updateServerPort(server, serverPort); err != nil {
|
||||||
return "", fmt.Errorf("failed to update server configuration: %v", err)
|
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,
|
important: true,
|
||||||
description: "",
|
description: "",
|
||||||
callback: func() (string, error) {
|
callback: func() (string, error) {
|
||||||
execPath := filepath.Join(server.GetServerPath(), "accServer.exe")
|
server.Platform = env.GetPlatform()
|
||||||
serverWorkingDir := filepath.Join(server.GetServerPath(), "server")
|
if err := s.runtime.Create(ctx, server); err != nil {
|
||||||
if err := s.windowsService.CreateService(ctx, server.ServiceName, execPath, serverWorkingDir, nil); err != nil {
|
return "", fmt.Errorf("failed to create server instance: %v", err)
|
||||||
return "", fmt.Errorf("failed to create Windows service: %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,
|
important: false,
|
||||||
description: "",
|
description: "",
|
||||||
callback: func() (string, error) {
|
callback: func() (string, error) {
|
||||||
s.configureFirewall(server)
|
|
||||||
tcpPorts = []int{serverPort}
|
tcpPorts = []int{serverPort}
|
||||||
udpPorts = []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.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,13 +486,19 @@ func (s *ServerService) rollbackSteps(ctx context.Context, server *model.Server,
|
|||||||
s.repository.Delete(ctx, server.ID)
|
s.repository.Delete(ctx, server.ID)
|
||||||
case model.StepFirewallRules:
|
case model.StepFirewallRules:
|
||||||
if len(tcpPorts) > 0 && len(udpPorts) > 0 {
|
if len(tcpPorts) > 0 && len(udpPorts) > 0 {
|
||||||
s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts)
|
s.firewall.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts)
|
||||||
}
|
}
|
||||||
case model.StepServiceCreation:
|
case model.StepServiceCreation:
|
||||||
s.windowsService.DeleteService(ctx, server.ServiceName)
|
s.runtime.Delete(ctx, server.ID)
|
||||||
case model.StepSteamDownload:
|
case model.StepSteamDownload:
|
||||||
|
if !env.IsDockerPlatform() {
|
||||||
s.steamService.UninstallServer(server.Path)
|
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)
|
return fmt.Errorf("failed to get server details: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.windowsService.DeleteService(ctx.UserContext(), server.ServiceName); err != nil {
|
if err := s.runtime.Delete(ctx.UserContext(), server.ID); err != nil {
|
||||||
logging.Error("Failed to delete Windows service: %v", err)
|
logging.Error("Failed to delete server instance: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration, err := s.configService.GetConfiguration(server)
|
configuration, err := s.configService.GetConfiguration(server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Error("Failed to get configuration for server %d: %v", server.ID, err)
|
logging.Error("Failed to get configuration for server %d: %v", server.ID, err)
|
||||||
}
|
}
|
||||||
|
if configuration != nil {
|
||||||
tcpPorts := []int{configuration.TcpPort.ToInt()}
|
tcpPorts := []int{configuration.TcpPort.ToInt()}
|
||||||
udpPorts := []int{configuration.UdpPort.ToInt()}
|
udpPorts := []int{configuration.UdpPort.ToInt()}
|
||||||
if err := s.firewallService.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil {
|
if err := s.firewall.DeleteServerRules(server.ServiceName, tcpPorts, udpPorts); err != nil {
|
||||||
logging.Error("Failed to delete firewall rules: %v", err)
|
logging.Error("Failed to delete firewall rules: %v", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.Platform != "docker" {
|
||||||
if err := s.steamService.UninstallServer(server.Path); err != nil {
|
if err := s.steamService.UninstallServer(server.Path); err != nil {
|
||||||
logging.Error("Failed to uninstall server: %v", err)
|
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 {
|
if err := s.repository.Delete(ctx.UserContext(), serverID); err != nil {
|
||||||
return fmt.Errorf("failed to delete server from database: %v", err)
|
return fmt.Errorf("failed to delete server from database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tailer, exists := s.logTailers.Load(server.ID); exists {
|
s.logStreamer.Stop(server.ID)
|
||||||
tailer.(*tracking.LogTailer).Stop()
|
|
||||||
s.logTailers.Delete(server.ID)
|
|
||||||
}
|
|
||||||
s.instances.Delete(server.ID)
|
s.instances.Delete(server.ID)
|
||||||
s.lastInsertTimes.Delete(server.ID)
|
s.lastInsertTimes.Delete(server.ID)
|
||||||
s.debouncers.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)
|
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)
|
return fmt.Errorf("failed to configure firewall: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
70
local/service/service.go
Normal file → Executable file
70
local/service/service.go
Normal file → Executable file
@@ -1,13 +1,22 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"acc-server-manager/local/platform"
|
||||||
"acc-server-manager/local/repository"
|
"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/logging"
|
||||||
|
"acc-server-manager/local/utl/network"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
dockerclient "github.com/docker/docker/client"
|
||||||
"go.uber.org/dig"
|
"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) {
|
func InitializeServices(c *dig.Container) {
|
||||||
logging.Debug("Initializing repositories")
|
logging.Debug("Initializing repositories")
|
||||||
repository.InitializeRepositories(c)
|
repository.InitializeRepositories(c)
|
||||||
@@ -19,21 +28,74 @@ func InitializeServices(c *dig.Container) {
|
|||||||
c.Provide(NewServiceControlService)
|
c.Provide(NewServiceControlService)
|
||||||
c.Provide(NewConfigService)
|
c.Provide(NewConfigService)
|
||||||
c.Provide(NewLookupService)
|
c.Provide(NewLookupService)
|
||||||
c.Provide(NewWindowsService)
|
|
||||||
c.Provide(NewFirewallService)
|
|
||||||
c.Provide(NewMembershipService)
|
c.Provide(NewMembershipService)
|
||||||
c.Provide(NewWebSocketService)
|
c.Provide(NewWebSocketService)
|
||||||
c.Provide(NewLeaderboardService)
|
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")
|
logging.Debug("Initializing service dependencies")
|
||||||
err := c.Invoke(func(server *ServerService, api *ServiceControlService, config *ConfigService) {
|
err := c.Invoke(func(server *ServerService, api *ServiceControlService, config *ConfigService) {
|
||||||
logging.Debug("Setting up service cross-references")
|
logging.Debug("Setting up service cross-references")
|
||||||
api.SetServerService(server)
|
api.SetServerService(server)
|
||||||
config.SetServerService(server)
|
config.SetServerService(server)
|
||||||
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Panic("unable to initialize services: " + err.Error())
|
logging.Panic("unable to initialize services: " + err.Error())
|
||||||
}
|
}
|
||||||
logging.Debug("Completed service initialization")
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
52
local/service/service_control.go
Normal file → Executable file
52
local/service/service_control.go
Normal file → Executable file
@@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"acc-server-manager/local/model"
|
"acc-server-manager/local/model"
|
||||||
|
"acc-server-manager/local/platform"
|
||||||
"acc-server-manager/local/repository"
|
"acc-server-manager/local/repository"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -15,20 +16,23 @@ type ServiceControlService struct {
|
|||||||
serverRepository *repository.ServerRepository
|
serverRepository *repository.ServerRepository
|
||||||
serverService *ServerService
|
serverService *ServerService
|
||||||
statusCache *model.ServerStatusCache
|
statusCache *model.ServerStatusCache
|
||||||
windowsService *WindowsService
|
runtime platform.ServerRuntime
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServiceControlService(repository *repository.ServiceControlRepository,
|
func NewServiceControlService(
|
||||||
serverRepository *repository.ServerRepository) *ServiceControlService {
|
repository *repository.ServiceControlRepository,
|
||||||
|
serverRepository *repository.ServerRepository,
|
||||||
|
runtime platform.ServerRuntime,
|
||||||
|
) *ServiceControlService {
|
||||||
return &ServiceControlService{
|
return &ServiceControlService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
serverRepository: serverRepository,
|
serverRepository: serverRepository,
|
||||||
|
runtime: runtime,
|
||||||
statusCache: model.NewServerStatusCache(model.CacheConfig{
|
statusCache: model.NewServerStatusCache(model.CacheConfig{
|
||||||
ExpirationTime: 30 * time.Second,
|
ExpirationTime: 30 * time.Second,
|
||||||
ThrottleTime: 5 * time.Second,
|
ThrottleTime: 5 * time.Second,
|
||||||
DefaultStatus: model.StatusRunning,
|
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) {
|
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) {
|
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) {
|
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)
|
server, err := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status, err := as.runtime.Start(context.Background(), server.ID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
as.serverService.StartAccServerRuntime(server)
|
as.serverService.StartAccServerRuntime(server)
|
||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ServiceControlService) StopServer(serviceName string) (string, error) {
|
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)
|
server, err := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ServiceControlService) RestartServer(serviceName string) (string, error) {
|
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)
|
server, err := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status, err := as.runtime.Restart(context.Background(), server.ID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
as.serverService.StartAccServerRuntime(server)
|
as.serverService.StartAccServerRuntime(server)
|
||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
|
|||||||
17
local/service/steam_service.go
Normal file → Executable file
17
local/service/steam_service.go
Normal file → Executable file
@@ -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) {
|
func (s *SteamService) GetCredentials(ctx context.Context) (*model.SteamCredentials, error) {
|
||||||
return s.repository.GetCurrent(ctx)
|
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 {
|
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()
|
steamCMDPath := env.GetSteamCMDPath()
|
||||||
steamCMDDir := filepath.Dir(steamCMDPath)
|
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)
|
return fmt.Errorf("failed to get Steam credentials: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
steamCMDPath := env.GetSteamCMDPath()
|
steamCMDPath := getSteamCMDPath()
|
||||||
|
|
||||||
steamCMDArgs := []string{
|
steamCMDArgs := []string{
|
||||||
"+force_install_dir", absPath,
|
"+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)
|
return fmt.Errorf("failed to get Steam credentials: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
steamCMDPath := env.GetSteamCMDPath()
|
steamCMDPath := getSteamCMDPath()
|
||||||
|
|
||||||
steamCMDArgs := []string{
|
steamCMDArgs := []string{
|
||||||
"+force_install_dir", absPath,
|
"+force_install_dir", absPath,
|
||||||
|
|||||||
68
local/service/windows_runtime.go
Normal file
68
local/service/windows_runtime.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
4
local/utl/db/db.go
Normal file → Executable file
4
local/utl/db/db.go
Normal file → Executable file
@@ -76,6 +76,10 @@ func runMigrations(db *gorm.DB) {
|
|||||||
logging.Error("Failed to run password security migration: %v", err)
|
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")
|
logging.Info("Custom database migrations completed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user