veylant/internal/config/config.go
2026-03-10 12:01:34 +01:00

262 lines
10 KiB
Go

package config
import (
"fmt"
"strings"
"github.com/spf13/viper"
)
// Config holds all application configuration.
// Values are loaded from config.yaml then overridden by env vars prefixed with VEYLANT_.
// Example: VEYLANT_SERVER_PORT=9090 overrides server.port.
type Config struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
Redis RedisConfig `mapstructure:"redis"`
Auth AuthConfig `mapstructure:"auth"`
PII PIIConfig `mapstructure:"pii"`
Log LogConfig `mapstructure:"log"`
Providers ProvidersConfig `mapstructure:"providers"`
RBAC RBACConfig `mapstructure:"rbac"`
Metrics MetricsConfig `mapstructure:"metrics"`
Routing RoutingConfig `mapstructure:"routing"`
ClickHouse ClickHouseConfig `mapstructure:"clickhouse"`
Crypto CryptoConfig `mapstructure:"crypto"`
RateLimit RateLimitConfig `mapstructure:"rate_limit"`
Notifications NotificationsConfig `mapstructure:"notifications"`
}
// NotificationsConfig holds outbound notification settings.
type NotificationsConfig struct {
SMTP SMTPConfig `mapstructure:"smtp"`
}
// SMTPConfig holds SMTP relay settings for email notifications.
// Sensitive fields (Password) should be provided via env vars:
//
// VEYLANT_NOTIFICATIONS_SMTP_PASSWORD=<value>
type SMTPConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
From string `mapstructure:"from"`
FromName string `mapstructure:"from_name"`
}
// RateLimitConfig holds default rate limiting parameters applied to all tenants
// that have no explicit per-tenant override in the rate_limit_configs table.
type RateLimitConfig struct {
// DefaultTenantRPM is the default tenant-wide requests per minute limit.
DefaultTenantRPM int `mapstructure:"default_tenant_rpm"`
// DefaultTenantBurst is the maximum burst size for a tenant bucket.
DefaultTenantBurst int `mapstructure:"default_tenant_burst"`
// DefaultUserRPM is the default per-user requests per minute limit within a tenant.
DefaultUserRPM int `mapstructure:"default_user_rpm"`
// DefaultUserBurst is the maximum burst size for a per-user bucket.
DefaultUserBurst int `mapstructure:"default_user_burst"`
}
// ClickHouseConfig holds ClickHouse connection settings for the audit log.
type ClickHouseConfig struct {
DSN string `mapstructure:"dsn"` // clickhouse://user:pass@host:9000/db
MaxConns int `mapstructure:"max_conns"`
DialTimeoutSec int `mapstructure:"dial_timeout_seconds"`
}
// CryptoConfig holds cryptographic settings.
type CryptoConfig struct {
// AESKeyBase64 is a base64-encoded 32-byte key for AES-256-GCM prompt encryption.
// Set via env var VEYLANT_CRYPTO_AES_KEY_BASE64 — never hardcode.
AESKeyBase64 string `mapstructure:"aes_key_base64"`
}
// RoutingConfig controls the intelligent routing engine behaviour.
type RoutingConfig struct {
// CacheTTLSeconds is how long routing rules are cached per tenant before
// a background refresh. 0 means use the default (30s).
CacheTTLSeconds int `mapstructure:"cache_ttl_seconds"`
}
// ProvidersConfig holds configuration for all LLM provider adapters.
type ProvidersConfig struct {
OpenAI OpenAIConfig `mapstructure:"openai"`
Anthropic AnthropicConfig `mapstructure:"anthropic"`
Azure AzureConfig `mapstructure:"azure"`
Mistral MistralConfig `mapstructure:"mistral"`
Ollama OllamaConfig `mapstructure:"ollama"`
}
// OpenAIConfig holds OpenAI adapter configuration.
type OpenAIConfig struct {
APIKey string `mapstructure:"api_key"`
BaseURL string `mapstructure:"base_url"`
TimeoutSeconds int `mapstructure:"timeout_seconds"`
MaxConns int `mapstructure:"max_conns"`
}
// AnthropicConfig holds Anthropic adapter configuration.
type AnthropicConfig struct {
APIKey string `mapstructure:"api_key"`
BaseURL string `mapstructure:"base_url"`
Version string `mapstructure:"version"` // Anthropic API version header, e.g. "2023-06-01"
TimeoutSeconds int `mapstructure:"timeout_seconds"`
MaxConns int `mapstructure:"max_conns"`
}
// AzureConfig holds Azure OpenAI adapter configuration.
type AzureConfig struct {
APIKey string `mapstructure:"api_key"`
ResourceName string `mapstructure:"resource_name"` // e.g. "my-azure-resource"
DeploymentID string `mapstructure:"deployment_id"` // e.g. "gpt-4o"
APIVersion string `mapstructure:"api_version"` // e.g. "2024-02-01"
TimeoutSeconds int `mapstructure:"timeout_seconds"`
MaxConns int `mapstructure:"max_conns"`
}
// MistralConfig holds Mistral AI adapter configuration (OpenAI-compatible).
type MistralConfig struct {
APIKey string `mapstructure:"api_key"`
BaseURL string `mapstructure:"base_url"`
TimeoutSeconds int `mapstructure:"timeout_seconds"`
MaxConns int `mapstructure:"max_conns"`
}
// OllamaConfig holds Ollama adapter configuration (OpenAI-compatible, local).
type OllamaConfig struct {
BaseURL string `mapstructure:"base_url"`
TimeoutSeconds int `mapstructure:"timeout_seconds"`
MaxConns int `mapstructure:"max_conns"`
}
// RBACConfig holds role-based access control settings for the provider router.
type RBACConfig struct {
// UserAllowedModels lists models accessible to the "user" role (exact or prefix match).
UserAllowedModels []string `mapstructure:"user_allowed_models"`
// AuditorCanComplete controls whether auditors can make chat completions.
// Defaults to false — auditors receive 403 on POST /v1/chat/completions.
AuditorCanComplete bool `mapstructure:"auditor_can_complete"`
}
// MetricsConfig holds Prometheus metrics configuration.
type MetricsConfig struct {
Enabled bool `mapstructure:"enabled"`
Path string `mapstructure:"path"`
}
type ServerConfig struct {
Port int `mapstructure:"port"`
ShutdownTimeout int `mapstructure:"shutdown_timeout_seconds"`
Env string `mapstructure:"env"` // development, staging, production
TenantName string `mapstructure:"tenant_name"` // display name used in PDF reports
AllowedOrigins []string `mapstructure:"allowed_origins"` // CORS allowed origins for the React dashboard
}
type DatabaseConfig struct {
URL string `mapstructure:"url"`
MaxOpenConns int `mapstructure:"max_open_conns"`
MaxIdleConns int `mapstructure:"max_idle_conns"`
MigrationsPath string `mapstructure:"migrations_path"`
}
type RedisConfig struct {
URL string `mapstructure:"url"`
}
// AuthConfig holds local JWT authentication settings.
// Override via VEYLANT_AUTH_JWT_SECRET and VEYLANT_AUTH_JWT_TTL_HOURS.
type AuthConfig struct {
JWTSecret string `mapstructure:"jwt_secret"`
JWTTTLHours int `mapstructure:"jwt_ttl_hours"`
}
type PIIConfig struct {
Enabled bool `mapstructure:"enabled"`
ServiceAddr string `mapstructure:"service_addr"` // gRPC address, e.g. localhost:50051
TimeoutMs int `mapstructure:"timeout_ms"`
FailOpen bool `mapstructure:"fail_open"` // if true, pass request through on PII service error
}
type LogConfig struct {
Level string `mapstructure:"level"` // debug, info, warn, error
Format string `mapstructure:"format"` // json, console
}
// Load reads configuration from config.yaml (searched in . and ./config)
// and overrides with environment variables prefixed VEYLANT_.
func Load() (*Config, error) {
v := viper.New()
v.SetConfigName("config")
v.SetConfigType("yaml")
v.AddConfigPath(".")
v.AddConfigPath("./config")
// Env var overrides: VEYLANT_SERVER_PORT → server.port
v.SetEnvPrefix("VEYLANT")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()
// Defaults
v.SetDefault("server.port", 8090)
v.SetDefault("server.shutdown_timeout_seconds", 30)
v.SetDefault("server.env", "development")
v.SetDefault("server.allowed_origins", []string{"http://localhost:3000"})
v.SetDefault("database.max_open_conns", 25)
v.SetDefault("database.max_idle_conns", 5)
v.SetDefault("database.migrations_path", "migrations")
v.SetDefault("auth.jwt_secret", "change-me-in-production-use-VEYLANT_AUTH_JWT_SECRET")
v.SetDefault("auth.jwt_ttl_hours", 24)
v.SetDefault("pii.enabled", false)
v.SetDefault("pii.service_addr", "localhost:50051")
v.SetDefault("pii.timeout_ms", 100)
v.SetDefault("pii.fail_open", true)
v.SetDefault("log.level", "info")
v.SetDefault("log.format", "json")
v.SetDefault("providers.openai.base_url", "https://api.openai.com/v1")
v.SetDefault("providers.openai.timeout_seconds", 30)
v.SetDefault("providers.openai.max_conns", 100)
v.SetDefault("providers.anthropic.base_url", "https://api.anthropic.com/v1")
v.SetDefault("providers.anthropic.version", "2023-06-01")
v.SetDefault("providers.anthropic.timeout_seconds", 30)
v.SetDefault("providers.anthropic.max_conns", 100)
v.SetDefault("providers.azure.api_version", "2024-02-01")
v.SetDefault("providers.azure.timeout_seconds", 30)
v.SetDefault("providers.azure.max_conns", 100)
v.SetDefault("providers.mistral.base_url", "https://api.mistral.ai/v1")
v.SetDefault("providers.mistral.timeout_seconds", 30)
v.SetDefault("providers.mistral.max_conns", 100)
v.SetDefault("providers.ollama.base_url", "http://localhost:11434/v1")
v.SetDefault("providers.ollama.timeout_seconds", 120)
v.SetDefault("providers.ollama.max_conns", 10)
v.SetDefault("rbac.user_allowed_models", []string{"gpt-4o-mini", "gpt-3.5-turbo", "mistral-small"})
v.SetDefault("rbac.auditor_can_complete", false)
v.SetDefault("metrics.enabled", true)
v.SetDefault("metrics.path", "/metrics")
v.SetDefault("routing.cache_ttl_seconds", 30)
v.SetDefault("clickhouse.max_conns", 10)
v.SetDefault("clickhouse.dial_timeout_seconds", 5)
v.SetDefault("rate_limit.default_tenant_rpm", 1000)
v.SetDefault("rate_limit.default_tenant_burst", 200)
v.SetDefault("rate_limit.default_user_rpm", 100)
v.SetDefault("rate_limit.default_user_burst", 20)
v.SetDefault("notifications.smtp.port", 587)
v.SetDefault("notifications.smtp.from", "noreply@veylant.ai")
v.SetDefault("notifications.smtp.from_name", "Veylant IA")
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return nil, fmt.Errorf("reading config: %w", err)
}
// Config file not found — rely on defaults and env vars only
}
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("unmarshaling config: %w", err)
}
return &cfg, nil
}