feat: Add companion agent one-click deployment + fix frontend TS errors
Go deployment orchestrator with platform-specific SteamCMD install, Rust server download, server.cfg generation, and service registration. Wire deploy command subscription in daemon, make GameServerPath optional, add InstallDir config with OS-aware defaults. Fix unused imports and WebSocket subscribe API in ServerView. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/vigilcyber/corrosion-companion/internal/deploy"
|
||||
"github.com/vigilcyber/corrosion-companion/internal/files"
|
||||
"github.com/vigilcyber/corrosion-companion/internal/process"
|
||||
"github.com/vigilcyber/corrosion-companion/internal/update"
|
||||
@@ -22,6 +23,7 @@ type DaemonConfig struct {
|
||||
GameServerPath string
|
||||
GameServerArgs string
|
||||
Version string
|
||||
InstallDir string
|
||||
}
|
||||
|
||||
// Daemon manages the companion agent's main operations
|
||||
@@ -31,6 +33,7 @@ type Daemon struct {
|
||||
gameServer *process.GameServer
|
||||
fileOps *files.Operations
|
||||
updater *update.Updater
|
||||
deployer *deploy.Deployer
|
||||
subscriptions []*nats.Subscription
|
||||
}
|
||||
|
||||
@@ -44,9 +47,26 @@ type HeartbeatPayload struct {
|
||||
CPUPercent float64 `json:"cpu_percent"`
|
||||
LastUpdate string `json:"last_update"`
|
||||
PlayerCount int `json:"player_count"`
|
||||
Version string `json:"version"`
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
Version string `json:"version"`
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
ServerInstalled bool `json:"server_installed"`
|
||||
}
|
||||
|
||||
// gameServerAdapter wraps process.GameServer to satisfy deploy.GameServerStarter
|
||||
type gameServerAdapter struct {
|
||||
gs *process.GameServer
|
||||
cfg *DaemonConfig
|
||||
}
|
||||
|
||||
func (a *gameServerAdapter) Start() error {
|
||||
return a.gs.Start()
|
||||
}
|
||||
|
||||
func (a *gameServerAdapter) UpdatePath(path string) {
|
||||
a.cfg.GameServerPath = path
|
||||
// Recreate game server with new path
|
||||
*a.gs = *process.NewGameServer(path, a.cfg.GameServerArgs)
|
||||
}
|
||||
|
||||
// NewDaemon creates a new daemon instance
|
||||
@@ -54,6 +74,8 @@ func NewDaemon(nc *nats.Conn, cfg *DaemonConfig) (*Daemon, error) {
|
||||
gameServer := process.NewGameServer(cfg.GameServerPath, cfg.GameServerArgs)
|
||||
fileOps := files.NewOperations()
|
||||
updater := update.NewUpdater(cfg.Version)
|
||||
adapter := &gameServerAdapter{gs: gameServer, cfg: cfg}
|
||||
deployer := deploy.NewDeployer(nc, cfg.LicenseID, cfg.InstallDir, adapter)
|
||||
|
||||
d := &Daemon{
|
||||
nc: nc,
|
||||
@@ -61,6 +83,7 @@ func NewDaemon(nc *nats.Conn, cfg *DaemonConfig) (*Daemon, error) {
|
||||
gameServer: gameServer,
|
||||
fileOps: fileOps,
|
||||
updater: updater,
|
||||
deployer: deployer,
|
||||
}
|
||||
|
||||
return d, nil
|
||||
@@ -90,6 +113,11 @@ func (d *Daemon) Run(ctx context.Context) error {
|
||||
return fmt.Errorf("failed to subscribe to self-update: %w", err)
|
||||
}
|
||||
|
||||
// Subscribe to deploy commands
|
||||
if err := d.subscribeDeployCommand(); err != nil {
|
||||
return fmt.Errorf("failed to subscribe to deploy commands: %w", err)
|
||||
}
|
||||
|
||||
log.Println("All subscriptions active")
|
||||
|
||||
// Start heartbeat ticker
|
||||
@@ -267,6 +295,49 @@ func (d *Daemon) subscribeSelfUpdate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// subscribeDeployCommand subscribes to server deployment commands
|
||||
func (d *Daemon) subscribeDeployCommand() error {
|
||||
subject := fmt.Sprintf("corrosion.%s.cmd.deploy", d.cfg.LicenseID)
|
||||
|
||||
sub, err := d.nc.Subscribe(subject, func(msg *nats.Msg) {
|
||||
var cmd struct {
|
||||
Action string `json:"action"`
|
||||
Config deploy.DeployConfig `json:"config"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(msg.Data, &cmd); err != nil {
|
||||
log.Printf("Failed to parse deploy command: %v", err)
|
||||
d.respondError(msg, "invalid_command", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Received deploy command: %s", cmd.Action)
|
||||
|
||||
// Run deployment in goroutine (it's long-running)
|
||||
go func() {
|
||||
if err := d.deployer.Deploy(cmd.Config); err != nil {
|
||||
log.Printf("Deployment failed: %v", err)
|
||||
} else {
|
||||
log.Println("Deployment completed successfully")
|
||||
}
|
||||
}()
|
||||
|
||||
// Immediately acknowledge the command
|
||||
d.respondSuccess(msg, map[string]interface{}{
|
||||
"status": "accepted",
|
||||
"message": "Deployment started, progress will be published to deploy.status",
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.subscriptions = append(d.subscriptions, sub)
|
||||
log.Printf("Subscribed to: %s", subject)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleFileOperation processes file operation requests
|
||||
func (d *Daemon) handleFileOperation(msg *nats.Msg) {
|
||||
// Parse common fields
|
||||
@@ -325,17 +396,18 @@ func (d *Daemon) publishHeartbeat() {
|
||||
diskFree := getDiskFreeSpace(d.cfg.GameServerPath)
|
||||
|
||||
payload := HeartbeatPayload{
|
||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
||||
Status: "running",
|
||||
ServerStatus: status,
|
||||
UptimeSeconds: int64(uptime.Seconds()),
|
||||
DiskFreeMB: diskFree,
|
||||
CPUPercent: 0.0, // TODO: Implement CPU monitoring
|
||||
LastUpdate: "", // TODO: Track last SteamCMD update
|
||||
PlayerCount: 0, // Populated by plugin, not companion
|
||||
Version: d.cfg.Version,
|
||||
OS: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
||||
Status: "running",
|
||||
ServerStatus: status,
|
||||
UptimeSeconds: int64(uptime.Seconds()),
|
||||
DiskFreeMB: diskFree,
|
||||
CPUPercent: 0.0, // TODO: Implement CPU monitoring
|
||||
LastUpdate: "", // TODO: Track last SteamCMD update
|
||||
PlayerCount: 0, // Populated by plugin, not companion
|
||||
Version: d.cfg.Version,
|
||||
OS: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
ServerInstalled: deploy.CheckServerInstalled(d.cfg.InstallDir),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(payload)
|
||||
|
||||
Reference in New Issue
Block a user