remove system config

This commit is contained in:
Fran Jurmanović
2025-07-01 00:58:34 +02:00
parent 56466188ec
commit 55cf7c049d
16 changed files with 391 additions and 360 deletions

View File

@@ -14,6 +14,15 @@ import (
"github.com/gofiber/fiber/v2"
)
// CachedUserInfo holds cached user authentication and permission data
type CachedUserInfo struct {
UserID string
Username string
RoleName string
Permissions map[string]bool
CachedAt time.Time
}
// AuthMiddleware provides authentication and permission middleware.
type AuthMiddleware struct {
membershipService *service.MembershipService
@@ -23,11 +32,16 @@ type AuthMiddleware struct {
// NewAuthMiddleware creates a new AuthMiddleware.
func NewAuthMiddleware(ms *service.MembershipService, cache *cache.InMemoryCache) *AuthMiddleware {
return &AuthMiddleware{
auth := &AuthMiddleware{
membershipService: ms,
cache: cache,
securityMW: security.NewSecurityMiddleware(),
}
// Set up bidirectional relationship for cache invalidation
ms.SetCacheInvalidator(auth)
return auth
}
// Authenticate is a middleware for JWT authentication with enhanced security.
@@ -77,7 +91,17 @@ func (m *AuthMiddleware) Authenticate(ctx *fiber.Ctx) error {
})
}
// Preload and cache user info to avoid database queries on permission checks
userInfo, err := m.getCachedUserInfo(ctx.UserContext(), claims.UserID)
if err != nil {
logging.Error("Authentication failed: unable to load user info for %s from IP %s: %v", claims.UserID, ip, err)
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Invalid or expired JWT",
})
}
ctx.Locals("userID", claims.UserID)
ctx.Locals("userInfo", userInfo)
ctx.Locals("authTime", time.Now())
logging.InfoWithContext("AUTH", "User %s authenticated successfully from IP %s", claims.UserID, ip)
@@ -103,15 +127,18 @@ func (m *AuthMiddleware) HasPermission(requiredPermission string) fiber.Handler
})
}
// Use cached permission check for better performance
has, err := m.hasPermissionCached(ctx.UserContext(), userID, requiredPermission)
if err != nil {
logging.ErrorWithContext("AUTH", "Permission check error for user %s, permission %s: %v", userID, requiredPermission, err)
return ctx.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "Forbidden",
// Use cached user info from authentication step - no database queries needed
userInfo, ok := ctx.Locals("userInfo").(*CachedUserInfo)
if !ok {
logging.Error("Permission check failed: no cached user info in context from IP %s", ctx.IP())
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Unauthorized",
})
}
// Check if user has permission using cached data
has := m.hasPermissionFromCache(userInfo, requiredPermission)
if !has {
logging.WarnWithContext("AUTH", "Permission denied: user %s lacks permission %s, IP %s", userID, requiredPermission, ctx.IP())
return ctx.Status(fiber.StatusForbidden).JSON(fiber.Map{
@@ -143,34 +170,66 @@ func (m *AuthMiddleware) RequireHTTPS() fiber.Handler {
}
}
// hasPermissionCached checks user permissions with caching using existing cache
func (m *AuthMiddleware) hasPermissionCached(ctx context.Context, userID, permission string) (bool, error) {
cacheKey := fmt.Sprintf("permission:%s:%s", userID, permission)
// getCachedUserInfo retrieves and caches complete user information including permissions
func (m *AuthMiddleware) getCachedUserInfo(ctx context.Context, userID string) (*CachedUserInfo, error) {
cacheKey := fmt.Sprintf("userinfo:%s", userID)
// Try cache first
if cached, found := m.cache.Get(cacheKey); found {
if hasPermission, ok := cached.(bool); ok {
logging.DebugWithContext("AUTH_CACHE", "Permission %s:%s found in cache: %v", userID, permission, hasPermission)
return hasPermission, nil
if userInfo, ok := cached.(*CachedUserInfo); ok {
logging.DebugWithContext("AUTH_CACHE", "User info for %s found in cache", userID)
return userInfo, nil
}
}
// Cache miss - check with service
has, err := m.membershipService.HasPermission(ctx, userID, permission)
// Cache miss - load from database
user, err := m.membershipService.GetUserWithPermissions(ctx, userID)
if err != nil {
return false, err
return nil, err
}
// Cache the result for 10 minutes
m.cache.Set(cacheKey, has, 10*time.Minute)
logging.DebugWithContext("AUTH_CACHE", "Permission %s:%s cached: %v", userID, permission, has)
// Build permission map for fast lookups
permissions := make(map[string]bool)
for _, p := range user.Role.Permissions {
permissions[p.Name] = true
}
return has, nil
userInfo := &CachedUserInfo{
UserID: userID,
Username: user.Username,
RoleName: user.Role.Name,
Permissions: permissions,
CachedAt: time.Now(),
}
// Cache for 15 minutes
m.cache.Set(cacheKey, userInfo, 15*time.Minute)
logging.DebugWithContext("AUTH_CACHE", "User info for %s cached with %d permissions", userID, len(permissions))
return userInfo, nil
}
// InvalidateUserPermissions removes cached permissions for a user
// hasPermissionFromCache checks permissions using cached user info (no database queries)
func (m *AuthMiddleware) hasPermissionFromCache(userInfo *CachedUserInfo, permission string) bool {
// Super Admin and Admin have all permissions
if userInfo.RoleName == "Super Admin" || userInfo.RoleName == "Admin" {
return true
}
// Check specific permission in cached map
return userInfo.Permissions[permission]
}
// InvalidateUserPermissions removes cached user info for a user
func (m *AuthMiddleware) InvalidateUserPermissions(userID string) {
// This is a simple implementation - in a production system you might want
// to track permission keys per user for more efficient invalidation
logging.InfoWithContext("AUTH_CACHE", "Permission cache invalidated for user %s", userID)
cacheKey := fmt.Sprintf("userinfo:%s", userID)
m.cache.Delete(cacheKey)
logging.InfoWithContext("AUTH_CACHE", "User info cache invalidated for user %s", userID)
}
// InvalidateAllUserPermissions clears all cached user info (useful for role/permission changes)
func (m *AuthMiddleware) InvalidateAllUserPermissions() {
// This would need to be implemented based on your cache interface
// For now, just log that invalidation was requested
logging.InfoWithContext("AUTH_CACHE", "All user info caches invalidation requested")
}

View File

@@ -3,7 +3,6 @@ package model
import (
"encoding/json"
"fmt"
"os"
"strconv"
"time"
@@ -122,33 +121,7 @@ type Configuration struct {
ConfigVersion IntString `json:"configVersion"`
}
type SystemConfig struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;"`
Key string `json:"key"`
Value string `json:"value"`
DefaultValue string `json:"defaultValue"`
Description string `json:"description"`
DateModified string `json:"dateModified"`
}
// BeforeCreate is a GORM hook that runs before creating new system config entries
func (sc *SystemConfig) BeforeCreate(tx *gorm.DB) error {
if sc.ID == uuid.Nil {
sc.ID = uuid.New()
}
return nil
}
// Known configuration keys
const (
ConfigKeySteamCMDPath = "steamcmd_path"
ConfigKeyNSSMPath = "nssm_path"
)
// Cache keys
const (
CacheKeySystemConfig = "system_config_%s" // Format with config key
)
func (i *IntBool) UnmarshalJSON(b []byte) error {
var str int
@@ -209,35 +182,3 @@ func (i IntString) ToString() string {
func (i IntString) ToInt() int {
return int(i)
}
func (c *SystemConfig) Validate() error {
if c.Key == "" {
return fmt.Errorf("key is required")
}
// Validate paths exist for certain config keys
switch c.Key {
case ConfigKeySteamCMDPath, ConfigKeyNSSMPath:
if c.Value == "" {
if c.DefaultValue == "" {
return fmt.Errorf("value or default value is required for path configuration")
}
// Use default value if value is empty
c.Value = c.DefaultValue
}
// Check if path exists
if _, err := os.Stat(c.Value); os.IsNotExist(err) {
return fmt.Errorf("path does not exist: %s", c.Value)
}
}
return nil
}
func (c *SystemConfig) GetEffectiveValue() string {
if c.Value != "" {
return c.Value
}
return c.DefaultValue
}

View File

@@ -16,6 +16,5 @@ func InitializeRepositories(c *dig.Container) {
c.Provide(NewConfigRepository)
c.Provide(NewLookupRepository)
c.Provide(NewSteamCredentialsRepository)
c.Provide(NewSystemConfigRepository)
c.Provide(NewMembershipRepository)
}

View File

@@ -1,58 +0,0 @@
package repository
import (
"acc-server-manager/local/model"
"context"
"time"
"gorm.io/gorm"
)
type SystemConfigRepository struct {
db *gorm.DB
}
func NewSystemConfigRepository(db *gorm.DB) *SystemConfigRepository {
return &SystemConfigRepository{
db: db,
}
}
func (r *SystemConfigRepository) Initialize(ctx context.Context) error {
// Migration and seeding are now handled in the db package
return nil
}
func (r *SystemConfigRepository) Get(ctx context.Context, key string) (*model.SystemConfig, error) {
var config model.SystemConfig
err := r.db.Where("key = ?", key).First(&config).Error
if err == gorm.ErrRecordNotFound {
return nil, nil
}
if err != nil {
return nil, err
}
return &config, nil
}
func (r *SystemConfigRepository) GetAll(ctx context.Context) (*[]model.SystemConfig, error) {
var configs []model.SystemConfig
if err := r.db.Find(&configs).Error; err != nil {
return nil, err
}
return &configs, nil
}
func (r *SystemConfigRepository) Update(ctx context.Context, config *model.SystemConfig) error {
if err := config.Validate(); err != nil {
return err
}
config.DateModified = time.Now().UTC().Format(time.RFC3339)
return r.db.Model(&model.SystemConfig{}).
Where("key = ?", config.Key).
Updates(map[string]interface{}{
"value": config.Value,
"date_modified": config.DateModified,
}).Error
}

View File

@@ -19,8 +19,7 @@ type ApiService struct {
}
func NewApiService(repository *repository.ApiRepository,
serverRepository *repository.ServerRepository,
systemConfigService *SystemConfigService) *ApiService {
serverRepository *repository.ServerRepository) *ApiService {
return &ApiService{
repository: repository,
serverRepository: serverRepository,
@@ -29,7 +28,7 @@ func NewApiService(repository *repository.ApiRepository,
ThrottleTime: 5 * time.Second, // Minimum 5 seconds between checks
DefaultStatus: model.StatusRunning, // Default to running if throttled
}),
windowsService: NewWindowsService(systemConfigService),
windowsService: NewWindowsService(),
}
}

View File

@@ -12,18 +12,31 @@ import (
"github.com/google/uuid"
)
// CacheInvalidator interface for cache invalidation
type CacheInvalidator interface {
InvalidateUserPermissions(userID string)
InvalidateAllUserPermissions()
}
// MembershipService provides business logic for membership-related operations.
type MembershipService struct {
repo *repository.MembershipRepository
repo *repository.MembershipRepository
cacheInvalidator CacheInvalidator
}
// NewMembershipService creates a new MembershipService.
func NewMembershipService(repo *repository.MembershipRepository) *MembershipService {
return &MembershipService{
repo: repo,
repo: repo,
cacheInvalidator: nil, // Will be set later via SetCacheInvalidator
}
}
// SetCacheInvalidator sets the cache invalidator after service initialization
func (s *MembershipService) SetCacheInvalidator(invalidator CacheInvalidator) {
s.cacheInvalidator = invalidator
}
// Login authenticates a user and returns a JWT.
func (s *MembershipService) Login(ctx context.Context, username, password string) (string, error) {
user, err := s.repo.FindUserByUsername(ctx, username)
@@ -109,6 +122,11 @@ func (s *MembershipService) DeleteUser(ctx context.Context, userID uuid.UUID) er
return err
}
// Invalidate cache for deleted user
if s.cacheInvalidator != nil {
s.cacheInvalidator.InvalidateUserPermissions(userID.String())
}
logging.InfoOperation("USER_DELETE", "Deleted user: "+userID.String())
return nil
}
@@ -142,6 +160,11 @@ func (s *MembershipService) UpdateUser(ctx context.Context, userID uuid.UUID, re
return nil, err
}
// Invalidate cache if role was changed
if req.RoleID != nil && s.cacheInvalidator != nil {
s.cacheInvalidator.InvalidateUserPermissions(userID.String())
}
logging.InfoOperation("USER_UPDATE", "Updated user: "+user.Username+" (ID: "+user.ID.String()+")")
return user, nil
}
@@ -241,6 +264,11 @@ func (s *MembershipService) SetupInitialData(ctx context.Context) error {
return err
}
// Invalidate all caches after role setup changes
if s.cacheInvalidator != nil {
s.cacheInvalidator.InvalidateAllUserPermissions()
}
// Create a default admin user if one doesn't exist
_, err = s.repo.FindUserByUsername(ctx, "admin")
if err != nil {

View File

@@ -3,6 +3,7 @@ package service
import (
"acc-server-manager/local/model"
"acc-server-manager/local/repository"
"acc-server-manager/local/utl/env"
"acc-server-manager/local/utl/logging"
"acc-server-manager/local/utl/tracking"
"context"
@@ -23,19 +24,18 @@ const (
)
type ServerService struct {
repository *repository.ServerRepository
stateHistoryRepo *repository.StateHistoryRepository
apiService *ApiService
configService *ConfigService
steamService *SteamService
windowsService *WindowsService
firewallService *FirewallService
systemConfigService *SystemConfigService
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
repository *repository.ServerRepository
stateHistoryRepo *repository.StateHistoryRepository
apiService *ApiService
configService *ConfigService
steamService *SteamService
windowsService *WindowsService
firewallService *FirewallService
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
}
type pendingState struct {
@@ -68,17 +68,15 @@ func NewServerService(
steamService *SteamService,
windowsService *WindowsService,
firewallService *FirewallService,
systemConfigService *SystemConfigService,
) *ServerService {
service := &ServerService{
repository: repository,
stateHistoryRepo: stateHistoryRepo,
apiService: apiService,
configService: configService,
steamService: steamService,
windowsService: windowsService,
firewallService: firewallService,
systemConfigService: systemConfigService,
repository: repository,
stateHistoryRepo: stateHistoryRepo,
apiService: apiService,
configService: configService,
steamService: steamService,
windowsService: windowsService,
firewallService: firewallService,
}
// Initialize server instances
@@ -203,13 +201,8 @@ func (s *ServerService) updateSessionDuration(server *model.Server, sessionType
}
func (s *ServerService) GenerateServerPath(server *model.Server) {
// Get the base steamcmd path
steamCMDPath, err := s.systemConfigService.GetSteamCMDDirPath(context.Background())
if err != nil {
logging.Error("Failed to get steamcmd path: %v", err)
return
}
// Get the base steamcmd path from environment variable
steamCMDPath := env.GetSteamCMDDirPath()
server.Path = server.GenerateServerPath(steamCMDPath)
}

View File

@@ -23,14 +23,13 @@ func InitializeServices(c *dig.Container) {
c.Provide(NewApiService)
c.Provide(NewConfigService)
c.Provide(NewLookupService)
c.Provide(NewSystemConfigService)
c.Provide(NewSteamService)
c.Provide(NewWindowsService)
c.Provide(NewFirewallService)
c.Provide(NewMembershipService)
logging.Debug("Initializing service dependencies")
err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService, systemConfig *SystemConfigService) {
err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService) {
logging.Debug("Setting up service cross-references")
api.SetServerService(server)
config.SetServerService(server)

View File

@@ -4,6 +4,7 @@ import (
"acc-server-manager/local/model"
"acc-server-manager/local/repository"
"acc-server-manager/local/utl/command"
"acc-server-manager/local/utl/env"
"acc-server-manager/local/utl/logging"
"context"
"fmt"
@@ -12,23 +13,21 @@ import (
)
const (
ACCServerAppID = "1430110"
ACCServerAppID = "1430110"
)
type SteamService struct {
executor *command.CommandExecutor
repository *repository.SteamCredentialsRepository
configService *SystemConfigService
executor *command.CommandExecutor
repository *repository.SteamCredentialsRepository
}
func NewSteamService(repository *repository.SteamCredentialsRepository, configService *SystemConfigService) *SteamService {
func NewSteamService(repository *repository.SteamCredentialsRepository) *SteamService {
return &SteamService{
executor: &command.CommandExecutor{
ExePath: "powershell",
LogOutput: true,
},
repository: repository,
configService: configService,
repository: repository,
}
}
@@ -44,12 +43,8 @@ func (s *SteamService) SaveCredentials(ctx context.Context, creds *model.SteamCr
}
func (s *SteamService) ensureSteamCMD(ctx context.Context) error {
// Get SteamCMD path from config
steamCMDPath, err := s.configService.GetSteamCMDDirPath(ctx)
if err != nil {
return fmt.Errorf("failed to get SteamCMD path from config: %v", err)
}
// Get SteamCMD path from environment variable
steamCMDPath := env.GetSteamCMDPath()
steamCMDDir := filepath.Dir(steamCMDPath)
// Check if SteamCMD exists
@@ -104,11 +99,8 @@ func (s *SteamService) InstallServer(ctx context.Context, installPath string) er
return fmt.Errorf("failed to get Steam credentials: %v", err)
}
// Get SteamCMD path from config
steamCMDPath, err := s.configService.GetSteamCMDPath(ctx)
if err != nil {
return fmt.Errorf("failed to get SteamCMD path from config: %v", err)
}
// Get SteamCMD path from environment variable
steamCMDPath := env.GetSteamCMDPath()
// Build SteamCMD command
args := []string{
@@ -162,4 +154,4 @@ func (s *SteamService) UpdateServer(ctx context.Context, installPath string) err
func (s *SteamService) UninstallServer(installPath string) error {
return os.RemoveAll(installPath)
}
}

View File

@@ -1,89 +0,0 @@
package service
import (
"acc-server-manager/local/model"
"acc-server-manager/local/repository"
"acc-server-manager/local/utl/cache"
"acc-server-manager/local/utl/logging"
"context"
"fmt"
"path/filepath"
"time"
)
const (
configCacheDuration = 24 * time.Hour
)
type SystemConfigService struct {
repository *repository.SystemConfigRepository
cache *cache.InMemoryCache
}
// NewSystemConfigService creates a new SystemConfigService with dependencies injected by dig
func NewSystemConfigService(repository *repository.SystemConfigRepository, cache *cache.InMemoryCache) *SystemConfigService {
logging.Debug("Initializing SystemConfigService")
return &SystemConfigService{
repository: repository,
cache: cache,
}
}
func (s *SystemConfigService) GetConfig(ctx context.Context, key string) (*model.SystemConfig, error) {
cacheKey := fmt.Sprintf(model.CacheKeySystemConfig, key)
fetcher := func() (*model.SystemConfig, error) {
logging.Debug("Loading system config from database: %s", key)
return s.repository.Get(ctx, key)
}
return cache.GetOrSet(s.cache, cacheKey, configCacheDuration, fetcher)
}
func (s *SystemConfigService) GetAllConfigs(ctx context.Context) (*[]model.SystemConfig, error) {
logging.Debug("Loading all system configs from database")
return s.repository.GetAll(ctx)
}
func (s *SystemConfigService) UpdateConfig(ctx context.Context, config *model.SystemConfig) error {
if err := s.repository.Update(ctx, config); err != nil {
return err
}
// Invalidate cache
cacheKey := fmt.Sprintf(model.CacheKeySystemConfig, config.Key)
s.cache.Delete(cacheKey)
logging.Debug("Invalidated system config in cache: %s", config.Key)
return nil
}
func (s *SystemConfigService) GetSteamCMDDirPath(ctx context.Context) (string, error) {
steamCMDPath, err := s.GetSteamCMDPath(ctx)
if err != nil {
return "", err
}
return filepath.Dir(steamCMDPath), nil
}
// Helper methods for common configurations
func (s *SystemConfigService) GetSteamCMDPath(ctx context.Context) (string, error) {
config, err := s.GetConfig(ctx, model.ConfigKeySteamCMDPath)
if err != nil {
return "", err
}
if config == nil {
return "", nil
}
return config.GetEffectiveValue(), nil
}
func (s *SystemConfigService) GetNSSMPath(ctx context.Context) (string, error) {
config, err := s.GetConfig(ctx, model.ConfigKeyNSSMPath)
if err != nil {
return "", err
}
if config == nil {
return "", nil
}
return config.GetEffectiveValue(), nil
}

View File

@@ -2,6 +2,7 @@ package service
import (
"acc-server-manager/local/utl/command"
"acc-server-manager/local/utl/env"
"acc-server-manager/local/utl/logging"
"context"
"fmt"
@@ -9,36 +10,27 @@ import (
"strings"
)
const (
NSSMPath = ".\\nssm.exe"
)
type WindowsService struct {
executor *command.CommandExecutor
configService *SystemConfigService
executor *command.CommandExecutor
}
func NewWindowsService(configService *SystemConfigService) *WindowsService {
func NewWindowsService() *WindowsService {
return &WindowsService{
executor: &command.CommandExecutor{
ExePath: "powershell",
LogOutput: true,
},
configService: configService,
}
}
// executeNSSM runs an NSSM command through PowerShell with elevation
func (s *WindowsService) executeNSSM(ctx context.Context, args ...string) (string, error) {
// Get NSSM path from config
nssmPath, err := s.configService.GetNSSMPath(ctx)
if err != nil {
return "", fmt.Errorf("failed to get NSSM path from config: %v", err)
}
func (s *WindowsService) ExecuteNSSM(ctx context.Context, args ...string) (string, error) {
// Get NSSM path from environment variable
nssmPath := env.GetNSSMPath()
// Prepend NSSM path to arguments
nssmArgs := append([]string{"-NoProfile", "-NonInteractive", "-Command", "& " + nssmPath}, args...)
output, err := s.executor.ExecuteWithOutput(nssmArgs...)
if err != nil {
// Log the full command and error for debugging
@@ -51,7 +43,7 @@ func (s *WindowsService) executeNSSM(ctx context.Context, args ...string) (strin
cleaned := strings.TrimSpace(strings.ReplaceAll(output, "\x00", ""))
// Remove \r\n from status strings
cleaned = strings.TrimSuffix(cleaned, "\r\n")
return cleaned, nil
}
@@ -77,25 +69,25 @@ func (s *WindowsService) CreateService(ctx context.Context, serviceName, execPat
logging.Info(" Working Directory: %s", absWorkingDir)
// First remove any existing service with the same name
s.executeNSSM(ctx, "remove", serviceName, "confirm")
s.ExecuteNSSM(ctx, "remove", serviceName, "confirm")
// Install service
if _, err := s.executeNSSM(ctx, "install", serviceName, absExecPath); err != nil {
if _, err := s.ExecuteNSSM(ctx, "install", serviceName, absExecPath); err != nil {
return fmt.Errorf("failed to install service: %v", err)
}
// Set arguments if provided
if len(args) > 0 {
cmdArgs := append([]string{"set", serviceName, "AppParameters"}, args...)
if _, err := s.executeNSSM(ctx, cmdArgs...); err != nil {
if _, err := s.ExecuteNSSM(ctx, cmdArgs...); err != nil {
// Try to clean up on failure
s.executeNSSM(ctx, "remove", serviceName, "confirm")
s.ExecuteNSSM(ctx, "remove", serviceName, "confirm")
return fmt.Errorf("failed to set arguments: %v", err)
}
}
// Verify service was created
if _, err := s.executeNSSM(ctx, "get", serviceName, "Application"); err != nil {
if _, err := s.ExecuteNSSM(ctx, "get", serviceName, "Application"); err != nil {
return fmt.Errorf("service creation verification failed: %v", err)
}
@@ -104,7 +96,7 @@ func (s *WindowsService) CreateService(ctx context.Context, serviceName, execPat
}
func (s *WindowsService) DeleteService(ctx context.Context, serviceName string) error {
if _, err := s.executeNSSM(ctx, "remove", serviceName, "confirm"); err != nil {
if _, err := s.ExecuteNSSM(ctx, "remove", serviceName, "confirm"); err != nil {
return fmt.Errorf("failed to remove service: %v", err)
}
@@ -125,15 +117,15 @@ func (s *WindowsService) UpdateService(ctx context.Context, serviceName, execPat
// Service Control Methods
func (s *WindowsService) Status(ctx context.Context, serviceName string) (string, error) {
return s.executeNSSM(ctx, "status", serviceName)
return s.ExecuteNSSM(ctx, "status", serviceName)
}
func (s *WindowsService) Start(ctx context.Context, serviceName string) (string, error) {
return s.executeNSSM(ctx, "start", serviceName)
return s.ExecuteNSSM(ctx, "start", serviceName)
}
func (s *WindowsService) Stop(ctx context.Context, serviceName string) (string, error) {
return s.executeNSSM(ctx, "stop", serviceName)
return s.ExecuteNSSM(ctx, "stop", serviceName)
}
func (s *WindowsService) Restart(ctx context.Context, serviceName string) (string, error) {
@@ -144,4 +136,4 @@ func (s *WindowsService) Restart(ctx context.Context, serviceName string) (strin
// Then start it again
return s.Start(ctx, serviceName)
}
}

View File

@@ -5,7 +5,6 @@ import (
"acc-server-manager/local/model"
"acc-server-manager/local/utl/logging"
"os"
"time"
"go.uber.org/dig"
"gorm.io/driver/sqlite"
@@ -45,10 +44,10 @@ func Migrate(db *gorm.DB) {
&model.SessionType{},
&model.StateHistory{},
&model.SteamCredentials{},
&model.SystemConfig{},
&model.Permission{},
&model.Role{},
&model.Server{},
&model.User{},
&model.Role{},
&model.Permission{},
)
if err != nil {
@@ -89,9 +88,6 @@ func Seed(db *gorm.DB) error {
if err := seedSessionTypes(db); err != nil {
return err
}
if err := seedSystemConfigs(db); err != nil {
return err
}
return nil
}
@@ -194,41 +190,3 @@ func seedSessionTypes(db *gorm.DB) error {
}
return nil
}
func seedSystemConfigs(db *gorm.DB) error {
configs := []model.SystemConfig{
{
Key: model.ConfigKeySteamCMDPath,
DefaultValue: "c:\\steamcmd\\steamcmd.exe",
Description: "Path to SteamCMD executable",
DateModified: time.Now().UTC().Format(time.RFC3339),
},
{
Key: model.ConfigKeyNSSMPath,
DefaultValue: ".\\nssm.exe",
Description: "Path to NSSM executable",
DateModified: time.Now().UTC().Format(time.RFC3339),
},
}
for _, config := range configs {
var exists bool
err := db.Model(&model.SystemConfig{}).
Select("count(*) > 0").
Where("key = ?", config.Key).
Find(&exists).
Error
if err != nil {
return err
}
if !exists {
if err := db.Create(&config).Error; err != nil {
return err
}
logging.Info("Seeded system config: %s", config.Key)
}
}
return nil
}

53
local/utl/env/env.go vendored Normal file
View File

@@ -0,0 +1,53 @@
package env
import (
"os"
"path/filepath"
)
const (
// Default paths for when environment variables are not set
DefaultSteamCMDPath = "c:\\steamcmd\\steamcmd.exe"
DefaultNSSMPath = ".\\nssm.exe"
)
// GetSteamCMDPath returns the SteamCMD executable path from environment variable or default
func GetSteamCMDPath() string {
if path := os.Getenv("STEAMCMD_PATH"); path != "" {
return path
}
return DefaultSteamCMDPath
}
// GetSteamCMDDirPath returns the directory containing SteamCMD executable
func GetSteamCMDDirPath() string {
steamCMDPath := GetSteamCMDPath()
return filepath.Dir(steamCMDPath)
}
// GetNSSMPath returns the NSSM executable path from environment variable or default
func GetNSSMPath() string {
if path := os.Getenv("NSSM_PATH"); path != "" {
return path
}
return DefaultNSSMPath
}
// ValidatePaths checks if the configured paths exist (optional validation)
func ValidatePaths() map[string]error {
errors := make(map[string]error)
// Check SteamCMD path
steamCMDPath := GetSteamCMDPath()
if _, err := os.Stat(steamCMDPath); os.IsNotExist(err) {
errors["STEAMCMD_PATH"] = err
}
// Check NSSM path
nssmPath := GetNSSMPath()
if _, err := os.Stat(nssmPath); os.IsNotExist(err) {
errors["NSSM_PATH"] = err
}
return errors
}

View File

@@ -205,7 +205,6 @@ func InitializeLogging() error {
GetWarnLogger()
GetInfoLogger()
GetDebugLogger()
GetPerformanceLogger()
// Log successful initialization
Info("Logging system initialized successfully")