Files
HexaHost-GameCloud/apps/node-agent/internal/agent/agent.go
smueller 9b061c3ee7
Some checks failed
CI / Node — lint, typecheck, test, build (push) Failing after 9s
CI / Go — node-agent tests (push) Failing after 9s
CI / Go — edge-gateway build (push) Successful in 13s
Phase3
2026-06-26 12:17:26 +02:00

111 lines
2.5 KiB
Go

package agent
import (
"context"
"log/slog"
"os"
"os/signal"
"syscall"
"github.com/hexahost/gamecloud/node-agent/internal/config"
dockerpkg "github.com/hexahost/gamecloud/node-agent/internal/docker"
"github.com/hexahost/gamecloud/node-agent/internal/health"
"github.com/hexahost/gamecloud/node-agent/internal/runtime"
"github.com/hexahost/gamecloud/node-agent/internal/ws"
)
const Version = "0.3.0-phase3"
// Agent coordinates the node agent lifecycle until shutdown.
type Agent struct {
cfg *config.Config
log *slog.Logger
health *health.Server
docker *dockerpkg.Client
ws *ws.Client
}
// New creates an agent instance from configuration.
func New(cfg *config.Config, log *slog.Logger) (*Agent, error) {
dockerClient, err := dockerpkg.New(cfg.DockerSocket)
if err != nil {
return nil, err
}
wsClient := ws.NewClient(cfg, log, nil, Version)
runtimeMgr := runtime.NewManager(dockerClient, wsClient, log)
wsClient.SetRuntime(runtimeMgr)
return &Agent{
cfg: cfg,
log: log,
health: health.NewServer(cfg.HealthListenAddr, Version),
docker: dockerClient,
ws: wsClient,
}, nil
}
// Run starts the agent loop and blocks until context cancellation or signal.
func (a *Agent) Run(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(sigCh)
errCh := make(chan error, 2)
go func() {
a.log.Info("health server listening", "addr", a.cfg.HealthListenAddr)
if err := a.health.ListenAndServe(); err != nil {
errCh <- err
}
}()
go func() {
errCh <- a.ws.Run(ctx)
}()
a.health.SetReady(true)
a.log.Info("agent started",
"node_id", a.cfg.NodeID,
"control_plane", a.cfg.ControlPlaneURL,
"version", Version,
)
for {
select {
case <-ctx.Done():
return a.shutdown(nil)
case sig := <-sigCh:
a.log.Info("shutdown signal received", "signal", sig.String())
cancel()
case err := <-errCh:
if ctx.Err() != nil {
return a.shutdown(nil)
}
if err != nil {
return err
}
}
}
}
func (a *Agent) shutdown(reason error) error {
a.health.SetReady(false)
a.log.Info("agent shutting down")
if a.docker != nil {
if err := a.docker.Close(); err != nil {
a.log.Warn("docker client close failed", "error", err)
}
}
if reason != nil && reason != context.Canceled {
a.log.Warn("shutdown with error", "error", reason)
return reason
}
a.log.Info("agent stopped")
return nil
}