Files
acc-server-manager/local/service/steam_service.go
Fran Jurmanović aab5d2ad61
All checks were successful
Release and Deploy / build (push) Successful in 6m8s
Release and Deploy / deploy (push) Successful in 27s
steam 2fa for polling and security
2025-08-16 16:43:54 +02:00

178 lines
5.0 KiB
Go

package service
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"
"acc-server-manager/local/utl/security"
"context"
"fmt"
"os"
"path/filepath"
"time"
"github.com/google/uuid"
)
const (
ACCServerAppID = "1430110"
)
type SteamService struct {
executor *command.CommandExecutor
interactiveExecutor *command.InteractiveCommandExecutor
repository *repository.SteamCredentialsRepository
tfaManager *model.Steam2FAManager
pathValidator *security.PathValidator
downloadVerifier *security.DownloadVerifier
}
func NewSteamService(repository *repository.SteamCredentialsRepository, tfaManager *model.Steam2FAManager) *SteamService {
baseExecutor := &command.CommandExecutor{
ExePath: "powershell",
LogOutput: true,
}
return &SteamService{
executor: baseExecutor,
interactiveExecutor: command.NewInteractiveCommandExecutor(baseExecutor, tfaManager),
repository: repository,
tfaManager: tfaManager,
pathValidator: security.NewPathValidator(),
downloadVerifier: security.NewDownloadVerifier(),
}
}
func (s *SteamService) GetCredentials(ctx context.Context) (*model.SteamCredentials, error) {
return s.repository.GetCurrent(ctx)
}
func (s *SteamService) SaveCredentials(ctx context.Context, creds *model.SteamCredentials) error {
if err := creds.Validate(); err != nil {
return err
}
return s.repository.Save(ctx, creds)
}
func (s *SteamService) ensureSteamCMD(_ context.Context) error {
// Get SteamCMD path from environment variable
steamCMDPath := env.GetSteamCMDPath()
steamCMDDir := filepath.Dir(steamCMDPath)
// Check if SteamCMD exists
if _, err := os.Stat(steamCMDPath); !os.IsNotExist(err) {
return nil
}
// Create directory if it doesn't exist
if err := os.MkdirAll(steamCMDDir, 0755); err != nil {
return fmt.Errorf("failed to create SteamCMD directory: %v", err)
}
// Download and install SteamCMD securely
logging.Info("Downloading SteamCMD...")
steamCMDZip := filepath.Join(steamCMDDir, "steamcmd.zip")
if err := s.downloadVerifier.VerifyAndDownload(
"https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip",
steamCMDZip,
""); err != nil {
return fmt.Errorf("failed to download SteamCMD: %v", err)
}
// Extract SteamCMD
logging.Info("Extracting SteamCMD...")
if err := s.executor.Execute("-Command",
fmt.Sprintf("Expand-Archive -Path 'steamcmd.zip' -DestinationPath '%s'", steamCMDDir)); err != nil {
return fmt.Errorf("failed to extract SteamCMD: %v", err)
}
// Clean up zip file
os.Remove("steamcmd.zip")
return nil
}
func (s *SteamService) InstallServer(ctx context.Context, installPath string, serverID *uuid.UUID) error {
if err := s.ensureSteamCMD(ctx); err != nil {
return err
}
// Validate installation path for security
if err := s.pathValidator.ValidateInstallPath(installPath); err != nil {
return fmt.Errorf("invalid installation path: %v", err)
}
// Convert to absolute path and ensure proper Windows path format
absPath, err := filepath.Abs(installPath)
if err != nil {
return fmt.Errorf("failed to get absolute path: %v", err)
}
absPath = filepath.Clean(absPath)
// Ensure install path exists
if err := os.MkdirAll(absPath, 0755); err != nil {
return fmt.Errorf("failed to create install directory: %v", err)
}
// Get Steam credentials
creds, err := s.GetCredentials(ctx)
if err != nil {
return fmt.Errorf("failed to get Steam credentials: %v", err)
}
// Get SteamCMD path from environment variable
steamCMDPath := env.GetSteamCMDPath()
// Build SteamCMD command
args := []string{
"-nologo",
"-noprofile",
steamCMDPath,
"+force_install_dir", absPath,
"+login",
}
if creds != nil && creds.Username != "" {
args = append(args, creds.Username)
if creds.Password != "" {
args = append(args, creds.Password)
}
} else {
args = append(args, "anonymous")
}
args = append(args,
"+app_update", ACCServerAppID,
"validate",
"+quit",
)
// Use interactive executor to handle potential 2FA prompts
logging.Info("Installing ACC server to %s...", absPath)
if err := s.interactiveExecutor.ExecuteInteractive(ctx, serverID, args...); err != nil {
return fmt.Errorf("failed to run SteamCMD: %v", err)
}
// Add a delay to allow Steam to properly cleanup
logging.Info("Waiting for Steam operations to complete...")
time.Sleep(5 * time.Second)
// Verify installation
exePath := filepath.Join(absPath, "server", "accServer.exe")
if _, err := os.Stat(exePath); os.IsNotExist(err) {
return fmt.Errorf("server installation failed: accServer.exe not found in %s", absPath)
}
logging.Info("Server installation completed successfully")
return nil
}
func (s *SteamService) UpdateServer(ctx context.Context, installPath string, serverID *uuid.UUID) error {
return s.InstallServer(ctx, installPath, serverID) // Same process as install
}
func (s *SteamService) UninstallServer(installPath string) error {
return os.RemoveAll(installPath)
}