package config import ( "os" "strconv" "strings" "time" ) type Config struct { ServiceAddr string PostgresURL string ClickHouseURL string ClickHouseDB string ClickHouseUser string ClickHousePassword string KafkaBrokers []string KafkaTopic string KafkaGroup string EnrollmentKey string TenantID string MaxBodyBytes int64 MaxBatchEvents int RawArchiveEnabled bool RawSpoolDir string RawRetention time.Duration DetectorInterval time.Duration DetectorLookback time.Duration UIQueryLimit int UIUsername string UIPassword string RulesDir string GrafanaPort string GrafanaPublicURL string } func Load() Config { return Config{ ServiceAddr: env("SERVICE_ADDR", ":8080"), PostgresURL: env("POSTGRES_URL", "postgres://siem:siem@postgres:5432/siem?sslmode=disable"), ClickHouseURL: strings.TrimRight(env("CLICKHOUSE_URL", "http://clickhouse:8123"), "/"), ClickHouseDB: env("CLICKHOUSE_DB", "siem"), ClickHouseUser: env("CLICKHOUSE_USER", "siem"), ClickHousePassword: env("CLICKHOUSE_PASSWORD", "siem"), KafkaBrokers: splitCSV(env("KAFKA_BROKERS", "redpanda:9092")), KafkaTopic: env("KAFKA_TOPIC", "siem-events"), KafkaGroup: env("KAFKA_GROUP", "siem-processor-v1"), EnrollmentKey: env("ENROLLMENT_KEY", "change-me"), TenantID: env("TENANT_ID", "default"), MaxBodyBytes: int64(envInt("MAX_BODY_BYTES", 8*1024*1024)), MaxBatchEvents: envInt("MAX_BATCH_EVENTS", 1000), RawArchiveEnabled: envBool("RAW_ARCHIVE_ENABLED", true), RawSpoolDir: env("RAW_SPOOL_DIR", "/var/spool/siem-raw"), RawRetention: envDuration("RAW_RETENTION", 30*24*time.Hour), DetectorInterval: envDuration("DETECTOR_INTERVAL", 30*time.Second), DetectorLookback: envDuration("DETECTOR_LOOKBACK", 20*time.Minute), UIQueryLimit: envInt("UI_QUERY_LIMIT", 500), UIUsername: env("UI_USERNAME", "admin"), UIPassword: env("UI_PASSWORD", "change-me"), RulesDir: env("RULES_DIR", "/app/rules"), GrafanaPort: env("GRAFANA_PORT", "3000"), GrafanaPublicURL: env("GRAFANA_PUBLIC_URL", ""), } } func env(k, d string) string { if v := strings.TrimSpace(os.Getenv(k)); v != "" { return v } return d } func envInt(k string, d int) int { v := strings.TrimSpace(os.Getenv(k)) if v == "" { return d } n, e := strconv.Atoi(v) if e != nil { return d } return n } func envBool(k string, d bool) bool { v := strings.ToLower(strings.TrimSpace(os.Getenv(k))) if v == "" { return d } return v == "1" || v == "true" || v == "yes" || v == "on" } func envDuration(k string, d time.Duration) time.Duration { v := strings.TrimSpace(os.Getenv(k)) if v == "" { return d } x, e := time.ParseDuration(v) if e != nil { return d } return x } func splitCSV(v string) []string { var out []string for _, p := range strings.Split(v, ",") { if p = strings.TrimSpace(p); p != "" { out = append(out, p) } } return out }