// Package config loads the master's configuration from environment variables. package config import ( "fmt" "os" "strings" ) // Config holds the master's runtime configuration. type Config struct { DatabaseURL string PublicURL string // where people reach the UI, without trailing slash AgentURL string // what agents dial, shown in the add-agent snippet AgentImage string OIDCIssuer string OIDCClientID string OIDCClientSecret string AllowedGroup string Port string AgentPort string LogLevel string } // Load reads and validates the master configuration, failing fast on // anything missing so a half-configured master never starts. func Load() (*Config, error) { cfg := &Config{ DatabaseURL: os.Getenv("DATABASE_URL"), PublicURL: strings.TrimSuffix(os.Getenv("PUBLIC_URL"), "/"), AgentURL: os.Getenv("AGENT_URL"), AgentImage: getenvDefault("AGENT_IMAGE", "gitea.shcizo.se/shcizo/package-updater:latest"), OIDCIssuer: os.Getenv("OIDC_ISSUER"), OIDCClientID: os.Getenv("OIDC_CLIENT_ID"), OIDCClientSecret: os.Getenv("OIDC_CLIENT_SECRET"), AllowedGroup: os.Getenv("ALLOWED_GROUP"), Port: getenvDefault("PORT", "8080"), AgentPort: getenvDefault("AGENT_PORT", "8090"), LogLevel: getenvDefault("LOG_LEVEL", "info"), } for _, r := range []struct{ name, value string }{ {"DATABASE_URL", cfg.DatabaseURL}, {"PUBLIC_URL", cfg.PublicURL}, {"AGENT_URL", cfg.AgentURL}, {"OIDC_ISSUER", cfg.OIDCIssuer}, {"OIDC_CLIENT_ID", cfg.OIDCClientID}, {"OIDC_CLIENT_SECRET", cfg.OIDCClientSecret}, {"ALLOWED_GROUP", cfg.AllowedGroup}, } { if r.value == "" { return nil, fmt.Errorf("%s is required", r.name) } } if !strings.HasPrefix(cfg.PublicURL, "http://") && !strings.HasPrefix(cfg.PublicURL, "https://") { return nil, fmt.Errorf("PUBLIC_URL must start with http:// or https://") } if !strings.HasPrefix(cfg.AgentURL, "wss://") { return nil, fmt.Errorf("AGENT_URL must start with wss://") } return cfg, nil } func getenvDefault(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }