package config import ( "fmt" "os" "strconv" "strings" "gitea.maximumdirect.net/eric/notarius/internal/core/diagnostics" "gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline" ) func LoadFromEnv() (Config, error) { cfg := Default() if err := cfg.applyEnvOverridesWithLookup(os.LookupEnv); err != nil { return Config{}, err } return cfg, nil } func (c *Config) ApplyEnvOverrides() error { return c.applyEnvOverridesWithLookup(os.LookupEnv) } func (c *Config) ApplyEnvOverridesWithLookup(lookup func(string) (string, bool)) error { return c.applyEnvOverridesWithLookup(lookup) } func (c *Config) applyEnvOverridesWithLookup(lookup func(string) (string, bool)) error { if c == nil { return fmt.Errorf("config must not be nil") } if c.LLMProfiles == nil { c.LLMProfiles = map[string]LLMProfile{} } defaultProfile := c.LLMProfiles[pipeline.DefaultLLMProfile] if raw, ok := lookup("NOTARIUS_LLM_DEFAULT_API_KEY"); ok { defaultProfile.APIKey = raw } if raw, ok := lookup("NOTARIUS_LLM_DEFAULT_BASE_URL"); ok { defaultProfile.BaseURL = strings.TrimSpace(raw) } if raw, ok := lookup("NOTARIUS_LLM_DEFAULT_MODEL"); ok { defaultProfile.Model = strings.TrimSpace(raw) } if raw, ok := lookup("NOTARIUS_LLM_DEFAULT_TIMEOUT_SECONDS"); ok { value, err := parseIntEnv("NOTARIUS_LLM_DEFAULT_TIMEOUT_SECONDS", raw) if err != nil { return err } defaultProfile.TimeoutSeconds = value } if raw, ok := lookup("NOTARIUS_LLM_DEFAULT_MAX_RETRIES"); ok { value, err := parseIntEnv("NOTARIUS_LLM_DEFAULT_MAX_RETRIES", raw) if err != nil { return err } defaultProfile.MaxRetries = value } if raw, ok := lookup("NOTARIUS_LLM_DEFAULT_MAX_CONCURRENCY"); ok { value, err := parseIntEnv("NOTARIUS_LLM_DEFAULT_MAX_CONCURRENCY", raw) if err != nil { return err } defaultProfile.MaxConcurrency = value } c.LLMProfiles[pipeline.DefaultLLMProfile] = defaultProfile if raw, ok := lookup("NOTARIUS_TOTAL_LLM_CONCURRENCY"); ok { value, err := parseIntEnv("NOTARIUS_TOTAL_LLM_CONCURRENCY", raw) if err != nil { return err } c.Concurrency.TotalLLM = value } if raw, ok := lookup("NOTARIUS_WORK_DIR"); ok { c.Diagnostics.WorkDir = strings.TrimSpace(raw) } if raw, ok := lookup("NOTARIUS_DIAGNOSTICS_RETENTION"); ok { c.Diagnostics.Retention = diagnostics.RetentionMode(strings.TrimSpace(raw)) } return nil } func parseIntEnv(name string, raw string) (int, error) { value, err := strconv.Atoi(strings.TrimSpace(raw)) if err != nil { return 0, fmt.Errorf("%s: must be an integer", name) } return value, nil }