Files
2026-10-13 07:02:34 +00:00

103 lines
3.3 KiB
Go

// Package config loads the master's configuration from environment variables.
package config
import (
"fmt"
"os"
"strings"
"time"
)
// 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
// UpdaterAPIKey is the bearer token for POST /update (UPDATER_API_KEY).
// Empty disables the webhook.
UpdaterAPIKey string
// UpdateTimeout bounds how long one webhook request waits for its auto
// jobs (UPDATE_TIMEOUT, default 5m).
UpdateTimeout time.Duration
// PollInterval is the time between registry polling cycles
// (POLL_INTERVAL, default 6h).
PollInterval time.Duration
}
// defaultUpdateTimeout is used when UPDATE_TIMEOUT is unset.
const defaultUpdateTimeout = 5 * time.Minute
// defaultPollInterval is used when POLL_INTERVAL is unset.
const defaultPollInterval = 6 * time.Hour
// 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"),
UpdaterAPIKey: os.Getenv("UPDATER_API_KEY"),
UpdateTimeout: defaultUpdateTimeout,
PollInterval: defaultPollInterval,
}
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://")
}
if v := os.Getenv("UPDATE_TIMEOUT"); v != "" {
d, err := time.ParseDuration(v)
if err != nil || d <= 0 {
return nil, fmt.Errorf("UPDATE_TIMEOUT must be a positive duration such as 5m, got %q", v)
}
cfg.UpdateTimeout = d
}
if v := os.Getenv("POLL_INTERVAL"); v != "" {
d, err := time.ParseDuration(v)
if err != nil || d <= 0 {
return nil, fmt.Errorf("POLL_INTERVAL must be a positive duration such as 6h, got %q", v)
}
cfg.PollInterval = d
}
return cfg, nil
}
func getenvDefault(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}