package config import ( "fmt" "os" "path/filepath" "strings" "gopkg.in/yaml.v3" ) type Config struct { Env string `yaml:"env"` Port string `yaml:"port"` DSN string `yaml:"dsn"` StorageDir string `yaml:"storage_dir"` MaxUploadBytes int64 `yaml:"max_upload_bytes"` AuthSecret string `yaml:"auth_secret"` SystemAIKey string `yaml:"system_ai_key"` AIKeyEncryptionSecret string `yaml:"ai_key_encryption_secret"` AllowedOrigins []string `yaml:"allowed_origins"` } func Load() Config { cfg, err := LoadFromDir("etc") if err != nil { panic(err) } return cfg } func LoadFromDir(configDir string) (Config, error) { mode := strings.TrimSpace(os.Getenv("SENLIN_APP_MODE")) if mode == "" { mode = "dev" } path := filepath.Join(configDir, "agent."+strings.ToLower(mode)+".yaml") data, err := os.ReadFile(path) if err != nil { return Config{}, err } var cfg Config if err := yaml.Unmarshal(data, &cfg); err != nil { return Config{}, err } if cfg.MaxUploadBytes <= 0 { cfg.MaxUploadBytes = 50 << 20 } if strings.TrimSpace(cfg.StorageDir) == "" { return Config{}, fmt.Errorf("storage_dir must not be empty") } hasAllowedOrigin := false for _, origin := range cfg.AllowedOrigins { if strings.TrimSpace(origin) != "" { hasAllowedOrigin = true break } } if !hasAllowedOrigin { return Config{}, fmt.Errorf("allowed_origins must include at least one origin") } if err := validateSecret(cfg.Env, "auth_secret", cfg.AuthSecret); err != nil { return Config{}, err } if err := validateSecret(cfg.Env, "ai_key_encryption_secret", cfg.AIKeyEncryptionSecret); err != nil { return Config{}, err } return cfg, nil } func validateSecret(environment, field, value string) error { secret := strings.TrimSpace(value) if secret == "" { return fmt.Errorf("%s must not be empty", field) } if strings.EqualFold(strings.TrimSpace(environment), "production") || strings.EqualFold(strings.TrimSpace(environment), "prod") { if len(secret) < 32 || isCommonSecret(secret) { return fmt.Errorf("%s must be at least 32 characters and must not use a development sentinel in production", field) } } return nil } func isCommonSecret(value string) bool { normalized := strings.ToLower(strings.TrimSpace(value)) for _, marker := range []string{"change-me", "changeme", "development", "dev-secret", "local-secret", "test-secret", "placeholder"} { if strings.Contains(normalized, marker) { return true } } switch normalized { case "secret", "password", "default", "admin": return true default: return false } }