feat: init commit with main func

This commit is contained in:
2026-03-15 19:29:27 +03:00
commit 27c03fff46
12 changed files with 974 additions and 0 deletions

112
config.go Normal file
View File

@@ -0,0 +1,112 @@
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
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
RecordTTL int
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")
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)
}
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
}