package main import ( "context" "flag" "fmt" "os" "os/signal" "syscall" "time" "github.com/cosmic-clash/cosmic-clash/server/supervisor" ) const usageText = `Usage: game-server-supervisor [options] -- [args...] The child command is started only after an allocated Agones endpoint has been validated and, when configured, an explicit process-ready probe succeeds. SIGTERM/SIGINT requests authenticated drain before the bounded grace deadline. ` func main() { args := os.Args[1:] separator := -1 for i, arg := range args { if arg == "--" { separator = i break } } if separator < 0 || separator == len(args)-1 { fmt.Fprint(os.Stderr, usageText) os.Exit(2) } options := flag.NewFlagSet("game-server-supervisor", flag.ContinueOnError) options.SetOutput(os.Stderr) sdkBaseURL := options.String("sdk-base-url", "", "Agones SDK REST base URL; empty enables direct mode") readyURL := options.String("ready-url", "", "explicit process-ready probe URL") drainURL := options.String("drain-url", "", "loopback drain URL") drainTokenEnv := options.String("drain-token-env", "COSMIC_CLASH_DRAIN_TOKEN", "environment variable containing the drain bearer token") transport := options.String("transport", "enet", "enet or steam_sdr") grace := options.Duration("drain-grace", supervisor.DefaultDrainGrace, "maximum graceful drain duration") controlPlaneURL := options.String("control-plane-url", "", "matchmaking control-plane base URL; empty skips process-ready registration entirely") workloadTokenPath := options.String("workload-token-path", "", "path to a projected workload service-account token, read fresh on every registration call; if unset, falls back to the cosmic-clash.io/workload-token annotation Agones applied to this GameServer at allocation time") serverIDEnv := options.String("server-id-env", "COSMIC_CLASH_SERVER_ID", "environment variable containing this GameServer's control-plane server ID (populate via the Kubernetes Downward API, fieldRef: metadata.name)") matchIDEnv := options.String("match-id-env", "COSMIC_CLASH_MATCH_ID", "environment variable containing the allocated match ID; if unset/empty, falls back to the cosmic-clash.io/match-id annotation on the allocated GameServer") protocolVersion := options.Int("protocol-version", 0, "protocol version reported at registration") imageDigestEnv := options.String("image-digest-env", "COSMIC_CLASH_IMAGE_DIGEST", "environment variable containing this build's sha256 image digest") assignmentReadyAttempts := options.Int("assignment-ready-attempts", 5, "retry attempts for assignment-ready registration after process-ready succeeds (a slow-to-propagate signed roster is not fatal)") assignmentReadyBackoff := options.Duration("assignment-ready-backoff", 2*time.Second, "delay between assignment-ready retry attempts") if err := options.Parse(args[:separator]); err != nil { os.Exit(2) } token := "" if *drainTokenEnv != "" { token = os.Getenv(*drainTokenEnv) } s, err := supervisor.New(supervisor.Config{ Command: args[separator+1:], SDKBaseURL: *sdkBaseURL, ReadyURL: *readyURL, DrainURL: *drainURL, DrainToken: token, Transport: *transport, ReadyTimeout: 30 * time.Second, ControlPlaneURL: *controlPlaneURL, WorkloadTokenPath: *workloadTokenPath, ServerID: os.Getenv(*serverIDEnv), MatchID: os.Getenv(*matchIDEnv), ProtocolVersion: *protocolVersion, ImageDigest: os.Getenv(*imageDigestEnv), AssignmentReadyAttempts: *assignmentReadyAttempts, AssignmentReadyBackoff: *assignmentReadyBackoff, }) if err != nil { fmt.Fprintf(os.Stderr, "game-server-supervisor: %v\n", err) os.Exit(2) } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() if err := s.Run(ctx, *grace); err != nil { fmt.Fprintf(os.Stderr, "game-server-supervisor: %v\n", err) os.Exit(1) } }