| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- // Package config is the env-driven configuration loader used by every
- // service. Twelve-factor: no flag parsing, no config files, just env
- // vars. Anything not in here has a sane default.
- //
- // Naming convention: BA_<SERVICE>_<KEY>, e.g. BA_INGESTD_HTTP_ADDR.
- package config
- import (
- "fmt"
- "os"
- "strconv"
- "strings"
- "time"
- )
- // Common is shared by every service.
- type Common struct {
- Env string // dev | staging | prod
- ServiceName string
- LogLevel string // debug | info | warn | error
- HTTPAddr string // /health + /metrics + (later) /v1/*
- // NATS
- NATSURL string // nats://nats:4222
- // Postgres
- PostgresDSN string
- // Redis
- RedisURL string // redis://redis:6379/0
- // Shutdown
- ShutdownGrace time.Duration
- }
- // Default values applied if env unset.
- func defaultCommon() Common {
- return Common{
- Env: "dev",
- ServiceName: "broad-announce",
- LogLevel: "info",
- HTTPAddr: ":8800",
- NATSURL: envOr("BA_NATS_URL", "nats://localhost:4222"),
- PostgresDSN: envOr("BA_POSTGRES_DSN", "postgres://ba:ba@localhost:5432/ba?sslmode=disable"),
- RedisURL: envOr("BA_REDIS_URL", "redis://localhost:6379/0"),
- ShutdownGrace: 15 * time.Second,
- }
- }
- // LoadCommon reads env, applies defaults, and returns a validated Common.
- func LoadCommon(serviceName string) (Common, error) {
- c := defaultCommon()
- c.ServiceName = serviceName
- if v := os.Getenv("BA_ENV"); v != "" {
- c.Env = v
- }
- if v := os.Getenv("BA_LOG_LEVEL"); v != "" {
- c.LogLevel = v
- }
- if v := os.Getenv("BA_HTTP_ADDR"); v != "" {
- c.HTTPAddr = v
- }
- if v := os.Getenv("BA_NATS_URL"); v != "" {
- c.NATSURL = v
- }
- if v := os.Getenv("BA_POSTGRES_DSN"); v != "" {
- c.PostgresDSN = v
- }
- if v := os.Getenv("BA_REDIS_URL"); v != "" {
- c.RedisURL = v
- }
- if v := os.Getenv("BA_SHUTDOWN_GRACE_SEC"); v != "" {
- n, err := strconv.Atoi(v)
- if err != nil {
- return c, fmt.Errorf("BA_SHUTDOWN_GRACE_SEC: %w", err)
- }
- c.ShutdownGrace = time.Duration(n) * time.Second
- }
- if c.Env != "dev" && c.Env != "staging" && c.Env != "prod" {
- return c, fmt.Errorf("BA_ENV must be dev|staging|prod, got %q", c.Env)
- }
- return c, nil
- }
- func envOr(k, def string) string {
- if v, ok := os.LookupEnv(k); ok && strings.TrimSpace(v) != "" {
- return v
- }
- return def
- }
- // GetInt parses an env var as int, returns def if unset.
- func GetInt(k string, def int) int {
- v := os.Getenv(k)
- if v == "" {
- return def
- }
- n, err := strconv.Atoi(v)
- if err != nil {
- return def
- }
- return n
- }
- // GetDuration parses an env var as Go duration, returns def if unset.
- func GetDuration(k string, def time.Duration) time.Duration {
- v := os.Getenv(k)
- if v == "" {
- return def
- }
- d, err := time.ParseDuration(v)
- if err != nil {
- return def
- }
- return d
- }
- // Ingestd is ingestd-specific config. Kept here so the service
- // binary has one import.
- type Ingestd struct {
- Common
- // Source protection defaults (SPEC §22). Per-source overrides
- // come from the DB and override these.
- MaxPayloadBytes int
- RateLimitPerSource int
- RateLimitPerCompany int
- MaxConcurrentPerIP int
- QuarantineHitsThreshold int
- QuarantineWindowSeconds int
- QuarantineDurationSecond int
- // DedupeTTLSeconds is the M6 sliding-window TTL for a
- // dedupe entry. Refreshed on every duplicate observation,
- // so a steady stream of duplicates keeps the window alive.
- // Default 300s (5 min) — up from 60s in M0–M5 to give
- // operators a longer window to see `×N` rollups.
- DedupeTTLSeconds int
- }
- // LoadIngestd reads ingestd-specific config.
- func LoadIngestd() (Ingestd, error) {
- c, err := LoadCommon("ingestd")
- if err != nil {
- return Ingestd{}, err
- }
- return Ingestd{
- Common: c,
- MaxPayloadBytes: GetInt("BA_INGESTD_MAX_PAYLOAD_BYTES", 256*1024),
- RateLimitPerSource: GetInt("BA_INGESTD_RATE_LIMIT_PER_SOURCE", 100),
- RateLimitPerCompany: GetInt("BA_INGESTD_RATE_LIMIT_PER_COMPANY", 10_000),
- MaxConcurrentPerIP: GetInt("BA_INGESTD_MAX_CONCURRENT_PER_IP", 64),
- DedupeTTLSeconds: GetInt("BA_INGESTD_DEDUPE_TTL_SECONDS", 300),
- QuarantineHitsThreshold: GetInt("BA_INGESTD_QUARANTINE_HITS_THRESHOLD", 100),
- QuarantineWindowSeconds: GetInt("BA_INGESTD_QUARANTINE_WINDOW_SECONDS", 60),
- QuarantineDurationSecond: GetInt("BA_INGESTD_QUARANTINE_DURATION_SECONDS", 300),
- }, nil
- }
|