// Package nats sets up an embedded NATS server for real-time pub/sub // messaging between game clients. package nats import ( "context" "fmt" "log/slog" "net" "os" "strconv" "github.com/delaneyj/toolbelt" "github.com/delaneyj/toolbelt/embeddednats" natsserver "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" ) func SetupNATS(ctx context.Context) (*nats.Conn, func(), error) { natsPort, err := getFreeNatsPort() if err != nil { return nil, nil, fmt.Errorf("obtaining NATS port: %w", err) } ns, err := embeddednats.New(ctx, embeddednats.WithNATSServerOptions(&natsserver.Options{ NoSigs: true, Port: natsPort, })) if err != nil { return nil, nil, fmt.Errorf("creating embedded nats server: %w", err) } ns.WaitForServer() slog.Info("NATS started", "port", natsPort) nc, err := ns.Client() if err != nil { return nil, nil, fmt.Errorf("creating nats client: %w", err) } cleanup := func() { nc.Close() ns.Close() //nolint:errcheck } return nc, cleanup, nil } func getFreeNatsPort() (int, error) { if p, ok := os.LookupEnv("NATS_PORT"); ok { natsPort, err := strconv.Atoi(p) if err != nil { return 0, fmt.Errorf("parsing NATS_PORT: %w", err) } if isPortFree(natsPort) { return natsPort, nil } } return toolbelt.FreePort() } func isPortFree(port int) bool { ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) if err != nil { return false } ln.Close() //nolint:errcheck // checking port availability return true }