// Package config owns GL configuration loading and validation. package config import ( "fmt" "os" "strings" "time" "github.com/knadh/koanf/parsers/toml" "github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/v2" ) type Config struct { Environment string `koanf:"environment"` GRPC GRPCConfig `koanf:"grpc"` Database DatabaseConfig `koanf:"db"` Settlement SettlementConfig `koanf:"settlement"` Telemetry TelemetryConfig `koanf:"telemetry"` } type DashboardConfig struct { Environment string `koanf:"environment"` HTTP HTTPConfig `koanf:"http"` Database DatabaseConfig `koanf:"db"` Telemetry TelemetryConfig `koanf:"telemetry"` } type TelemetryConfig struct { Enabled bool `koanf:"enabled"` Protocol string `koanf:"protocol"` Endpoint string `koanf:"endpoint"` Insecure bool `koanf:"insecure"` Headers map[string]string `koanf:"headers"` Sampler string `koanf:"sampler"` SamplerArg string `koanf:"sampler-arg"` ExportTimeout time.Duration `koanf:"export-timeout"` BatchTimeout time.Duration `koanf:"batch-timeout"` ShutdownTimeout time.Duration `koanf:"shutdown-timeout"` } type GRPCConfig struct { Host string `koanf:"host"` Port int `koanf:"port"` ShutdownTimeout time.Duration `koanf:"timeout"` } type HTTPConfig struct { Host string `koanf:"host"` Port int `koanf:"port"` ReadHeaderTimeout time.Duration `koanf:"read-header-timeout"` ShutdownTimeout time.Duration `koanf:"shutdown-timeout"` } type DatabaseConfig struct { Host string `koanf:"host"` Port int `koanf:"port"` Name string `koanf:"name"` User string `koanf:"user"` Password string `koanf:"password"` GormLogLevel int `koanf:"gorm-log-level"` SSLMode string `koanf:"-"` } type SettlementConfig struct { Enabled bool `koanf:"enabled"` Endpoint string `koanf:"endpoint"` WorkerID string `koanf:"worker-id"` PollInterval time.Duration `koanf:"poll-interval"` BatchSize int `koanf:"batch-size"` MaxAttempts int `koanf:"max-attempts"` AdminToken string `koanf:"admin-token"` } func Load(path string) (*Config, error) { cfg := &Config{ Environment: "local", GRPC: GRPCConfig{ Host: "0.0.0.0", Port: 8600, ShutdownTimeout: 10 * time.Second, }, Database: defaultDatabaseConfig(), Settlement: SettlementConfig{WorkerID: "gl-settlement-1", PollInterval: 5 * time.Second, BatchSize: 20, MaxAttempts: 8}, } if err := load(path, cfg); err != nil { return nil, err } applyEnvironment(&cfg.Database, &cfg.Settlement) if err := cfg.Validate(); err != nil { return nil, err } return cfg, nil } func LoadDashboard(path string) (*DashboardConfig, error) { cfg := &DashboardConfig{ Environment: "local", HTTP: HTTPConfig{ Host: "0.0.0.0", Port: 8080, ReadHeaderTimeout: 5 * time.Second, ShutdownTimeout: 10 * time.Second, }, Database: defaultDatabaseConfig(), } if err := load(path, cfg); err != nil { return nil, err } applyEnvironment(&cfg.Database, nil) if err := cfg.Validate(); err != nil { return nil, err } return cfg, nil } func applyEnvironment(database *DatabaseConfig, settlement *SettlementConfig) { if value, ok := os.LookupEnv("DARANO_GL_DB_PASSWORD"); ok { database.Password = value } if settlement == nil { return } if value, ok := os.LookupEnv("DARANO_GL_SETTLEMENT_ENDPOINT"); ok { settlement.Endpoint = strings.TrimRight(value, "/") } if value, ok := os.LookupEnv("DARANO_GL_ADMIN_TOKEN"); ok { settlement.AdminToken = value } } func load(path string, target any) error { k := koanf.New(".") if err := k.Load(file.Provider(path), toml.Parser()); err != nil { return fmt.Errorf("load config: %w", err) } if err := k.Unmarshal("", target); err != nil { return fmt.Errorf("decode config: %w", err) } return nil } func defaultDatabaseConfig() DatabaseConfig { return DatabaseConfig{ Host: "127.0.0.1", Port: 5432, Name: "gl_db", User: "postgres", SSLMode: "disable", } } func (c *Config) Validate() error { if c.GRPC.Host == "" { return fmt.Errorf("grpc host is required") } if c.GRPC.Port < 0 || c.GRPC.Port > 65535 { return fmt.Errorf("grpc port must be between 0 and 65535") } if c.GRPC.ShutdownTimeout <= 0 { return fmt.Errorf("grpc shutdown timeout must be positive") } if c.Environment == "production" && c.Settlement.AdminToken == "" { return fmt.Errorf("settlement admin token is required in production") } return validateDatabase(c.Database) } func (c *DashboardConfig) Validate() error { if c.HTTP.Host == "" { return fmt.Errorf("http host is required") } if c.HTTP.Port < 0 || c.HTTP.Port > 65535 { return fmt.Errorf("http port must be between 0 and 65535") } if c.HTTP.ReadHeaderTimeout <= 0 || c.HTTP.ShutdownTimeout <= 0 { return fmt.Errorf("http timeouts must be positive") } return validateDatabase(c.Database) } func validateDatabase(database DatabaseConfig) error { if database.Host == "" || database.Name == "" || database.User == "" { return fmt.Errorf("database host, name, and user are required") } if database.Port < 1 || database.Port > 65535 { return fmt.Errorf("database port must be between 1 and 65535") } return nil }