Skip to main content

wowlab_sentinel/
config.rs

1// #t(file: rust_hardcoded_url) config module with env var defaults for URLs
2
3use wowlab_common::{EnvLoader, ensure_query_param};
4use wowlab_parsers::parse_csv_list;
5
6#[rustfmt::skip]
7pub(crate) use secrecy::{ExposeSecret, SecretString};
8
9const DEFAULT_HTTP_PORT: u16 = 8080;
10const DEFAULT_CLEANUP_JOBS_DAYS: f64 = 14.0;
11const DEFAULT_RANKING_TOP_N: usize = 100;
12const DEFAULT_RETENTION_SNAPSHOTS_DAYS: f64 = 90.0;
13const DEFAULT_RETENTION_ROLLING_DAYS: f64 = 90.0;
14const DEFAULT_DB_ACQUIRE_TIMEOUT_SECS: u64 = 5;
15const DEFAULT_DB_IDLE_TIMEOUT_SECS: u64 = 600;
16const DEFAULT_SCHEDULER_BATCH_SIZE: i64 = 100;
17const DEFAULT_SCHEDULER_POLL_TIMEOUT_SECS: u64 = 30;
18const DEFAULT_SCHEDULER_RECONNECT_DELAY_SECS: u64 = 5;
19const DEFAULT_RECLAIM_TIMEOUT_MINUTES: i64 = 5;
20const DEFAULT_RECLAIM_MAX_ATTEMPTS: i32 = 3;
21const DEFAULT_BURST_MAX_NODES: usize = 5;
22
23#[derive(Clone, Debug)]
24pub(crate) struct Config {
25    pub database_url: SecretString,
26    pub centrifugo_url: String,
27    pub centrifugo_key: SecretString,
28    pub centrifugo_token_secret: SecretString,
29    pub http_port: u16,
30    pub deploy_webhook_secret: Option<SecretString>,
31    pub deploy_branch_filter: Vec<String>,
32    pub mcp_enabled: bool,
33    /// 6-field cron: sec min hour day month weekday.
34    pub cron_presence: String,
35    pub cron_reclaim: String,
36    pub cron_uptime: String,
37    pub cron_cleanup: String,
38    pub cron_cleanup_jobs_days: f64,
39    pub cron_rollup: String,
40    pub cron_ranking: String,
41    pub ranking_top_n: usize,
42    pub cron_integrity: String,
43    pub cron_retention: String,
44    pub cron_docs: String,
45    pub cron_queue_depth: String,
46    pub docs_base_url: String,
47    pub retention_snapshots_days: f64,
48    pub retention_rolling_days: f64,
49    pub db_acquire_timeout_secs: u64,
50    pub db_idle_timeout_secs: u64,
51    pub scheduler_batch_size: i64,
52    pub scheduler_poll_timeout_secs: u64,
53    pub scheduler_reconnect_delay_secs: u64,
54    pub reclaim_timeout_minutes: i64,
55    pub reclaim_max_attempts: i32,
56    pub cors_origins: Vec<String>,
57    pub latitude_api_key: Option<SecretString>,
58    pub latitude_base_url: String,
59    pub latitude_project: Option<String>,
60    pub latitude_site: Option<String>,
61    pub latitude_burst_tag_id: Option<String>,
62    pub burst_enabled: bool,
63    pub burst_floor_capacity: i64,
64    pub burst_per_node_throughput: i64,
65    pub burst_max_nodes: usize,
66    pub burst_node_claim_token: Option<SecretString>,
67    pub burst_ts_authkey: Option<SecretString>,
68    pub cron_burst_scheduler: String,
69}
70
71impl Config {
72    pub(crate) fn from_env() -> Result<Self, wowlab_common::EnvError> {
73        let env = EnvLoader::new("SENTINEL");
74
75        Ok(Self {
76            database_url: ensure_query_param(&env.require("DATABASE_URL")?, "sslmode", "require")
77                .into(),
78            centrifugo_url: env.require("CENTRIFUGO_URL")?,
79            centrifugo_key: env.require_secret("CENTRIFUGO_KEY")?,
80            centrifugo_token_secret: env.require_secret("CENTRIFUGO_TOKEN_SECRET")?,
81            http_port: env.parse_or("HTTP_PORT", DEFAULT_HTTP_PORT),
82            deploy_webhook_secret: env.get_secret("DEPLOY_WEBHOOK_SECRET"),
83            deploy_branch_filter: parse_csv_list(
84                &env.get_or("DEPLOY_BRANCH_FILTER", "main,master"),
85            ),
86            mcp_enabled: env.parse_bool("MCP_ENABLED", false),
87            cron_presence: env.get_or("CRON_PRESENCE", "*/30 * * * * *"),
88            cron_reclaim: env.get_or("CRON_RECLAIM", "0 * * * * *"),
89            cron_uptime: env.get_or("CRON_UPTIME", "*/30 * * * * *"),
90            cron_cleanup: env.get_or("CRON_CLEANUP", "0 0 3 * * *"),
91
92            cron_cleanup_jobs_days: env
93                .parse_or("CRON_CLEANUP_JOBS_DAYS", DEFAULT_CLEANUP_JOBS_DAYS),
94            cron_rollup: env.get_or("CRON_ROLLUP", "0 0 4 * * *"),
95            cron_ranking: env.get_or("CRON_RANKING", "0 0 * * * *"),
96            ranking_top_n: env.parse_or("RANKING_TOP_N", DEFAULT_RANKING_TOP_N),
97            cron_integrity: env.get_or("CRON_INTEGRITY", "0 30 4 * * *"),
98            cron_retention: env.get_or("CRON_RETENTION", "0 0 5 * * *"),
99            cron_docs: env.get_or("CRON_DOCS", "0 */30 * * * *"),
100            cron_queue_depth: env.get_or("CRON_QUEUE_DEPTH", "*/30 * * * * *"),
101            docs_base_url: env.get_or("DOCS_BASE_URL", "https://wowlab.gg"),
102            retention_snapshots_days: env
103                .parse_or("RETENTION_SNAPSHOTS_DAYS", DEFAULT_RETENTION_SNAPSHOTS_DAYS),
104            retention_rolling_days: env
105                .parse_or("RETENTION_ROLLING_DAYS", DEFAULT_RETENTION_ROLLING_DAYS),
106            db_acquire_timeout_secs: env
107                .parse_or("DB_ACQUIRE_TIMEOUT_SECS", DEFAULT_DB_ACQUIRE_TIMEOUT_SECS),
108            db_idle_timeout_secs: env
109                .parse_or("DB_IDLE_TIMEOUT_SECS", DEFAULT_DB_IDLE_TIMEOUT_SECS),
110            scheduler_batch_size: env
111                .parse_or("SCHEDULER_BATCH_SIZE", DEFAULT_SCHEDULER_BATCH_SIZE),
112            scheduler_poll_timeout_secs: env.parse_or(
113                "SCHEDULER_POLL_TIMEOUT_SECS",
114                DEFAULT_SCHEDULER_POLL_TIMEOUT_SECS,
115            ),
116            scheduler_reconnect_delay_secs: env.parse_or(
117                "SCHEDULER_RECONNECT_DELAY_SECS",
118                DEFAULT_SCHEDULER_RECONNECT_DELAY_SECS,
119            ),
120            reclaim_timeout_minutes: env
121                .parse_or("RECLAIM_TIMEOUT_MINUTES", DEFAULT_RECLAIM_TIMEOUT_MINUTES),
122            reclaim_max_attempts: env
123                .parse_or("RECLAIM_MAX_ATTEMPTS", DEFAULT_RECLAIM_MAX_ATTEMPTS),
124            cors_origins: parse_csv_list(&env.get_or(
125                "CORS_ORIGINS",
126                "https://wowlab.gg,https://*.wowlab.gg,http://localhost:*",
127            )),
128            latitude_api_key: env.get_secret("LATITUDE_API_KEY"),
129            latitude_base_url: env.get_or("LATITUDE_BASE_URL", "https://api.latitude.sh"),
130            latitude_project: env.get("LATITUDE_PROJECT"),
131            latitude_site: env.get("LATITUDE_SITE"),
132            latitude_burst_tag_id: env.get("LATITUDE_BURST_TAG_ID"),
133            burst_enabled: env.parse_bool("BURST_ENABLED", false),
134            burst_floor_capacity: env.parse_or("BURST_FLOOR_CAPACITY", 0),
135            burst_per_node_throughput: env.parse_or("BURST_PER_NODE_THROUGHPUT", 1),
136            burst_max_nodes: env.parse_or("BURST_MAX_NODES", DEFAULT_BURST_MAX_NODES),
137            burst_node_claim_token: env.get_secret("BURST_NODE_CLAIM_TOKEN"),
138            burst_ts_authkey: env.get_secret("BURST_TS_AUTHKEY"),
139            cron_burst_scheduler: env.get_or("CRON_BURST_SCHEDULER", "0 * * * * *"),
140        })
141    }
142
143    pub(crate) fn log(&self) {
144        tracing::info!(config = ?self, "Configuration loaded");
145    }
146}