// Package config holds the indexer's YAML configuration. // // It lives in its own package (rather than package main) so that both the // indexer CLI and the ffd-metrics daemon can parse the same configuration // file shape. package config import ( "fmt" "os" "strings" ) // Forge is a single instance to scrape: its bare hostname (FQDN) and its // forge type. The URL is used as the forge's unique identity (the HTTP scheme // is added internally when a connection is needed; a scheme prefix in the URL // is accepted to force the connection protocol, but the stored identity is // always the bare hostname). Type selects the scraper: a pinned "forgejo" or // "gitea" is recommended and skips probing; "auto" (the default, still // experimental) runs the detector at scrape time so a backend change (e.g. a // migration from Gitea to Forgejo) is picked up automatically. Only supported // types are accepted. type Forge struct { URL string `yaml:"url"` Type string `yaml:"type"` } // Forges is the list of forge instances to scrape. type Forges []Forge // TypesenseConfig holds the connection settings to the search engine. // ApiKey may be inlined or supplied via ApiKeyFile (a path to a file // containing the key, e.g. a sops/agenix secret). If both are set, the file // takes precedence so secrets need not live in the config file. type TypesenseConfig struct { Host string `yaml:"host"` ApiKey string `yaml:"api_key"` ApiKeyFile string `yaml:"api_key_file"` } // MetricsConfig configures the Prometheus metrics daemon. type MetricsConfig struct { // Listen is the address the ffd-metrics daemon binds and serves // /metrics on, e.g. "127.0.0.1:9090". Listen string `yaml:"listen"` } // Config is the top-level configuration file. type Config struct { DbPath string `yaml:"db_path"` Typesense TypesenseConfig `yaml:"typesense"` Metrics MetricsConfig `yaml:"metrics"` Forges Forges `yaml:"forges"` } // Supported forge type values accepted in the config. "auto" (the default) // triggers detection at scrape time; the rest pin a specific scraper. const ( ForgeTypeAuto = "auto" ForgeTypeForgejo = "forgejo" ForgeTypeGitea = "gitea" ) // SupportedForgeTypes are the accepted values for Forge.Type, including the // sentinel "auto" that triggers detection at scrape time. var SupportedForgeTypes = map[string]bool{ "": true, // default to "auto" ForgeTypeAuto: true, ForgeTypeForgejo: true, ForgeTypeGitea: true, } // Validate normalizes a Forge's Type (empty means "auto") and rejects // unsupported values. func (f *Forge) Validate() error { if f.URL == "" { return fmt.Errorf("forge entry missing url") } if f.Type == "" { f.Type = ForgeTypeAuto } if !SupportedForgeTypes[f.Type] { return fmt.Errorf("unsupported forge type %q for %s (want %s, %s or %s)", f.Type, f.URL, ForgeTypeAuto, ForgeTypeForgejo, ForgeTypeGitea) } return nil } // ResolveAPIKey returns the effective Typesense API key. If ApiKeyFile is // set it is read (taking precedence over the inline ApiKey), and its contents // are trimmed of surrounding whitespace/newline so a plain `cat` secret file // works. An empty key (from either source) is returned as "" for the caller // to treat as "not configured". func (tc TypesenseConfig) ResolveAPIKey() (string, error) { if tc.ApiKeyFile == "" { return tc.ApiKey, nil } b, err := os.ReadFile(tc.ApiKeyFile) if err != nil { return "", fmt.Errorf("read typesense api_key_file %q: %w", tc.ApiKeyFile, err) } return strings.TrimSpace(string(b)), nil }