config: initial loading & env overrides
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Iaa7401a20506a084a2a16882e61ea0bc6a6a6964
This commit is contained in:
parent
356aa999af
commit
4f8d1c64d2
3 changed files with 222 additions and 0 deletions
30
config.example.yaml
Normal file
30
config.example.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
server:
|
||||
listen: ":8080"
|
||||
read_timeout: 30s
|
||||
write_timeout: 30s
|
||||
|
||||
upstreams:
|
||||
- url: "https://cache.nixos.org"
|
||||
priority: 10
|
||||
public_key: "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
||||
|
||||
# Try without a public key
|
||||
- url: "https://nix-community.cachix.org"
|
||||
priority: 20
|
||||
|
||||
cache:
|
||||
db_path: "/var/lib/ncro/routes.db"
|
||||
max_entries: 100000
|
||||
ttl: 1h
|
||||
latency_alpha: 0.3
|
||||
|
||||
mesh:
|
||||
enabled: false
|
||||
bind_addr: "0.0.0.0:7946"
|
||||
peers: []
|
||||
private_key: "/etc/ncro/node.key"
|
||||
gossip_interval: 30s
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
format: "json"
|
||||
|
|
@ -1 +1,130 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Duration is a wrapper around time.Duration that supports YAML unmarshaling
|
||||
// from Go duration strings (e.g., "30s", "1h"). yaml.v3 cannot unmarshal
|
||||
// duration strings directly into time.Duration (int64), so we handle it here.
|
||||
type Duration struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
|
||||
var s string
|
||||
if err := value.Decode(&s); err != nil {
|
||||
// Try decoding as a raw int64 (nanoseconds) as fallback.
|
||||
var ns int64
|
||||
if err2 := value.Decode(&ns); err2 != nil {
|
||||
return fmt.Errorf("cannot unmarshal duration: %w", err)
|
||||
}
|
||||
d.Duration = time.Duration(ns)
|
||||
return nil
|
||||
}
|
||||
parsed, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid duration %q: %w", s, err)
|
||||
}
|
||||
d.Duration = parsed
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpstreamConfig struct {
|
||||
URL string `yaml:"url"`
|
||||
Priority int `yaml:"priority"`
|
||||
PublicKey string `yaml:"public_key"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Listen string `yaml:"listen"`
|
||||
ReadTimeout Duration `yaml:"read_timeout"`
|
||||
WriteTimeout Duration `yaml:"write_timeout"`
|
||||
}
|
||||
|
||||
type CacheConfig struct {
|
||||
DBPath string `yaml:"db_path"`
|
||||
MaxEntries int `yaml:"max_entries"`
|
||||
TTL Duration `yaml:"ttl"`
|
||||
LatencyAlpha float64 `yaml:"latency_alpha"`
|
||||
}
|
||||
|
||||
type MeshConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
BindAddr string `yaml:"bind_addr"`
|
||||
Peers []string `yaml:"peers"`
|
||||
PrivateKeyPath string `yaml:"private_key"`
|
||||
GossipInterval Duration `yaml:"gossip_interval"`
|
||||
}
|
||||
|
||||
type LoggingConfig struct {
|
||||
Level string `yaml:"level"`
|
||||
Format string `yaml:"format"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig `yaml:"server"`
|
||||
Upstreams []UpstreamConfig `yaml:"upstreams"`
|
||||
Cache CacheConfig `yaml:"cache"`
|
||||
Mesh MeshConfig `yaml:"mesh"`
|
||||
Logging LoggingConfig `yaml:"logging"`
|
||||
}
|
||||
|
||||
func defaults() Config {
|
||||
return Config{
|
||||
Server: ServerConfig{
|
||||
Listen: ":8080",
|
||||
ReadTimeout: Duration{30 * time.Second},
|
||||
WriteTimeout: Duration{30 * time.Second},
|
||||
},
|
||||
Upstreams: []UpstreamConfig{
|
||||
{URL: "https://cache.nixos.org", Priority: 10},
|
||||
},
|
||||
Cache: CacheConfig{
|
||||
DBPath: "/var/lib/ncro/routes.db",
|
||||
MaxEntries: 100000,
|
||||
TTL: Duration{time.Hour},
|
||||
LatencyAlpha: 0.3,
|
||||
},
|
||||
Mesh: MeshConfig{
|
||||
BindAddr: "0.0.0.0:7946",
|
||||
GossipInterval: Duration{30 * time.Second},
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
Level: "info",
|
||||
Format: "json",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Load loads config from file (if non-empty) and applies env overrides.
|
||||
func Load(path string) (*Config, error) {
|
||||
cfg := defaults()
|
||||
|
||||
if path != "" {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Env overrides
|
||||
if v := os.Getenv("NCRO_LISTEN"); v != "" {
|
||||
cfg.Server.Listen = v
|
||||
}
|
||||
if v := os.Getenv("NCRO_DB_PATH"); v != "" {
|
||||
cfg.Cache.DBPath = v
|
||||
}
|
||||
if v := os.Getenv("NCRO_LOG_LEVEL"); v != "" {
|
||||
cfg.Logging.Level = v
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
|
|
|||
63
internal/config/config_test.go
Normal file
63
internal/config/config_test.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package config_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"notashelf.dev/ncro/internal/config"
|
||||
)
|
||||
|
||||
func TestLoadDefaults(t *testing.T) {
|
||||
cfg, err := config.Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("Load(\"\") error: %v", err)
|
||||
}
|
||||
if cfg.Server.Listen != ":8080" {
|
||||
t.Errorf("default listen = %q, want :8080", cfg.Server.Listen)
|
||||
}
|
||||
if len(cfg.Upstreams) == 0 {
|
||||
t.Error("expected at least one default upstream")
|
||||
}
|
||||
if cfg.Cache.MaxEntries != 100000 {
|
||||
t.Errorf("default max_entries = %d, want 100000", cfg.Cache.MaxEntries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFromYAML(t *testing.T) {
|
||||
yamlContent := `
|
||||
server:
|
||||
listen: ":9090"
|
||||
upstreams:
|
||||
- url: "https://cache.nixos.org"
|
||||
priority: 10
|
||||
cache:
|
||||
db_path: "/tmp/test.db"
|
||||
max_entries: 500
|
||||
`
|
||||
f, _ := os.CreateTemp("", "ncro-*.yaml")
|
||||
defer os.Remove(f.Name())
|
||||
f.WriteString(yamlContent)
|
||||
f.Close()
|
||||
|
||||
cfg, err := config.Load(f.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("Load error: %v", err)
|
||||
}
|
||||
if cfg.Server.Listen != ":9090" {
|
||||
t.Errorf("listen = %q, want :9090", cfg.Server.Listen)
|
||||
}
|
||||
if cfg.Cache.MaxEntries != 500 {
|
||||
t.Errorf("max_entries = %d, want 500", cfg.Cache.MaxEntries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvOverride(t *testing.T) {
|
||||
t.Setenv("NCRO_LISTEN", ":1234")
|
||||
cfg, err := config.Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("Load error: %v", err)
|
||||
}
|
||||
if cfg.Server.Listen != ":1234" {
|
||||
t.Errorf("env override listen = %q, want :1234", cfg.Server.Listen)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue