Files
corrosion-admin-panel/companion-agent/internal/process/steamcmd.go
Vantz Stockwell a62715409f feat: Add Go companion agent for bare metal server management
Implements complete companion agent for Rust servers not on managed panels.

Features:
- NATS integration with token auth and auto-reconnect
- Game server process management (start/stop/restart/monitor)
- File operations (read/write/delete/list) via NATS
- SteamCMD integration for automated updates
- Self-update capability with download and replace
- Heartbeat publishing every 60s with server status
- Graceful shutdown handling (SIGTERM/SIGINT)
- Zombie process prevention via cmd.Wait()
- Cross-platform builds (Linux amd64, Windows amd64)

Structure:
- cmd/agent/main.go: Entry point, config, signal handling
- internal/app/daemon.go: Main loop, NATS subscriptions
- internal/client/nats.go: NATS connection with reconnect
- internal/process/gameserver.go: Process management
- internal/process/steamcmd.go: Steam update execution
- internal/files/operations.go: File system operations
- internal/update/updater.go: Self-update logic
- Makefile: Cross-compilation targets
- README.md: Installation and configuration guide

NATS Subjects:
- Publishes: corrosion.{license_id}.companion.heartbeat
- Publishes: corrosion.{license_id}.files.response
- Subscribes: corrosion.{license_id}.cmd.server
- Subscribes: corrosion.{license_id}.files.{get|put|delete|list}
- Subscribes: corrosion.{license_id}.update.steam
- Subscribes: corrosion.{license_id}.update.companion

Built binaries: 7.0MB (Linux), 7.2MB (Windows)
Total code: 1,356 LOC across 8 files

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 12:05:23 -05:00

139 lines
3.3 KiB
Go

package process
import (
"fmt"
"log"
"os"
"os/exec"
"time"
)
const (
rustAppID = "258550" // Rust Dedicated Server App ID
)
// SteamCMD handles SteamCMD operations for game server updates
type SteamCMD struct {
path string
}
// NewSteamCMD creates a new SteamCMD instance
func NewSteamCMD(path string) *SteamCMD {
return &SteamCMD{
path: path,
}
}
// UpdateRustServer updates the Rust Dedicated Server via SteamCMD
func (sc *SteamCMD) UpdateRustServer(validate bool) error {
log.Printf("Starting SteamCMD update for Rust Server (validate=%v)", validate)
// Check if SteamCMD exists
if _, err := os.Stat(sc.path); os.IsNotExist(err) {
return fmt.Errorf("steamcmd not found at: %s", sc.path)
}
startTime := time.Now()
// Build SteamCMD command
// +login anonymous +force_install_dir /path/to/rust +app_update 258550 validate +quit
args := []string{
"+login", "anonymous",
"+force_install_dir", getServerInstallDir(),
"+app_update", rustAppID,
}
if validate {
args = append(args, "validate")
}
args = append(args, "+quit")
log.Printf("Executing: %s %v", sc.path, args)
// Create command
cmd := exec.Command(sc.path, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Run SteamCMD (this will block until update completes)
if err := cmd.Run(); err != nil {
return fmt.Errorf("steamcmd update failed: %w", err)
}
duration := time.Since(startTime)
log.Printf("SteamCMD update completed in %v", duration)
return nil
}
// UpdateRustServerWithPath updates the Rust server to a specific install directory
func (sc *SteamCMD) UpdateRustServerWithPath(installPath string, validate bool) error {
log.Printf("Starting SteamCMD update for Rust Server at %s (validate=%v)", installPath, validate)
// Check if SteamCMD exists
if _, err := os.Stat(sc.path); os.IsNotExist(err) {
return fmt.Errorf("steamcmd not found at: %s", sc.path)
}
startTime := time.Now()
// Build SteamCMD command
args := []string{
"+login", "anonymous",
"+force_install_dir", installPath,
"+app_update", rustAppID,
}
if validate {
args = append(args, "validate")
}
args = append(args, "+quit")
log.Printf("Executing: %s %v", sc.path, args)
// Create command
cmd := exec.Command(sc.path, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Run SteamCMD
if err := cmd.Run(); err != nil {
return fmt.Errorf("steamcmd update failed: %w", err)
}
duration := time.Since(startTime)
log.Printf("SteamCMD update completed in %v", duration)
return nil
}
// CheckSteamCMDInstalled verifies SteamCMD is installed and executable
func (sc *SteamCMD) CheckSteamCMDInstalled() error {
if _, err := os.Stat(sc.path); os.IsNotExist(err) {
return fmt.Errorf("steamcmd not found at: %s", sc.path)
}
// Try to execute with --help or similar to verify it's executable
cmd := exec.Command(sc.path, "+quit")
if err := cmd.Run(); err != nil {
return fmt.Errorf("steamcmd is not executable or working: %w", err)
}
return nil
}
// getServerInstallDir returns the default server installation directory
// This should ideally come from configuration, but we provide a fallback
func getServerInstallDir() string {
// Try to determine from GAME_SERVER_PATH environment variable
serverPath := os.Getenv("GAME_SERVER_PATH")
if serverPath != "" {
return getDirectory(serverPath)
}
// Default fallback paths by OS
return "/home/rustserver/server"
}