remove system config
This commit is contained in:
23
README.md
23
README.md
@@ -19,7 +19,14 @@ A comprehensive web-based management system for Assetto Corsa Competizione (ACC)
|
|||||||
go build -o api.exe cmd/api/main.go
|
go build -o api.exe cmd/api/main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Generate Configuration**
|
2. **Set Environment Variables**
|
||||||
|
```powershell
|
||||||
|
# Set tool paths (optional - defaults will be used if not set)
|
||||||
|
$env:STEAMCMD_PATH = "C:\steamcmd\steamcmd.exe"
|
||||||
|
$env:NSSM_PATH = ".\nssm.exe"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Generate Configuration**
|
||||||
```powershell
|
```powershell
|
||||||
# Windows PowerShell
|
# Windows PowerShell
|
||||||
.\scripts\generate-secrets.ps1
|
.\scripts\generate-secrets.ps1
|
||||||
@@ -28,7 +35,7 @@ A comprehensive web-based management system for Assetto Corsa Competizione (ACC)
|
|||||||
copy .env.example .env
|
copy .env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Run Application**
|
4. **Run Application**
|
||||||
```bash
|
```bash
|
||||||
./api.exe
|
./api.exe
|
||||||
```
|
```
|
||||||
@@ -44,6 +51,18 @@ A comprehensive web-based management system for Assetto Corsa Competizione (ACC)
|
|||||||
- **Configuration Management** - Web-based configuration editor
|
- **Configuration Management** - Web-based configuration editor
|
||||||
- **Service Integration** - Windows Service management
|
- **Service Integration** - Windows Service management
|
||||||
|
|
||||||
|
## 🔧 Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
The application uses environment variables for tool configuration:
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `STEAMCMD_PATH` | Path to SteamCMD executable | `c:\steamcmd\steamcmd.exe` |
|
||||||
|
| `NSSM_PATH` | Path to NSSM executable | `.\nssm.exe` |
|
||||||
|
|
||||||
|
For detailed configuration information, see [Environment Variables Documentation](documentation/ENVIRONMENT_VARIABLES.md).
|
||||||
|
|
||||||
## 🏗️ Architecture
|
## 🏗️ Architecture
|
||||||
|
|
||||||
- **Backend**: Go + Fiber web framework
|
- **Backend**: Go + Fiber web framework
|
||||||
|
|||||||
147
documentation/ENVIRONMENT_VARIABLES.md
Normal file
147
documentation/ENVIRONMENT_VARIABLES.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# Environment Variables Configuration
|
||||||
|
|
||||||
|
This document describes the environment variables used by the ACC Server Manager to replace the previous database-based system configuration.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `system_configs` database table has been completely removed and replaced with environment variables for better configuration management and deployment flexibility.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### STEAMCMD_PATH
|
||||||
|
**Description:** Path to the SteamCMD executable
|
||||||
|
**Default:** `c:\steamcmd\steamcmd.exe`
|
||||||
|
**Example:** `STEAMCMD_PATH=D:\tools\steamcmd\steamcmd.exe`
|
||||||
|
|
||||||
|
This path is used for:
|
||||||
|
- Installing ACC dedicated servers
|
||||||
|
- Updating server files
|
||||||
|
- Managing Steam-based server installations
|
||||||
|
|
||||||
|
### NSSM_PATH
|
||||||
|
**Description:** Path to the NSSM (Non-Sucking Service Manager) executable
|
||||||
|
**Default:** `.\nssm.exe`
|
||||||
|
**Example:** `NSSM_PATH=C:\tools\nssm\win64\nssm.exe`
|
||||||
|
|
||||||
|
This path is used for:
|
||||||
|
- Creating Windows services for ACC servers
|
||||||
|
- Managing service lifecycle (start, stop, restart)
|
||||||
|
- Service configuration and management
|
||||||
|
|
||||||
|
## Setting Environment Variables
|
||||||
|
|
||||||
|
### Windows Command Prompt
|
||||||
|
```cmd
|
||||||
|
set STEAMCMD_PATH=D:\tools\steamcmd\steamcmd.exe
|
||||||
|
set NSSM_PATH=C:\tools\nssm\win64\nssm.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows PowerShell
|
||||||
|
```powershell
|
||||||
|
$env:STEAMCMD_PATH = "D:\tools\steamcmd\steamcmd.exe"
|
||||||
|
$env:NSSM_PATH = "C:\tools\nssm\win64\nssm.exe"
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Environment Variables (Persistent)
|
||||||
|
1. Open System Properties → Advanced → Environment Variables
|
||||||
|
2. Add new system variables:
|
||||||
|
- Variable name: `STEAMCMD_PATH`
|
||||||
|
- Variable value: `D:\tools\steamcmd\steamcmd.exe`
|
||||||
|
3. Repeat for `NSSM_PATH`
|
||||||
|
|
||||||
|
### Docker Environment
|
||||||
|
```dockerfile
|
||||||
|
ENV STEAMCMD_PATH=/opt/steamcmd/steamcmd.sh
|
||||||
|
ENV NSSM_PATH=/usr/local/bin/nssm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- STEAMCMD_PATH=/opt/steamcmd/steamcmd.sh
|
||||||
|
- NSSM_PATH=/usr/local/bin/nssm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration from system_configs
|
||||||
|
|
||||||
|
### Automatic Migration
|
||||||
|
A migration script (`003_remove_system_configs.sql`) will automatically:
|
||||||
|
1. Remove the `system_configs` table
|
||||||
|
2. Clean up related database references
|
||||||
|
3. Record the migration in `migration_records`
|
||||||
|
|
||||||
|
### Manual Configuration Required
|
||||||
|
After upgrading, you must set the environment variables based on your previous system configuration:
|
||||||
|
|
||||||
|
1. Check your previous configuration (if you had custom paths):
|
||||||
|
```sql
|
||||||
|
SELECT key, value, default_value FROM system_configs;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Set environment variables accordingly:
|
||||||
|
- If you used custom `steamcmd_path`: Set `STEAMCMD_PATH`
|
||||||
|
- If you used custom `nssm_path`: Set `NSSM_PATH`
|
||||||
|
|
||||||
|
3. Restart the ACC Server Manager service
|
||||||
|
|
||||||
|
### Validation
|
||||||
|
The application will use default values if environment variables are not set. To validate your configuration:
|
||||||
|
|
||||||
|
1. Check the application logs on startup
|
||||||
|
2. The `env.ValidatePaths()` function can be used to verify paths exist
|
||||||
|
3. Monitor for any "failed to get path" errors in logs
|
||||||
|
|
||||||
|
## Benefits of Environment Variables
|
||||||
|
|
||||||
|
### Deployment Flexibility
|
||||||
|
- Different environments can have different tool paths
|
||||||
|
- No database dependency for basic configuration
|
||||||
|
- Container-friendly configuration
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Sensitive paths not stored in database
|
||||||
|
- Environment-specific configuration
|
||||||
|
- Better separation of configuration from data
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- No database queries for basic path lookups
|
||||||
|
- Reduced database load on every operation
|
||||||
|
- Faster service startup
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**Issue:** SteamCMD operations fail
|
||||||
|
**Solution:** Verify `STEAMCMD_PATH` points to valid steamcmd.exe
|
||||||
|
|
||||||
|
**Issue:** Service creation fails
|
||||||
|
**Solution:** Verify `NSSM_PATH` points to valid nssm.exe
|
||||||
|
|
||||||
|
**Issue:** Using default paths
|
||||||
|
**Solution:** Set environment variables and restart application
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
Enable debug logging to see which paths are being used:
|
||||||
|
```
|
||||||
|
2024-01-01 12:00:00 DEBUG Using SteamCMD path: D:\tools\steamcmd\steamcmd.exe
|
||||||
|
2024-01-01 12:00:00 DEBUG Using NSSM path: C:\tools\nssm\win64\nssm.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Changes Summary
|
||||||
|
|
||||||
|
### Removed Components
|
||||||
|
- `local/model/config.go` - SystemConfig struct and related constants
|
||||||
|
- `local/service/system_config_service.go` - SystemConfigService
|
||||||
|
- `local/repository/system_config_repository.go` - SystemConfigRepository
|
||||||
|
- Database table: `system_configs`
|
||||||
|
|
||||||
|
### Added Components
|
||||||
|
- `local/utl/env/env.go` - Environment variable utilities
|
||||||
|
- Migration script: `003_remove_system_configs.sql`
|
||||||
|
|
||||||
|
### Modified Services
|
||||||
|
- **SteamService**: Now uses `env.GetSteamCMDPath()`
|
||||||
|
- **WindowsService**: Now uses `env.GetNSSMPath()`
|
||||||
|
- **ServerService**: Removed SystemConfigService dependency
|
||||||
|
- **ApiService**: Removed SystemConfigService dependency
|
||||||
@@ -14,6 +14,15 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"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.
|
// AuthMiddleware provides authentication and permission middleware.
|
||||||
type AuthMiddleware struct {
|
type AuthMiddleware struct {
|
||||||
membershipService *service.MembershipService
|
membershipService *service.MembershipService
|
||||||
@@ -23,11 +32,16 @@ type AuthMiddleware struct {
|
|||||||
|
|
||||||
// NewAuthMiddleware creates a new AuthMiddleware.
|
// NewAuthMiddleware creates a new AuthMiddleware.
|
||||||
func NewAuthMiddleware(ms *service.MembershipService, cache *cache.InMemoryCache) *AuthMiddleware {
|
func NewAuthMiddleware(ms *service.MembershipService, cache *cache.InMemoryCache) *AuthMiddleware {
|
||||||
return &AuthMiddleware{
|
auth := &AuthMiddleware{
|
||||||
membershipService: ms,
|
membershipService: ms,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
securityMW: security.NewSecurityMiddleware(),
|
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.
|
// 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("userID", claims.UserID)
|
||||||
|
ctx.Locals("userInfo", userInfo)
|
||||||
ctx.Locals("authTime", time.Now())
|
ctx.Locals("authTime", time.Now())
|
||||||
|
|
||||||
logging.InfoWithContext("AUTH", "User %s authenticated successfully from IP %s", claims.UserID, ip)
|
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
|
// Use cached user info from authentication step - no database queries needed
|
||||||
has, err := m.hasPermissionCached(ctx.UserContext(), userID, requiredPermission)
|
userInfo, ok := ctx.Locals("userInfo").(*CachedUserInfo)
|
||||||
if err != nil {
|
if !ok {
|
||||||
logging.ErrorWithContext("AUTH", "Permission check error for user %s, permission %s: %v", userID, requiredPermission, err)
|
logging.Error("Permission check failed: no cached user info in context from IP %s", ctx.IP())
|
||||||
return ctx.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
"error": "Forbidden",
|
"error": "Unauthorized",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if user has permission using cached data
|
||||||
|
has := m.hasPermissionFromCache(userInfo, requiredPermission)
|
||||||
|
|
||||||
if !has {
|
if !has {
|
||||||
logging.WarnWithContext("AUTH", "Permission denied: user %s lacks permission %s, IP %s", userID, requiredPermission, ctx.IP())
|
logging.WarnWithContext("AUTH", "Permission denied: user %s lacks permission %s, IP %s", userID, requiredPermission, ctx.IP())
|
||||||
return ctx.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
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
|
// getCachedUserInfo retrieves and caches complete user information including permissions
|
||||||
func (m *AuthMiddleware) hasPermissionCached(ctx context.Context, userID, permission string) (bool, error) {
|
func (m *AuthMiddleware) getCachedUserInfo(ctx context.Context, userID string) (*CachedUserInfo, error) {
|
||||||
cacheKey := fmt.Sprintf("permission:%s:%s", userID, permission)
|
cacheKey := fmt.Sprintf("userinfo:%s", userID)
|
||||||
|
|
||||||
// Try cache first
|
// Try cache first
|
||||||
if cached, found := m.cache.Get(cacheKey); found {
|
if cached, found := m.cache.Get(cacheKey); found {
|
||||||
if hasPermission, ok := cached.(bool); ok {
|
if userInfo, ok := cached.(*CachedUserInfo); ok {
|
||||||
logging.DebugWithContext("AUTH_CACHE", "Permission %s:%s found in cache: %v", userID, permission, hasPermission)
|
logging.DebugWithContext("AUTH_CACHE", "User info for %s found in cache", userID)
|
||||||
return hasPermission, nil
|
return userInfo, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache miss - check with service
|
// Cache miss - load from database
|
||||||
has, err := m.membershipService.HasPermission(ctx, userID, permission)
|
user, err := m.membershipService.GetUserWithPermissions(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the result for 10 minutes
|
// Build permission map for fast lookups
|
||||||
m.cache.Set(cacheKey, has, 10*time.Minute)
|
permissions := make(map[string]bool)
|
||||||
logging.DebugWithContext("AUTH_CACHE", "Permission %s:%s cached: %v", userID, permission, has)
|
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) {
|
func (m *AuthMiddleware) InvalidateUserPermissions(userID string) {
|
||||||
// This is a simple implementation - in a production system you might want
|
cacheKey := fmt.Sprintf("userinfo:%s", userID)
|
||||||
// to track permission keys per user for more efficient invalidation
|
m.cache.Delete(cacheKey)
|
||||||
logging.InfoWithContext("AUTH_CACHE", "Permission cache invalidated for user %s", userID)
|
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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package model
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -122,33 +121,7 @@ type Configuration struct {
|
|||||||
ConfigVersion IntString `json:"configVersion"`
|
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
|
// 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 {
|
func (i *IntBool) UnmarshalJSON(b []byte) error {
|
||||||
var str int
|
var str int
|
||||||
@@ -209,35 +182,3 @@ func (i IntString) ToString() string {
|
|||||||
func (i IntString) ToInt() int {
|
func (i IntString) ToInt() int {
|
||||||
return int(i)
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,6 +16,5 @@ func InitializeRepositories(c *dig.Container) {
|
|||||||
c.Provide(NewConfigRepository)
|
c.Provide(NewConfigRepository)
|
||||||
c.Provide(NewLookupRepository)
|
c.Provide(NewLookupRepository)
|
||||||
c.Provide(NewSteamCredentialsRepository)
|
c.Provide(NewSteamCredentialsRepository)
|
||||||
c.Provide(NewSystemConfigRepository)
|
|
||||||
c.Provide(NewMembershipRepository)
|
c.Provide(NewMembershipRepository)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -19,8 +19,7 @@ type ApiService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewApiService(repository *repository.ApiRepository,
|
func NewApiService(repository *repository.ApiRepository,
|
||||||
serverRepository *repository.ServerRepository,
|
serverRepository *repository.ServerRepository) *ApiService {
|
||||||
systemConfigService *SystemConfigService) *ApiService {
|
|
||||||
return &ApiService{
|
return &ApiService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
serverRepository: serverRepository,
|
serverRepository: serverRepository,
|
||||||
@@ -29,7 +28,7 @@ func NewApiService(repository *repository.ApiRepository,
|
|||||||
ThrottleTime: 5 * time.Second, // Minimum 5 seconds between checks
|
ThrottleTime: 5 * time.Second, // Minimum 5 seconds between checks
|
||||||
DefaultStatus: model.StatusRunning, // Default to running if throttled
|
DefaultStatus: model.StatusRunning, // Default to running if throttled
|
||||||
}),
|
}),
|
||||||
windowsService: NewWindowsService(systemConfigService),
|
windowsService: NewWindowsService(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,18 +12,31 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CacheInvalidator interface for cache invalidation
|
||||||
|
type CacheInvalidator interface {
|
||||||
|
InvalidateUserPermissions(userID string)
|
||||||
|
InvalidateAllUserPermissions()
|
||||||
|
}
|
||||||
|
|
||||||
// MembershipService provides business logic for membership-related operations.
|
// MembershipService provides business logic for membership-related operations.
|
||||||
type MembershipService struct {
|
type MembershipService struct {
|
||||||
repo *repository.MembershipRepository
|
repo *repository.MembershipRepository
|
||||||
|
cacheInvalidator CacheInvalidator
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMembershipService creates a new MembershipService.
|
// NewMembershipService creates a new MembershipService.
|
||||||
func NewMembershipService(repo *repository.MembershipRepository) *MembershipService {
|
func NewMembershipService(repo *repository.MembershipRepository) *MembershipService {
|
||||||
return &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.
|
// Login authenticates a user and returns a JWT.
|
||||||
func (s *MembershipService) Login(ctx context.Context, username, password string) (string, error) {
|
func (s *MembershipService) Login(ctx context.Context, username, password string) (string, error) {
|
||||||
user, err := s.repo.FindUserByUsername(ctx, username)
|
user, err := s.repo.FindUserByUsername(ctx, username)
|
||||||
@@ -109,6 +122,11 @@ func (s *MembershipService) DeleteUser(ctx context.Context, userID uuid.UUID) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invalidate cache for deleted user
|
||||||
|
if s.cacheInvalidator != nil {
|
||||||
|
s.cacheInvalidator.InvalidateUserPermissions(userID.String())
|
||||||
|
}
|
||||||
|
|
||||||
logging.InfoOperation("USER_DELETE", "Deleted user: "+userID.String())
|
logging.InfoOperation("USER_DELETE", "Deleted user: "+userID.String())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -142,6 +160,11 @@ func (s *MembershipService) UpdateUser(ctx context.Context, userID uuid.UUID, re
|
|||||||
return nil, err
|
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()+")")
|
logging.InfoOperation("USER_UPDATE", "Updated user: "+user.Username+" (ID: "+user.ID.String()+")")
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
@@ -241,6 +264,11 @@ func (s *MembershipService) SetupInitialData(ctx context.Context) error {
|
|||||||
return err
|
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
|
// Create a default admin user if one doesn't exist
|
||||||
_, err = s.repo.FindUserByUsername(ctx, "admin")
|
_, err = s.repo.FindUserByUsername(ctx, "admin")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"acc-server-manager/local/model"
|
"acc-server-manager/local/model"
|
||||||
"acc-server-manager/local/repository"
|
"acc-server-manager/local/repository"
|
||||||
|
"acc-server-manager/local/utl/env"
|
||||||
"acc-server-manager/local/utl/logging"
|
"acc-server-manager/local/utl/logging"
|
||||||
"acc-server-manager/local/utl/tracking"
|
"acc-server-manager/local/utl/tracking"
|
||||||
"context"
|
"context"
|
||||||
@@ -30,7 +31,6 @@ type ServerService struct {
|
|||||||
steamService *SteamService
|
steamService *SteamService
|
||||||
windowsService *WindowsService
|
windowsService *WindowsService
|
||||||
firewallService *FirewallService
|
firewallService *FirewallService
|
||||||
systemConfigService *SystemConfigService
|
|
||||||
instances sync.Map // Track instances per server
|
instances sync.Map // Track instances per server
|
||||||
lastInsertTimes sync.Map // Track last insert time per server
|
lastInsertTimes sync.Map // Track last insert time per server
|
||||||
debouncers sync.Map // Track debounce timers per server
|
debouncers sync.Map // Track debounce timers per server
|
||||||
@@ -68,7 +68,6 @@ func NewServerService(
|
|||||||
steamService *SteamService,
|
steamService *SteamService,
|
||||||
windowsService *WindowsService,
|
windowsService *WindowsService,
|
||||||
firewallService *FirewallService,
|
firewallService *FirewallService,
|
||||||
systemConfigService *SystemConfigService,
|
|
||||||
) *ServerService {
|
) *ServerService {
|
||||||
service := &ServerService{
|
service := &ServerService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
@@ -78,7 +77,6 @@ func NewServerService(
|
|||||||
steamService: steamService,
|
steamService: steamService,
|
||||||
windowsService: windowsService,
|
windowsService: windowsService,
|
||||||
firewallService: firewallService,
|
firewallService: firewallService,
|
||||||
systemConfigService: systemConfigService,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize server instances
|
// Initialize server instances
|
||||||
@@ -203,13 +201,8 @@ func (s *ServerService) updateSessionDuration(server *model.Server, sessionType
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GenerateServerPath(server *model.Server) {
|
func (s *ServerService) GenerateServerPath(server *model.Server) {
|
||||||
// Get the base steamcmd path
|
// Get the base steamcmd path from environment variable
|
||||||
steamCMDPath, err := s.systemConfigService.GetSteamCMDDirPath(context.Background())
|
steamCMDPath := env.GetSteamCMDDirPath()
|
||||||
if err != nil {
|
|
||||||
logging.Error("Failed to get steamcmd path: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
server.Path = server.GenerateServerPath(steamCMDPath)
|
server.Path = server.GenerateServerPath(steamCMDPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,13 @@ func InitializeServices(c *dig.Container) {
|
|||||||
c.Provide(NewApiService)
|
c.Provide(NewApiService)
|
||||||
c.Provide(NewConfigService)
|
c.Provide(NewConfigService)
|
||||||
c.Provide(NewLookupService)
|
c.Provide(NewLookupService)
|
||||||
c.Provide(NewSystemConfigService)
|
|
||||||
c.Provide(NewSteamService)
|
c.Provide(NewSteamService)
|
||||||
c.Provide(NewWindowsService)
|
c.Provide(NewWindowsService)
|
||||||
c.Provide(NewFirewallService)
|
c.Provide(NewFirewallService)
|
||||||
c.Provide(NewMembershipService)
|
c.Provide(NewMembershipService)
|
||||||
|
|
||||||
logging.Debug("Initializing service dependencies")
|
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")
|
logging.Debug("Setting up service cross-references")
|
||||||
api.SetServerService(server)
|
api.SetServerService(server)
|
||||||
config.SetServerService(server)
|
config.SetServerService(server)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"acc-server-manager/local/model"
|
"acc-server-manager/local/model"
|
||||||
"acc-server-manager/local/repository"
|
"acc-server-manager/local/repository"
|
||||||
"acc-server-manager/local/utl/command"
|
"acc-server-manager/local/utl/command"
|
||||||
|
"acc-server-manager/local/utl/env"
|
||||||
"acc-server-manager/local/utl/logging"
|
"acc-server-manager/local/utl/logging"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -18,17 +19,15 @@ const (
|
|||||||
type SteamService struct {
|
type SteamService struct {
|
||||||
executor *command.CommandExecutor
|
executor *command.CommandExecutor
|
||||||
repository *repository.SteamCredentialsRepository
|
repository *repository.SteamCredentialsRepository
|
||||||
configService *SystemConfigService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSteamService(repository *repository.SteamCredentialsRepository, configService *SystemConfigService) *SteamService {
|
func NewSteamService(repository *repository.SteamCredentialsRepository) *SteamService {
|
||||||
return &SteamService{
|
return &SteamService{
|
||||||
executor: &command.CommandExecutor{
|
executor: &command.CommandExecutor{
|
||||||
ExePath: "powershell",
|
ExePath: "powershell",
|
||||||
LogOutput: true,
|
LogOutput: true,
|
||||||
},
|
},
|
||||||
repository: repository,
|
repository: repository,
|
||||||
configService: configService,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,12 +43,8 @@ func (s *SteamService) SaveCredentials(ctx context.Context, creds *model.SteamCr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SteamService) ensureSteamCMD(ctx context.Context) error {
|
func (s *SteamService) ensureSteamCMD(ctx context.Context) error {
|
||||||
// Get SteamCMD path from config
|
// Get SteamCMD path from environment variable
|
||||||
steamCMDPath, err := s.configService.GetSteamCMDDirPath(ctx)
|
steamCMDPath := env.GetSteamCMDPath()
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get SteamCMD path from config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
steamCMDDir := filepath.Dir(steamCMDPath)
|
steamCMDDir := filepath.Dir(steamCMDPath)
|
||||||
|
|
||||||
// Check if SteamCMD exists
|
// 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)
|
return fmt.Errorf("failed to get Steam credentials: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get SteamCMD path from config
|
// Get SteamCMD path from environment variable
|
||||||
steamCMDPath, err := s.configService.GetSteamCMDPath(ctx)
|
steamCMDPath := env.GetSteamCMDPath()
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get SteamCMD path from config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build SteamCMD command
|
// Build SteamCMD command
|
||||||
args := []string{
|
args := []string{
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"acc-server-manager/local/utl/command"
|
"acc-server-manager/local/utl/command"
|
||||||
|
"acc-server-manager/local/utl/env"
|
||||||
"acc-server-manager/local/utl/logging"
|
"acc-server-manager/local/utl/logging"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -9,32 +10,23 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
NSSMPath = ".\\nssm.exe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WindowsService struct {
|
type WindowsService struct {
|
||||||
executor *command.CommandExecutor
|
executor *command.CommandExecutor
|
||||||
configService *SystemConfigService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWindowsService(configService *SystemConfigService) *WindowsService {
|
func NewWindowsService() *WindowsService {
|
||||||
return &WindowsService{
|
return &WindowsService{
|
||||||
executor: &command.CommandExecutor{
|
executor: &command.CommandExecutor{
|
||||||
ExePath: "powershell",
|
ExePath: "powershell",
|
||||||
LogOutput: true,
|
LogOutput: true,
|
||||||
},
|
},
|
||||||
configService: configService,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeNSSM runs an NSSM command through PowerShell with elevation
|
// executeNSSM runs an NSSM command through PowerShell with elevation
|
||||||
func (s *WindowsService) executeNSSM(ctx context.Context, args ...string) (string, error) {
|
func (s *WindowsService) ExecuteNSSM(ctx context.Context, args ...string) (string, error) {
|
||||||
// Get NSSM path from config
|
// Get NSSM path from environment variable
|
||||||
nssmPath, err := s.configService.GetNSSMPath(ctx)
|
nssmPath := env.GetNSSMPath()
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to get NSSM path from config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepend NSSM path to arguments
|
// Prepend NSSM path to arguments
|
||||||
nssmArgs := append([]string{"-NoProfile", "-NonInteractive", "-Command", "& " + nssmPath}, args...)
|
nssmArgs := append([]string{"-NoProfile", "-NonInteractive", "-Command", "& " + nssmPath}, args...)
|
||||||
@@ -77,25 +69,25 @@ func (s *WindowsService) CreateService(ctx context.Context, serviceName, execPat
|
|||||||
logging.Info(" Working Directory: %s", absWorkingDir)
|
logging.Info(" Working Directory: %s", absWorkingDir)
|
||||||
|
|
||||||
// First remove any existing service with the same name
|
// First remove any existing service with the same name
|
||||||
s.executeNSSM(ctx, "remove", serviceName, "confirm")
|
s.ExecuteNSSM(ctx, "remove", serviceName, "confirm")
|
||||||
|
|
||||||
// Install service
|
// 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)
|
return fmt.Errorf("failed to install service: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set arguments if provided
|
// Set arguments if provided
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
cmdArgs := append([]string{"set", serviceName, "AppParameters"}, args...)
|
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
|
// 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)
|
return fmt.Errorf("failed to set arguments: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify service was created
|
// 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)
|
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 {
|
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)
|
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
|
// Service Control Methods
|
||||||
|
|
||||||
func (s *WindowsService) Status(ctx context.Context, serviceName string) (string, error) {
|
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) {
|
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) {
|
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) {
|
func (s *WindowsService) Restart(ctx context.Context, serviceName string) (string, error) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"acc-server-manager/local/model"
|
"acc-server-manager/local/model"
|
||||||
"acc-server-manager/local/utl/logging"
|
"acc-server-manager/local/utl/logging"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.uber.org/dig"
|
"go.uber.org/dig"
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
@@ -45,10 +44,10 @@ func Migrate(db *gorm.DB) {
|
|||||||
&model.SessionType{},
|
&model.SessionType{},
|
||||||
&model.StateHistory{},
|
&model.StateHistory{},
|
||||||
&model.SteamCredentials{},
|
&model.SteamCredentials{},
|
||||||
&model.SystemConfig{},
|
&model.Server{},
|
||||||
&model.Permission{},
|
|
||||||
&model.Role{},
|
|
||||||
&model.User{},
|
&model.User{},
|
||||||
|
&model.Role{},
|
||||||
|
&model.Permission{},
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -89,9 +88,6 @@ func Seed(db *gorm.DB) error {
|
|||||||
if err := seedSessionTypes(db); err != nil {
|
if err := seedSessionTypes(db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := seedSystemConfigs(db); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,41 +190,3 @@ func seedSessionTypes(db *gorm.DB) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
53
local/utl/env/env.go
vendored
Normal 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
|
||||||
|
}
|
||||||
@@ -205,7 +205,6 @@ func InitializeLogging() error {
|
|||||||
GetWarnLogger()
|
GetWarnLogger()
|
||||||
GetInfoLogger()
|
GetInfoLogger()
|
||||||
GetDebugLogger()
|
GetDebugLogger()
|
||||||
GetPerformanceLogger()
|
|
||||||
|
|
||||||
// Log successful initialization
|
// Log successful initialization
|
||||||
Info("Logging system initialized successfully")
|
Info("Logging system initialized successfully")
|
||||||
|
|||||||
Reference in New Issue
Block a user