136 lines
3.6 KiB
Go
136 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Config holds all runtime configuration loaded from environment variables.
|
|
type Config struct {
|
|
TraefikURL string
|
|
TraefikUsername string
|
|
TraefikPassword string
|
|
|
|
GitAuthUsername string
|
|
GitAuthToken string
|
|
|
|
Zones []string
|
|
PublicIP string
|
|
PublicIPv6 string // empty = no AAAA records
|
|
|
|
RepoPath string
|
|
RepoBranch string
|
|
RepoRemote string
|
|
DynamicDir string
|
|
AuthorName string
|
|
AuthorEmail string
|
|
|
|
ReconcileInterval time.Duration
|
|
DebounceDelay time.Duration
|
|
LogLevel string
|
|
|
|
RecordTTL int
|
|
CloudflareAutoTTL bool
|
|
ExcludeRouters map[string]struct{}
|
|
}
|
|
|
|
// LoadConfig reads configuration from environment variables and validates required fields.
|
|
func LoadConfig() (*Config, error) {
|
|
cfg := &Config{}
|
|
|
|
cfg.TraefikURL = envOrDefault("TRAEFIK_URL", "http://localhost:8080")
|
|
cfg.TraefikUsername = os.Getenv("TRAEFIK_USERNAME")
|
|
cfg.TraefikPassword = os.Getenv("TRAEFIK_PASSWORD")
|
|
|
|
// Optional HTTPS git authentication credentials.
|
|
// If GIT_AUTH_TOKEN is set, git commands will run non-interactively via GIT_ASKPASS.
|
|
cfg.GitAuthUsername = envOrDefault("GIT_AUTH_USERNAME", "x-access-token")
|
|
cfg.GitAuthToken = os.Getenv("GIT_AUTH_TOKEN")
|
|
|
|
zonesStr := os.Getenv("DNS_ZONES")
|
|
if zonesStr == "" {
|
|
return nil, fmt.Errorf("DNS_ZONES is required (comma-separated list of zones, e.g. example.com,example.net)")
|
|
}
|
|
for _, z := range strings.Split(zonesStr, ",") {
|
|
if z = strings.TrimSpace(z); z != "" {
|
|
cfg.Zones = append(cfg.Zones, z)
|
|
}
|
|
}
|
|
|
|
cfg.PublicIP = os.Getenv("PUBLIC_IP")
|
|
if cfg.PublicIP == "" {
|
|
return nil, fmt.Errorf("PUBLIC_IP is required")
|
|
}
|
|
cfg.PublicIPv6 = os.Getenv("PUBLIC_IPV6")
|
|
|
|
cfg.RepoPath = os.Getenv("DNS_REPO_PATH")
|
|
if cfg.RepoPath == "" {
|
|
return nil, fmt.Errorf("DNS_REPO_PATH is required")
|
|
}
|
|
|
|
cfg.RepoBranch = envOrDefault("DNS_REPO_BRANCH", "main")
|
|
cfg.RepoRemote = envOrDefault("DNS_REPO_REMOTE", "origin")
|
|
cfg.DynamicDir = envOrDefault("DNS_REPO_DYNAMIC_DIR", "zones-dynamic")
|
|
cfg.AuthorName = envOrDefault("DNS_REPO_AUTHOR_NAME", "traefik-dns-watcher")
|
|
cfg.AuthorEmail = envOrDefault("DNS_REPO_AUTHOR_EMAIL", "dns-bot@localhost")
|
|
|
|
var err error
|
|
cfg.ReconcileInterval, err = parseDurationEnv("RECONCILE_INTERVAL", "60s")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cfg.DebounceDelay, err = parseDurationEnv("DEBOUNCE_DELAY", "5s")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ttlStr := envOrDefault("RECORD_TTL", "300")
|
|
cfg.RecordTTL, err = strconv.Atoi(ttlStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("RECORD_TTL: invalid integer %q: %w", ttlStr, err)
|
|
}
|
|
|
|
autoTTLStr := envOrDefault("CF_AUTO_TTL", "true")
|
|
cfg.CloudflareAutoTTL, err = strconv.ParseBool(autoTTLStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("CF_AUTO_TTL: invalid boolean %q: %w", autoTTLStr, err)
|
|
}
|
|
|
|
cfg.LogLevel = strings.ToLower(envOrDefault("LOG_LEVEL", "info"))
|
|
switch cfg.LogLevel {
|
|
case "debug", "info", "warn", "error":
|
|
default:
|
|
return nil, fmt.Errorf("LOG_LEVEL: invalid value %q (allowed: debug, info, warn, error)", cfg.LogLevel)
|
|
}
|
|
|
|
cfg.ExcludeRouters = make(map[string]struct{})
|
|
if v := os.Getenv("EXCLUDE_ROUTERS"); v != "" {
|
|
for _, r := range strings.Split(v, ",") {
|
|
if r = strings.TrimSpace(r); r != "" {
|
|
cfg.ExcludeRouters[r] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func envOrDefault(key, def string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
func parseDurationEnv(key, def string) (time.Duration, error) {
|
|
s := envOrDefault(key, def)
|
|
d, err := time.ParseDuration(s)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("%s: invalid duration %q: %w", key, s, err)
|
|
}
|
|
return d, nil
|
|
}
|