package config import ( "fmt" "os" "strconv" "strings" "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 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_STAGE_WORKERS_EXTRACT"); ok { value, err := parseIntEnv("NOTARIUS_STAGE_WORKERS_EXTRACT", raw) if err != nil { return err } if c.Concurrency.StageWorkers == nil { c.Concurrency.StageWorkers = make(map[string]int) } c.Concurrency.StageWorkers["extract"] = value c.Concurrency.extractWorkersConfigured = true } c.Concurrency.recomputeStageWorkerDefaults() if raw, ok := lookup("NOTARIUS_OUTPUT_DIR"); ok { c.Output.Directory = strings.TrimSpace(raw) if c.Output.Directory == "" { return fmt.Errorf("NOTARIUS_OUTPUT_DIR: must not be empty") } if strings.ContainsRune(c.Output.Directory, '\x00') { return fmt.Errorf("NOTARIUS_OUTPUT_DIR: must not contain NUL") } } if raw, ok := lookup("NOTARIUS_CACHE_CHUNK_PLANS_MODE"); ok { mode, err := pipeline.ParseChunkCacheMode(raw) if err != nil { return fmt.Errorf("NOTARIUS_CACHE_CHUNK_PLANS_MODE: %w", err) } c.Cache.ChunkPlans.Mode = mode } if raw, ok := lookup("NOTARIUS_CACHE_CHUNK_PLANS_DIR"); ok { c.Cache.ChunkPlans.Directory = cleanOptionalPath(raw) if c.Cache.ChunkPlans.Directory == "" { return fmt.Errorf("NOTARIUS_CACHE_CHUNK_PLANS_DIR: must not be empty") } if strings.ContainsRune(c.Cache.ChunkPlans.Directory, '\x00') { return fmt.Errorf("NOTARIUS_CACHE_CHUNK_PLANS_DIR: must not contain NUL") } } if raw, ok := lookup("NOTARIUS_CACHE_CHECKPOINTS_DIR"); ok { c.Cache.Checkpoints.Directory = cleanOptionalPath(raw) if c.Cache.Checkpoints.Directory == "" { return fmt.Errorf("NOTARIUS_CACHE_CHECKPOINTS_DIR: must not be empty") } if strings.ContainsRune(c.Cache.Checkpoints.Directory, '\x00') { return fmt.Errorf("NOTARIUS_CACHE_CHECKPOINTS_DIR: must not contain NUL") } } if raw, ok := lookup("NOTARIUS_DEBUG_DIR"); ok { c.Debug.Directory = strings.TrimSpace(raw) if c.Debug.Directory == "" { return fmt.Errorf("NOTARIUS_DEBUG_DIR: must not be empty") } if strings.ContainsRune(c.Debug.Directory, '\x00') { return fmt.Errorf("NOTARIUS_DEBUG_DIR: must not contain NUL") } } 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 }