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>
This commit is contained in:
92
companion-agent/cmd/agent/main.go
Normal file
92
companion-agent/cmd/agent/main.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
"github.com/vigilcyber/corrosion-companion/internal/app"
|
||||
"github.com/vigilcyber/corrosion-companion/internal/client"
|
||||
)
|
||||
|
||||
// Config holds all environment-based configuration
|
||||
type Config struct {
|
||||
// NATS connection
|
||||
NATSUrl string `envconfig:"NATS_URL" required:"true"`
|
||||
NATSToken string `envconfig:"NATS_TOKEN" required:"true"`
|
||||
|
||||
// License identification
|
||||
LicenseID string `envconfig:"LICENSE_ID" required:"true"`
|
||||
|
||||
// Game server configuration
|
||||
SteamCMDPath string `envconfig:"STEAMCMD_PATH" default:"/usr/games/steamcmd"`
|
||||
GameServerPath string `envconfig:"GAME_SERVER_PATH" required:"true"`
|
||||
GameServerArgs string `envconfig:"GAME_SERVER_ARGS" default:"-batchmode"`
|
||||
|
||||
// Optional settings
|
||||
HeartbeatInterval int `envconfig:"HEARTBEAT_INTERVAL" default:"60"`
|
||||
LogLevel string `envconfig:"LOG_LEVEL" default:"info"`
|
||||
}
|
||||
|
||||
const version = "1.0.0"
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
log.Printf("Corrosion Companion Agent v%s starting...", version)
|
||||
|
||||
// Load configuration from environment
|
||||
var cfg Config
|
||||
if err := envconfig.Process("", &cfg); err != nil {
|
||||
log.Fatalf("Failed to load configuration: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Configuration loaded:")
|
||||
log.Printf(" NATS URL: %s", cfg.NATSUrl)
|
||||
log.Printf(" License ID: %s", cfg.LicenseID)
|
||||
log.Printf(" Game Server Path: %s", cfg.GameServerPath)
|
||||
log.Printf(" SteamCMD Path: %s", cfg.SteamCMDPath)
|
||||
log.Printf(" Heartbeat Interval: %ds", cfg.HeartbeatInterval)
|
||||
|
||||
// Create context with signal handling for graceful shutdown
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
// Connect to NATS
|
||||
log.Println("Connecting to NATS...")
|
||||
nc, err := client.Connect(cfg.NATSUrl, cfg.NATSToken)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to NATS: %v", err)
|
||||
}
|
||||
defer nc.Close()
|
||||
|
||||
log.Println("NATS connection established")
|
||||
|
||||
// Initialize daemon configuration
|
||||
daemonCfg := &app.DaemonConfig{
|
||||
LicenseID: cfg.LicenseID,
|
||||
HeartbeatInterval: time.Duration(cfg.HeartbeatInterval) * time.Second,
|
||||
SteamCMDPath: cfg.SteamCMDPath,
|
||||
GameServerPath: cfg.GameServerPath,
|
||||
GameServerArgs: cfg.GameServerArgs,
|
||||
Version: version,
|
||||
}
|
||||
|
||||
// Start daemon
|
||||
daemon, err := app.NewDaemon(nc, daemonCfg)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to initialize daemon: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Daemon initialized, starting main loop...")
|
||||
|
||||
// Run daemon until context is cancelled
|
||||
if err := daemon.Run(ctx); err != nil {
|
||||
log.Fatalf("Daemon error: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Companion agent shutdown complete")
|
||||
}
|
||||
Reference in New Issue
Block a user