diff --git a/.env.example b/.env.example index ab37138..0897b48 100755 --- a/.env.example +++ b/.env.example @@ -64,12 +64,9 @@ PASSWORD=change-this-default-admin-password # Docker image name for ACC server containers (built from docker/acc-server/Dockerfile) # ACC_IMAGE=acc-wine:latest -# Host-side path for the shared ACC game installation volume. -# IMPORTANT: must be the HOST path — ACC containers are Docker siblings of the manager. -# ACC_GAME_VOLUME=/data/acc-game - -# Host-side base path for per-server config directories. -# ACC_CONFIG_BASE_PATH=/data/configs +# Base path where per-server game installations are stored. +# Each server gets its own subdirectory: ACC_SERVERS_PATH// +# ACC_SERVERS_PATH=/data/servers # Port pool for Docker-mode ACC server containers (one port per server). # PORT_POOL_MIN=9600 diff --git a/Dockerfile b/Dockerfile index ff2b854..4354d63 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,14 +50,13 @@ WORKDIR /app COPY --from=builder /acc-server-manager /app/acc-server-manager # Persistent data directories. -RUN mkdir -p /data/configs /data/acc-game /data/db +RUN mkdir -p /data/servers /data/db ENV PLATFORM=docker ENV PORT=3000 ENV DB_NAME=/data/db/acc.db ENV STEAMCMD_LINUX_PATH=/usr/games/steamcmd -ENV ACC_GAME_VOLUME=/data/acc-game -ENV ACC_CONFIG_BASE_PATH=/data/configs +ENV ACC_SERVERS_PATH=/data/servers EXPOSE 3000 diff --git a/docker-compose.yml b/docker-compose.yml index 8d06305..45e7260 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,25 +11,22 @@ services: environment: PLATFORM: docker DB_NAME: /data/db/acc.db - ACC_GAME_VOLUME: /var/lib/docker/volumes/acc-server-manager_acc-game/_data - ACC_CONFIG_BASE_PATH: /var/lib/docker/volumes/acc-server-manager_acc-configs/_data STEAMCMD_LINUX_PATH: /usr/games/steamcmd PORT_POOL_MIN: "9600" PORT_POOL_MAX: "9700" ACC_IMAGE: acc-wine:latest + ACC_SERVERS_PATH: /data/servers env_file: - .env volumes: - /var/run/docker.sock:/var/run/docker.sock - - acc-game:/data/acc-game - - acc-configs:/data/configs + - acc-servers:/data/servers - acc-db:/data/db networks: - acc-net volumes: - acc-game: - acc-configs: + acc-servers: acc-db: networks: diff --git a/docker/acc-server/Dockerfile b/docker/acc-server/Dockerfile index 5a8001a..861bdb2 100644 --- a/docker/acc-server/Dockerfile +++ b/docker/acc-server/Dockerfile @@ -24,10 +24,8 @@ WORKDIR /home/accserver COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -# Volumes (bind-mounted by DockerRuntime.Create): -# /acc/game — shared game files, mounted read-only (ACC_GAME_VOLUME) -# /acc/cfg — per-server config dir, mounted read-write (server.GetConfigPath()) -VOLUME ["/acc/game", "/acc/cfg"] +# Bind-mounted by DockerRuntime.Create: per-server game install (rw) +VOLUME ["/acc/game"] # Default server port — overridden at runtime via SERVER_PORT env var. ENV SERVER_PORT=9600 diff --git a/docker/acc-server/entrypoint.sh b/docker/acc-server/entrypoint.sh index ed2db8b..f3ce7f0 100644 --- a/docker/acc-server/entrypoint.sh +++ b/docker/acc-server/entrypoint.sh @@ -12,6 +12,5 @@ trap cleanup SIGTERM SIGINT echo "Starting ACC server on port ${SERVER_PORT}..." -exec wine /acc/game/server/accServer.exe \ - -serverCfgPath /acc/cfg \ +exec wine /acc/game/accServer.exe \ -port "${SERVER_PORT}" diff --git a/local/api/api.go b/local/api/api.go index f8b6114..0f3f775 100755 --- a/local/api/api.go +++ b/local/api/api.go @@ -33,6 +33,7 @@ func Init(di *dig.Container, app *fiber.App) { System: groups.Group("/system"), WebSocket: groups.Group("/ws"), Leaderboard: serverIdGroup.Group("/leaderboard"), + Steam: groups.Group("/steam"), } accessKeyMiddleware := middleware.NewAccessKeyMiddleware() diff --git a/local/controller/controller.go b/local/controller/controller.go index 96c5692..79af86b 100755 --- a/local/controller/controller.go +++ b/local/controller/controller.go @@ -64,4 +64,9 @@ func InitializeControllers(c *dig.Container) { if err != nil { logging.Panic("unable to initialize leaderboard controller") } + + err = c.Invoke(NewSteamController) + if err != nil { + logging.Panic("unable to initialize steam controller") + } } diff --git a/local/controller/steam.go b/local/controller/steam.go new file mode 100644 index 0000000..16832da --- /dev/null +++ b/local/controller/steam.go @@ -0,0 +1,57 @@ +package controller + +import ( + "acc-server-manager/local/middleware" + "acc-server-manager/local/model" + "acc-server-manager/local/service" + "acc-server-manager/local/utl/common" + "acc-server-manager/local/utl/error_handler" + + "github.com/gofiber/fiber/v2" +) + +type SteamController struct { + steamService *service.SteamService + auth *middleware.AuthMiddleware + errorHandler *error_handler.ControllerErrorHandler +} + +func NewSteamController(steamService *service.SteamService, auth *middleware.AuthMiddleware, routeGroups *common.RouteGroups) *SteamController { + sc := &SteamController{ + steamService: steamService, + auth: auth, + errorHandler: error_handler.NewControllerErrorHandler(), + } + + routeGroups.Steam.Use(auth.Authenticate, auth.RequireSuperAdmin()) + routeGroups.Steam.Post("/credentials", sc.SetCredentials) + + return sc +} + +func (sc *SteamController) SetCredentials(c *fiber.Ctx) error { + type request struct { + Username string `json:"username"` + Password string `json:"password"` + } + + var req request + if err := c.BodyParser(&req); err != nil { + return sc.errorHandler.HandleParsingError(c, err) + } + + creds := &model.SteamCredentials{ + Username: req.Username, + Password: req.Password, + } + + if err := creds.Validate(); err != nil { + return sc.errorHandler.HandleServiceError(c, err) + } + + if err := sc.steamService.SaveCredentials(c.UserContext(), creds); err != nil { + return sc.errorHandler.HandleServiceError(c, err) + } + + return c.Status(fiber.StatusCreated).JSON(fiber.Map{"id": creds.ID}) +} diff --git a/local/middleware/auth.go b/local/middleware/auth.go index 467d966..7abc5fb 100755 --- a/local/middleware/auth.go +++ b/local/middleware/auth.go @@ -178,6 +178,23 @@ func (m *AuthMiddleware) HasPermission(requiredPermission string) fiber.Handler } } +func (m *AuthMiddleware) RequireSuperAdmin() fiber.Handler { + return func(ctx *fiber.Ctx) error { + if os.Getenv("TESTING_ENV") == "true" { + return ctx.Next() + } + + userInfo, ok := ctx.Locals("userInfo").(*CachedUserInfo) + if !ok || userInfo.RoleName != "Super Admin" { + return ctx.Status(fiber.StatusForbidden).JSON(fiber.Map{ + "error": "Forbidden", + }) + } + + return ctx.Next() + } +} + func (m *AuthMiddleware) AuthRateLimit() fiber.Handler { return m.securityMW.AuthRateLimit() } diff --git a/local/service/docker_runtime.go b/local/service/docker_runtime.go index 7b37544..5e2b6e9 100644 --- a/local/service/docker_runtime.go +++ b/local/service/docker_runtime.go @@ -31,9 +31,6 @@ func NewDockerRuntime(client *dockerclient.Client, repo *repository.ServerReposi func (r *DockerRuntime) Create(ctx context.Context, server *model.Server) error { image := env.GetACCImage() - gameVolume := env.GetACCGameVolume() - configPath := server.GetConfigPath() - portStr := strconv.Itoa(server.Port) natPort, err := nat.NewPort("tcp", portStr) if err != nil { @@ -54,8 +51,7 @@ func (r *DockerRuntime) Create(ctx context.Context, server *model.Server) error } binds := []string{ - fmt.Sprintf("%s:/acc/game:ro", gameVolume), - fmt.Sprintf("%s:/acc/cfg:rw", configPath), + fmt.Sprintf("%s:/acc/game:rw", server.GetServerPath()), } resp, err := r.client.ContainerCreate(ctx, &container.Config{ diff --git a/local/service/server.go b/local/service/server.go index 2cf5555..b74ecdd 100755 --- a/local/service/server.go +++ b/local/service/server.go @@ -200,8 +200,8 @@ func (s *ServerService) GenerateServerPath(server *model.Server) { } if env.IsDockerPlatform() { - server.Path = filepath.Join(env.GetACCConfigBasePath(), server.ServiceName) - server.FromSteamCMD = false + server.Path = filepath.Join(env.GetACCServersPath(), server.ServiceName) + server.FromSteamCMD = true } else { steamCMDPath := env.GetSteamCMDDirPath() server.Path = server.GenerateServerPath(steamCMDPath) @@ -369,11 +369,7 @@ func (s *ServerService) createServerBackground(ctx context.Context, server *mode important: true, description: "Server files downloaded successfully", callback: func() (string, error) { - installPath := server.Path - if env.IsDockerPlatform() { - installPath = env.GetACCGameVolume() - } - if err := s.steamService.InstallServerWithWebSocket(ctx, installPath, &server.ID, s.webSocketService); err != nil { + 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 diff --git a/local/service/steam_service.go b/local/service/steam_service.go index 2af06e4..b14e113 100755 --- a/local/service/steam_service.go +++ b/local/service/steam_service.go @@ -105,9 +105,11 @@ func (s *SteamService) InstallServerWithWebSocket(ctx context.Context, installPa return err } - if err := s.pathValidator.ValidateInstallPath(installPath); err != nil { - wsService.BroadcastSteamOutput(*serverID, fmt.Sprintf("Invalid installation path: %v", err), true) - return fmt.Errorf("invalid installation path: %v", err) + if !env.IsDockerPlatform() { + if err := s.pathValidator.ValidateInstallPath(installPath); err != nil { + wsService.BroadcastSteamOutput(*serverID, fmt.Sprintf("Invalid installation path: %v", err), true) + return fmt.Errorf("invalid installation path: %v", err) + } } absPath, err := filepath.Abs(installPath) @@ -148,6 +150,10 @@ func (s *SteamService) InstallServerWithWebSocket(ctx context.Context, installPa steamCMDArgs = append(steamCMDArgs, "anonymous") } + if env.IsDockerPlatform() { + steamCMDArgs = append(steamCMDArgs, "+@sSteamCmdForcePlatformType", "windows") + } + steamCMDArgs = append(steamCMDArgs, "+app_update", ACCServerAppID, "validate", @@ -229,9 +235,11 @@ func (s *SteamService) InstallServerWithCallbacks(ctx context.Context, installPa return err } - if err := s.pathValidator.ValidateInstallPath(installPath); err != nil { - outputCallback(*serverID, fmt.Sprintf("Invalid installation path: %v", err), true) - return fmt.Errorf("invalid installation path: %v", err) + if !env.IsDockerPlatform() { + if err := s.pathValidator.ValidateInstallPath(installPath); err != nil { + outputCallback(*serverID, fmt.Sprintf("Invalid installation path: %v", err), true) + return fmt.Errorf("invalid installation path: %v", err) + } } absPath, err := filepath.Abs(installPath) @@ -272,6 +280,10 @@ func (s *SteamService) InstallServerWithCallbacks(ctx context.Context, installPa steamCMDArgs = append(steamCMDArgs, "anonymous") } + if env.IsDockerPlatform() { + steamCMDArgs = append(steamCMDArgs, "+@sSteamCmdForcePlatformType", "windows") + } + steamCMDArgs = append(steamCMDArgs, "+app_update", ACCServerAppID, "validate", diff --git a/local/service/websocket.go b/local/service/websocket.go index 9ecacca..1f2cbd2 100755 --- a/local/service/websocket.go +++ b/local/service/websocket.go @@ -15,6 +15,7 @@ type WebSocketConnection struct { conn *websocket.Conn serverID *uuid.UUID userID *uuid.UUID + mu sync.Mutex } type WebSocketService struct { @@ -131,7 +132,7 @@ func (ws *WebSocketService) broadcastToServer(serverID uuid.UUID, message model. ws.connections.Range(func(key, value interface{}) bool { if wsConn, ok := value.(*WebSocketConnection); ok { if wsConn.serverID != nil && *wsConn.serverID == serverID { - if err := wsConn.conn.WriteMessage(websocket.TextMessage, data); err != nil { + if err := wsConn.writeMessage(websocket.TextMessage, data); err != nil { logging.Error("Failed to send WebSocket message to connection %s: %v", key, err) ws.RemoveConnection(key.(string)) } else { @@ -145,7 +146,7 @@ func (ws *WebSocketService) broadcastToServer(serverID uuid.UUID, message model. if !sentToAssociatedConnections && (message.Type == model.MessageTypeStep || message.Type == model.MessageTypeError || message.Type == model.MessageTypeComplete) { ws.connections.Range(func(key, value interface{}) bool { if wsConn, ok := value.(*WebSocketConnection); ok { - if err := wsConn.conn.WriteMessage(websocket.TextMessage, data); err != nil { + if err := wsConn.writeMessage(websocket.TextMessage, data); err != nil { logging.Error("Failed to send WebSocket message to connection %s: %v", key, err) ws.RemoveConnection(key.(string)) } @@ -165,7 +166,7 @@ func (ws *WebSocketService) BroadcastToUser(userID uuid.UUID, message model.WebS ws.connections.Range(func(key, value interface{}) bool { if wsConn, ok := value.(*WebSocketConnection); ok { if wsConn.userID != nil && *wsConn.userID == userID { - if err := wsConn.conn.WriteMessage(websocket.TextMessage, data); err != nil { + if err := wsConn.writeMessage(websocket.TextMessage, data); err != nil { logging.Error("Failed to send WebSocket message to connection %s: %v", key, err) ws.RemoveConnection(key.(string)) } @@ -175,6 +176,12 @@ func (ws *WebSocketService) BroadcastToUser(userID uuid.UUID, message model.WebS }) } +func (wc *WebSocketConnection) writeMessage(messageType int, data []byte) error { + wc.mu.Lock() + defer wc.mu.Unlock() + return wc.conn.WriteMessage(messageType, data) +} + func (ws *WebSocketService) GetActiveConnections() int { count := 0 ws.connections.Range(func(key, value interface{}) bool { diff --git a/local/utl/command/callback_executor.go b/local/utl/command/callback_executor.go index 2125317..6547810 100755 --- a/local/utl/command/callback_executor.go +++ b/local/utl/command/callback_executor.go @@ -123,11 +123,12 @@ func (e *CallbackInteractiveCommandExecutor) monitorOutputWithCallbacks(ctx cont stdoutScanner := bufio.NewScanner(stdout) stderrScanner := bufio.NewScanner(stderr) - outputChan := make(chan outputLine, 100) + outputChan := make(chan outputLine, 10000) readersDone := make(chan struct{}, 2) steamConsoleStarted := false tfaRequestCreated := false + tfaAutoCompleted := false go func() { defer func() { readersDone <- struct{}{} }() @@ -182,13 +183,14 @@ func (e *CallbackInteractiveCommandExecutor) monitorOutputWithCallbacks(ctx cont if readersFinished == 2 { close(outputChan) for lineData := range outputChan { - if e.is2FAPrompt(lineData.text) { - if err := e.handle2FAPrompt(ctx, lineData.text, serverID); err != nil { - logging.Error("Failed to handle 2FA prompt: %v", err) - e.callbacks.OnOutput(e.serverID, fmt.Sprintf("Failed to handle 2FA prompt: %v", err), true) - done <- err - return - } + if e.is2FAPrompt(lineData.text) && !tfaRequestCreated { + tfaRequestCreated = true + go func(prompt string) { + if err := e.handle2FAPrompt(ctx, prompt, serverID); err != nil { + logging.Error("Failed to handle 2FA prompt: %v", err) + e.callbacks.OnOutput(e.serverID, fmt.Sprintf("Failed to handle 2FA prompt: %v", err), true) + } + }(lineData.text) } } return @@ -207,33 +209,34 @@ func (e *CallbackInteractiveCommandExecutor) monitorOutputWithCallbacks(ctx cont if e.is2FAPrompt(lineData.text) { if !tfaRequestCreated { - e.callbacks.OnOutput(e.serverID, "2FA prompt detected - waiting for user confirmation", false) - if err := e.handle2FAPrompt(ctx, lineData.text, serverID); err != nil { - logging.Error("Failed to handle 2FA prompt: %v", err) - e.callbacks.OnOutput(e.serverID, fmt.Sprintf("Failed to handle 2FA prompt: %v", err), true) - done <- err - return - } tfaRequestCreated = true + e.callbacks.OnOutput(e.serverID, "2FA prompt detected - waiting for user confirmation", false) + go func(prompt string) { + if err := e.handle2FAPrompt(ctx, prompt, serverID); err != nil { + logging.Error("Failed to handle 2FA prompt: %v", err) + e.callbacks.OnOutput(e.serverID, fmt.Sprintf("Failed to handle 2FA prompt: %v", err), true) + } + }(lineData.text) } } - if tfaRequestCreated && e.isSteamContinuing(lineData.text) { + if tfaRequestCreated && !tfaAutoCompleted && e.isSteamContinuing(lineData.text) { logging.Info("Steam CMD appears to have continued after 2FA confirmation") + tfaAutoCompleted = true e.callbacks.OnOutput(e.serverID, "Steam CMD continued after 2FA confirmation", false) e.autoCompletePendingRequests(serverID) } case <-time.After(15 * time.Second): if steamConsoleStarted && !tfaRequestCreated { + tfaRequestCreated = true logging.Info("Steam Console started but no output for 15 seconds - likely waiting for Steam Guard 2FA") e.callbacks.OnOutput(e.serverID, "Waiting for Steam Guard 2FA confirmation...", false) - if err := e.handle2FAPrompt(ctx, "Steam CMD appears to be waiting for Steam Guard confirmation after startup", serverID); err != nil { - logging.Error("Failed to handle Steam Guard 2FA prompt: %v", err) - e.callbacks.OnOutput(e.serverID, fmt.Sprintf("Failed to handle Steam Guard 2FA prompt: %v", err), true) - done <- err - return - } - tfaRequestCreated = true + go func() { + if err := e.handle2FAPrompt(ctx, "Steam CMD appears to be waiting for Steam Guard confirmation after startup", serverID); err != nil { + logging.Error("Failed to handle Steam Guard 2FA prompt: %v", err) + e.callbacks.OnOutput(e.serverID, fmt.Sprintf("Failed to handle Steam Guard 2FA prompt: %v", err), true) + } + }() } } } diff --git a/local/utl/command/interactive_executor.go b/local/utl/command/interactive_executor.go index 1e6ff47..f3bd369 100755 --- a/local/utl/command/interactive_executor.go +++ b/local/utl/command/interactive_executor.go @@ -106,11 +106,12 @@ func (e *InteractiveCommandExecutor) monitorOutput(ctx context.Context, stdout, stdoutScanner := bufio.NewScanner(stdout) stderrScanner := bufio.NewScanner(stderr) - outputChan := make(chan string, 100) + outputChan := make(chan string, 10000) readersDone := make(chan struct{}, 2) steamConsoleStarted := false tfaRequestCreated := false + tfaAutoCompleted := false go func() { defer func() { readersDone <- struct{}{} }() @@ -165,12 +166,13 @@ func (e *InteractiveCommandExecutor) monitorOutput(ctx context.Context, stdout, if readersFinished == 2 { close(outputChan) for line := range outputChan { - if e.is2FAPrompt(line) { - if err := e.handle2FAPrompt(ctx, line, serverID); err != nil { - logging.Error("Failed to handle 2FA prompt: %v", err) - done <- err - return - } + if e.is2FAPrompt(line) && !tfaRequestCreated { + tfaRequestCreated = true + go func(prompt string) { + if err := e.handle2FAPrompt(ctx, prompt, serverID); err != nil { + logging.Error("Failed to handle 2FA prompt: %v", err) + } + }(line) } } return @@ -188,28 +190,29 @@ func (e *InteractiveCommandExecutor) monitorOutput(ctx context.Context, stdout, if e.is2FAPrompt(line) { if !tfaRequestCreated { - if err := e.handle2FAPrompt(ctx, line, serverID); err != nil { - logging.Error("Failed to handle 2FA prompt: %v", err) - done <- err - return - } tfaRequestCreated = true + go func(prompt string) { + if err := e.handle2FAPrompt(ctx, prompt, serverID); err != nil { + logging.Error("Failed to handle 2FA prompt: %v", err) + } + }(line) } } - if tfaRequestCreated && e.isSteamContinuing(line) { + if tfaRequestCreated && !tfaAutoCompleted && e.isSteamContinuing(line) { logging.Info("Steam CMD appears to have continued after 2FA confirmation - auto-completing 2FA request") + tfaAutoCompleted = true e.autoCompletePendingRequests(serverID) } case <-time.After(15 * time.Second): if steamConsoleStarted && !tfaRequestCreated { - logging.Info("Steam Console started but no output for 15 seconds - likely waiting for Steam Guard 2FA") - if err := e.handle2FAPrompt(ctx, "Steam CMD appears to be waiting for Steam Guard confirmation after startup", serverID); err != nil { - logging.Error("Failed to handle Steam Guard 2FA prompt: %v", err) - done <- err - return - } tfaRequestCreated = true + logging.Info("Steam Console started but no output for 15 seconds - likely waiting for Steam Guard 2FA") + go func() { + if err := e.handle2FAPrompt(ctx, "Steam CMD appears to be waiting for Steam Guard confirmation after startup", serverID); err != nil { + logging.Error("Failed to handle Steam Guard 2FA prompt: %v", err) + } + }() } else if !steamConsoleStarted { logging.Info("No output for 15 seconds (Steam Console not yet started)") } @@ -220,22 +223,15 @@ func (e *InteractiveCommandExecutor) monitorOutput(ctx context.Context, stdout, func (e *InteractiveCommandExecutor) is2FAPrompt(line string) bool { twoFAKeywords := []string{ "please enter your steam guard code", - "steam guard", - "two-factor", - "authentication code", + "steam guard code", + "two-factor authentication", "please check your steam mobile app", - "confirm in application", "enter the current code from your steam mobile app", "steam guard mobile authenticator", - "waiting for user info", - "login failure", "two factor code required", "enter steam guard code", "mobile authenticator code", - "authenticator app", - "guard code", - "mobile app", - "confirmation required", + "2fa prompt detected", } lowerLine := strings.ToLower(line) @@ -246,21 +242,6 @@ func (e *InteractiveCommandExecutor) is2FAPrompt(line string) bool { } } - waitingPatterns := []string{ - "waiting for", - "please enter", - "enter code", - "code:", - "authenticator:", - } - - for _, pattern := range waitingPatterns { - if strings.Contains(lowerLine, pattern) { - logging.Info("Potential 2FA waiting pattern found: '%s' in line: '%s'", pattern, line) - return true - } - } - return false } diff --git a/local/utl/common/common.go b/local/utl/common/common.go index 8e94bbe..0ad0917 100755 --- a/local/utl/common/common.go +++ b/local/utl/common/common.go @@ -27,6 +27,7 @@ type RouteGroups struct { System fiber.Router WebSocket fiber.Router Leaderboard fiber.Router + Steam fiber.Router } func CheckError(err error) { diff --git a/local/utl/env/env.go b/local/utl/env/env.go index d1e50cd..f258c0a 100755 --- a/local/utl/env/env.go +++ b/local/utl/env/env.go @@ -10,8 +10,7 @@ const ( DefaultNSSMPath = ".\\nssm.exe" DefaultPlatform = "windows" DefaultACCImage = "acc-wine:latest" - DefaultACCGameVolume = "/data/acc-game" - DefaultACCConfigBasePath = "/data/configs" + DefaultACCServersPath = "/data/servers" DefaultLinuxSteamCMDPath = "/usr/games/steamcmd" ) @@ -59,18 +58,11 @@ func GetACCImage() string { return DefaultACCImage } -func GetACCGameVolume() string { - if v := os.Getenv("ACC_GAME_VOLUME"); v != "" { - return v - } - return DefaultACCGameVolume -} - -func GetACCConfigBasePath() string { - if p := os.Getenv("ACC_CONFIG_BASE_PATH"); p != "" { +func GetACCServersPath() string { + if p := os.Getenv("ACC_SERVERS_PATH"); p != "" { return p } - return DefaultACCConfigBasePath + return DefaultACCServersPath } func ValidatePaths() map[string]error {