package config import ( "fmt" "io" "net/netip" "os" "strings" "gopkg.in/yaml.v3" ) type Config struct { Server struct { TrustedProxies []string `yaml:"trusted_proxies"` Listen string `yaml:"listen"` PublicOrigin string `yaml:"public_origin"` } `yaml:"server"` Admin struct { Password string `yaml:"password"` } `yaml:"admin"` Storage struct { LogRetentionDays int `yaml:"log_retention_days"` Path string `yaml:"path"` } `yaml:"storage"` Limits struct { RequestsPerMinute int `yaml:"requests_per_minute"` PerKeyConcurrency int `yaml:"per_key_concurrency"` GlobalConcurrency int `yaml:"global_concurrency"` } `yaml:"limits"` } func Load(path string) (Config, error) { var c Config c.Limits.RequestsPerMinute = 120 c.Limits.PerKeyConcurrency = 8 c.Limits.GlobalConcurrency = 64 c.Server.Listen = "127.0.0.1:8080" c.Storage.Path = "./data/router.db" f, err := os.Open(path) if err != nil { return c, err } defer f.Close() dec := yaml.NewDecoder(f) dec.KnownFields(true) if err = dec.Decode(&c); err != nil { return c, fmt.Errorf("invalid config: %w", err) } var extra any if err = dec.Decode(&extra); err != io.EOF { return c, fmt.Errorf("config must contain exactly one YAML document") } if len(c.Admin.Password) < 12 || strings.HasPrefix(c.Admin.Password, "CHANGE-ME") { return c, fmt.Errorf("admin.password must be a non-placeholder password of at least 12 characters") } if c.Server.Listen == "" || c.Storage.Path == "" { return c, fmt.Errorf("server.listen and storage.path must not be empty") } if c.Limits.RequestsPerMinute <= 0 || c.Limits.PerKeyConcurrency <= 0 || c.Limits.GlobalConcurrency <= 0 { return c, fmt.Errorf("limits must be positive integers") } if c.Storage.LogRetentionDays < 0 { return c, fmt.Errorf("storage.log_retention_days must be nonnegative") } for _, cidr := range c.Server.TrustedProxies { p, err := netip.ParsePrefix(cidr) if err != nil { return c, fmt.Errorf("invalid trusted proxy CIDR %q: %w", cidr, err) } if p.Bits() == 0 { return c, fmt.Errorf("trusted proxy CIDR must not trust all addresses") } } return c, nil }