package compass import ( "fmt" "os" "path/filepath" "strings" "github.com/knadh/koanf/parsers/yaml" "github.com/knadh/koanf/v2" ) // Set stores an in-memory override that wins over every file and env layer. func (c *Config) Set(path string, value any) error { if c == nil { return fmt.Errorf("compass: config is nil") } if path == "" { return fmt.Errorf("compass: empty config path") } c.mu.Lock() defer c.mu.Unlock() if c.runtime == nil { c.runtime = koanf.New(".") } return c.runtime.Set(path, value) } // Persist writes runtime overrides to config/env//overrides.yaml // using an atomic replace and restrictive file permissions. func (c *Config) Persist() error { if c == nil { return fmt.Errorf("compass: config is nil") } c.mu.Lock() defer c.mu.Unlock() if c.dir == "" { return fmt.Errorf("compass: config directory is empty") } env, err := sanitizeEnv(c.env) if err != nil { return err } destDir, dest, err := overridesPath(c.dir, env) if err != nil { return err } if err := os.MkdirAll(destDir, 0o700); err != nil { return fmt.Errorf("compass: create overrides dir: %w", err) } raw := []byte("{}\n") if c.runtime != nil { body, err := c.runtime.Marshal(yaml.Parser()) if err != nil { return fmt.Errorf("compass: marshal overrides: %w", err) } raw = body } tmp := dest + ".tmp" if err := os.WriteFile(tmp, raw, 0o600); err != nil { return fmt.Errorf("compass: write overrides: %w", err) } if err := os.Rename(tmp, dest); err != nil { _ = os.Remove(tmp) return fmt.Errorf("compass: persist overrides: %w", err) } return nil } // Reload rebuilds the tree from disk and plugin sources and clears runtime Set. func (c *Config) Reload() error { if c == nil { return fmt.Errorf("compass: config is nil") } c.mu.Lock() defer c.mu.Unlock() c.runtime = koanf.New(".") if err := c.refreshSources(); err != nil { return err } return c.rebuild() } func overridesPath(dir, env string) (destDir, dest string, err error) { root, err := filepath.Abs(dir) if err != nil { return "", "", fmt.Errorf("compass: resolve config dir: %w", err) } root = filepath.Clean(root) destDir = filepath.Clean(filepath.Join(root, "env", env)) if !within(root, destDir) { return "", "", fmt.Errorf("compass: persist path escapes config directory") } dest = filepath.Clean(filepath.Join(destDir, "overrides.yaml")) if !within(destDir, dest) { return "", "", fmt.Errorf("compass: persist path escapes config directory") } return destDir, dest, nil } func within(root, path string) bool { rel, err := filepath.Rel(root, path) if err != nil { return false } return rel == "." || (rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator))) }