Menu
Public repository / master
AkurAI-Monitor / src/config.rs
Latest change 9a35bd9c668fae6e3383420387d1a0b1b4b3f524 - Read-only bearer token and host filter on /api/status by Ólafur Búi Ólafsson
use std::sync::LazyLock;
pub struct Config {
pub listen_addr: String,
pub db_url: String,
pub static_dir: String,
pub alert_log: String,
pub log_files: Vec<String>,
pub interval_secs: u64,
pub retention_days: i64,
pub log_retention_days: i64,
pub log_debug_retention_days: i64,
pub log_max_rows: i64,
pub log_stale_secs: i64,
/// Open alerts whose metric has had no sample for this long auto-resolve.
pub alert_stale_secs: i64,
pub journal_replay_secs: i64,
pub oidc_issuer: String,
pub oidc_client_id: String,
pub oidc_client_secret: String,
pub oidc_redirect_uri: String,
pub ingest_token: String,
pub api_token: String,
/// Read-only bearer for GET on status/metrics/alerts/hosts (widgets, dashboards).
pub read_token: String,
pub journal_units: Vec<String>,
pub journal_match: Vec<String>,
pub systemd_match: Vec<String>,
pub proc_top_n: usize,
}
/// Default: five collection intervals, long enough to ride out a missed
/// sample or two but short enough that a vanished metric doesn't stay CRIT.
fn alert_stale_secs(raw: Option<&str>, interval_secs: u64) -> i64 {
raw.and_then(|value| value.trim().parse().ok())
.unwrap_or(5 * interval_secs as i64)
}
fn cli_overrides(
args: impl Iterator<Item = String>,
) -> (Option<String>, Option<String>, Option<String>) {
let mut static_dir = None;
let mut host = None;
let mut port = None;
let mut args = args.peekable();
while let Some(arg) = args.next() {
let target = match arg.as_str() {
"--dir" => &mut static_dir,
"--host" => &mut host,
"--port" => &mut port,
_ => continue,
};
*target = args.next();
}
(static_dir, host, port)
}
static CONFIG: LazyLock<Config> = LazyLock::new(|| {
let (static_dir, host, port) = cli_overrides(std::env::args().skip(1));
let listen_addr = match (host, port) {
(Some(host), Some(port)) => format!("{host}:{port}"),
_ => std::env::var("MONITOR_LISTEN").unwrap_or_else(|_| "127.0.0.1:8800".to_string()),
};
let log_files_str = std::env::var("MONITOR_LOG_FILES")
.unwrap_or_else(|_| "/var/log/syslog,/var/log/auth.log".to_string());
let log_files: Vec<String> = log_files_str
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
let base_url = std::env::var("MONITOR_BASE_URL")
.unwrap_or_else(|_| "https://monitor.olibuijr.com".to_string())
.trim_end_matches('/')
.to_string();
let redirect_uri = std::env::var("MONITOR_OIDC_REDIRECT_URI")
.unwrap_or_else(|_| format!("{base_url}/auth/callback"));
let journal_match: Vec<String> = std::env::var("MONITOR_JOURNAL_MATCH")
.unwrap_or_else(|_| "akurai-,golfsetrid,rust-monitor".to_string())
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
let interval_secs = std::env::var("MONITOR_INTERVAL")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(60);
Config {
listen_addr,
db_url: std::env::var("MONITOR_DB_URL")
.unwrap_or_else(|_| "mysql://monitor@127.0.0.1:3306/akurai_monitor".to_string()),
static_dir: static_dir
.or_else(|| std::env::var("MONITOR_STATIC_DIR").ok())
.unwrap_or_else(|| "./ui".to_string()),
alert_log: std::env::var("MONITOR_ALERT_LOG")
.unwrap_or_else(|_| "./data/alerts.log".to_string()),
log_files,
interval_secs,
retention_days: std::env::var("MONITOR_RETENTION_DAYS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(30),
log_retention_days: std::env::var("MONITOR_LOG_RETENTION_DAYS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(7),
log_debug_retention_days: std::env::var("MONITOR_LOG_DEBUG_RETENTION_DAYS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(2),
log_max_rows: std::env::var("MONITOR_LOG_MAX_ROWS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(2_000_000),
log_stale_secs: std::env::var("MONITOR_LOG_STALE_SECS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(900),
alert_stale_secs: alert_stale_secs(
std::env::var("MONITOR_ALERT_STALE_SECS").ok().as_deref(),
interval_secs,
),
journal_replay_secs: std::env::var("MONITOR_JOURNAL_REPLAY_SECS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(300),
oidc_issuer: std::env::var("MONITOR_OIDC_ISSUER")
.unwrap_or_else(|_| "https://auth.olibuijr.com".to_string()),
oidc_client_id: std::env::var("MONITOR_OIDC_CLIENT_ID").unwrap_or_default(),
oidc_client_secret: std::env::var("MONITOR_OIDC_CLIENT_SECRET").unwrap_or_default(),
oidc_redirect_uri: redirect_uri,
ingest_token: std::env::var("MONITOR_INGEST_TOKEN").unwrap_or_default(),
api_token: std::env::var("MONITOR_API_TOKEN").unwrap_or_default(),
read_token: std::env::var("MONITOR_READ_TOKEN").unwrap_or_default(),
journal_units: std::env::var("MONITOR_JOURNAL_UNITS")
.unwrap_or_default()
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect(),
journal_match: journal_match.clone(),
systemd_match: std::env::var("MONITOR_SYSTEMD_MATCH")
.ok()
.map(|raw| {
raw.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
})
.unwrap_or(journal_match),
proc_top_n: std::env::var("MONITOR_PROC_TOP_N")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(15),
}
});
pub fn get() -> &'static Config {
&CONFIG
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alert_stale_window_defaults_to_five_collection_intervals() {
assert_eq!(alert_stale_secs(None, 60), 300);
assert_eq!(alert_stale_secs(Some("not-a-number"), 30), 150);
assert_eq!(alert_stale_secs(Some("900"), 60), 900);
}
#[test]
fn deployment_cli_overrides_static_directory_and_listener() {
let args = [
"serve",
"--dir",
"/opt/rust-monitor/app",
"--host",
"127.0.0.1",
"--port",
"8800",
"--name",
"rust-monitor",
]
.into_iter()
.map(str::to_owned);
assert_eq!(
cli_overrides(args),
(
Some("/opt/rust-monitor/app".to_string()),
Some("127.0.0.1".to_string()),
Some("8800".to_string())
)
);
}
}