Files
ruby-sea-battle/game-server/internal/config/config.go
T

83 lines
2.4 KiB
Go

// Package config loads the game server configuration from environment
// variables.
package config
import (
"fmt"
"os"
"strconv"
"strings"
"time"
)
type Config struct {
// ListenAddr is the address the websocket HTTP server binds to
// (e.g. ":8081"). The server may sit behind a reverse proxy.
ListenAddr string
// PublicAddr is the public endpoint address of this server
// (e.g. "ws://example.com:8081/ws"). It is used only to identify this
// server: the "game_server" claim of a game token must match it
// strictly. The main server must be configured to put exactly this
// string into the tokens it issues.
PublicAddr string
// JWTSecret is the HS256 secret shared with the main server; both the
// game token and the user token are verified with it.
JWTSecret []byte
// JoinTimeout is how long the first player waits for the second one
// before the match is cancelled.
JoinTimeout time.Duration
// AllowedOrigins restricts the Origin header of websocket upgrade
// requests; ["*"] disables the check.
AllowedOrigins []string
}
func Load() (Config, error) {
var cfg Config
cfg.ListenAddr = getenv("LISTEN_ADDR", ":8081")
cfg.PublicAddr = os.Getenv("PUBLIC_ADDR")
if cfg.PublicAddr == "" {
return cfg, fmt.Errorf("PUBLIC_ADDR is required (public endpoint address, e.g. ws://example.com:8081/ws)")
}
secret := os.Getenv("JWT_SECRET")
if secret == "" {
return cfg, fmt.Errorf("JWT_SECRET is required (HS256 secret shared with the main server)")
}
cfg.JWTSecret = []byte(secret)
joinTimeout, err := parseDuration(getenv("JOIN_TIMEOUT", "2m"))
if err != nil {
return cfg, fmt.Errorf("invalid JOIN_TIMEOUT: %w", err)
}
cfg.JoinTimeout = joinTimeout
origins := getenv("ALLOWED_ORIGINS", "*")
cfg.AllowedOrigins = strings.Split(origins, ",")
for i := range cfg.AllowedOrigins {
cfg.AllowedOrigins[i] = strings.TrimSpace(cfg.AllowedOrigins[i])
}
return cfg, nil
}
func getenv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
// parseDuration accepts either a Go duration string ("90s", "2m") or a plain
// number of seconds ("120").
func parseDuration(s string) (time.Duration, error) {
if d, err := time.ParseDuration(s); err == nil {
return d, nil
}
if secs, err := strconv.Atoi(s); err == nil {
return time.Duration(secs) * time.Second, nil
}
return 0, fmt.Errorf("not a duration: %q", s)
}