294 lines
9.9 KiB
Rust
294 lines
9.9 KiB
Rust
use serde::Deserialize;
|
|
use std::env;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
|
|
pub const NX9_BZOD_DATA_DIR_ENV: &str = "NX9_BZOD_DATA_DIR";
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum ConfigError {
|
|
MissingDataDir,
|
|
InvalidConfig(String),
|
|
}
|
|
|
|
impl std::fmt::Display for ConfigError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::MissingDataDir => write!(
|
|
f,
|
|
"data directory is not configured; set NX9_BZOD_DATA_DIR or use --data-dir=<path>"
|
|
),
|
|
Self::InvalidConfig(msg) => write!(f, "configuration error: {msg}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for ConfigError {}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum DataDirSource {
|
|
Cli,
|
|
Env,
|
|
ConfigFile,
|
|
}
|
|
|
|
impl std::fmt::Display for DataDirSource {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::Cli => write!(f, "CLI"),
|
|
Self::Env => write!(f, "NX9_BZOD_DATA_DIR"),
|
|
Self::ConfigFile => write!(f, "config.toml"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Config {
|
|
pub host: String,
|
|
pub port: u16,
|
|
pub data_dir: PathBuf,
|
|
pub admin_username: String,
|
|
pub bootstrap_password_sha256: String,
|
|
pub session_secret: String,
|
|
pub cookie_secure: bool,
|
|
pub data_retention_days: Option<i64>,
|
|
pub link_check_interval_mins: u64,
|
|
pub aggregation_interval_mins: u64,
|
|
pub backup_enabled: bool,
|
|
pub backup_interval_mins: u64,
|
|
pub backup_dir: PathBuf,
|
|
pub base_url: Option<String>,
|
|
}
|
|
|
|
#[derive(Deserialize, Default)]
|
|
struct TomlConfig {
|
|
host: Option<String>,
|
|
port: Option<u16>,
|
|
data_dir: Option<String>,
|
|
admin_username: Option<String>,
|
|
bootstrap_password_sha256: Option<String>,
|
|
session_secret: Option<String>,
|
|
cookie_secure: Option<bool>,
|
|
data_retention_days: Option<String>,
|
|
link_check_interval_mins: Option<u64>,
|
|
aggregation_interval_mins: Option<u64>,
|
|
backup: Option<TomlBackupConfig>,
|
|
base_url: Option<String>,
|
|
}
|
|
|
|
#[derive(Deserialize, Default)]
|
|
struct TomlBackupConfig {
|
|
enabled: Option<bool>,
|
|
interval_mins: Option<u64>,
|
|
out_dir: Option<String>,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn load() -> Result<Self, ConfigError> {
|
|
Self::load_with_cli(None::<&std::path::Path>)
|
|
}
|
|
|
|
pub fn load_with_cli<P: AsRef<std::path::Path>>(
|
|
cli_data_dir: Option<P>,
|
|
) -> Result<Self, ConfigError> {
|
|
Self::load_from_sources(cli_data_dir, true)
|
|
}
|
|
|
|
pub fn load_from_sources<P: AsRef<std::path::Path>>(
|
|
cli_data_dir: Option<P>,
|
|
load_dotenv: bool,
|
|
) -> Result<Self, ConfigError> {
|
|
// 1. Built-in defaults (no implicit data_dir default)
|
|
let mut host = "0.0.0.0".to_string();
|
|
let mut port = 8080u16;
|
|
let mut admin_username = "admin".to_string();
|
|
let mut bootstrap_password_sha256 = "".to_string();
|
|
let mut session_secret =
|
|
"bzod-default-session-secret-change-me-in-production-please-do-it".to_string();
|
|
let mut cookie_secure = true;
|
|
let mut data_retention_days = None;
|
|
let mut link_check_interval_mins = 60u64;
|
|
let mut aggregation_interval_mins = 60u64;
|
|
let mut backup_enabled = false;
|
|
let mut backup_interval_mins = 1440u64; // Default: once per day
|
|
let mut backup_dir = PathBuf::from("./backups");
|
|
let mut base_url = None;
|
|
|
|
let mut resolved_data_dir: Option<PathBuf> = None;
|
|
let mut data_dir_source: Option<DataDirSource> = None;
|
|
|
|
// 2. Load bzod.toml / config.toml if it exists
|
|
let possible_tomls = [
|
|
"bzod.toml",
|
|
"config/bzod.toml",
|
|
"config.toml",
|
|
"config/config.toml",
|
|
];
|
|
for toml_path in possible_tomls {
|
|
if let Ok(toml_content) = fs::read_to_string(toml_path) {
|
|
if let Ok(toml_config) = toml::from_str::<TomlConfig>(&toml_content) {
|
|
if let Some(h) = toml_config.host {
|
|
host = h;
|
|
}
|
|
if let Some(p) = toml_config.port {
|
|
port = p;
|
|
}
|
|
if let Some(d) = toml_config.data_dir {
|
|
if !d.trim().is_empty() {
|
|
resolved_data_dir = Some(PathBuf::from(d));
|
|
data_dir_source = Some(DataDirSource::ConfigFile);
|
|
}
|
|
}
|
|
if let Some(u) = toml_config.admin_username {
|
|
admin_username = u;
|
|
}
|
|
if let Some(s) = toml_config.bootstrap_password_sha256 {
|
|
bootstrap_password_sha256 = s;
|
|
}
|
|
if let Some(sec) = toml_config.session_secret {
|
|
session_secret = sec;
|
|
}
|
|
if let Some(c) = toml_config.cookie_secure {
|
|
cookie_secure = c;
|
|
}
|
|
if let Some(ret) = toml_config.data_retention_days {
|
|
if ret.eq_ignore_ascii_case("unlimited") {
|
|
data_retention_days = None;
|
|
} else if let Ok(parsed) = ret.parse::<i64>() {
|
|
data_retention_days = Some(parsed);
|
|
}
|
|
}
|
|
if let Some(lc) = toml_config.link_check_interval_mins {
|
|
link_check_interval_mins = lc;
|
|
}
|
|
if let Some(ag) = toml_config.aggregation_interval_mins {
|
|
aggregation_interval_mins = ag;
|
|
}
|
|
if let Some(b) = toml_config.backup {
|
|
if let Some(be) = b.enabled {
|
|
backup_enabled = be;
|
|
}
|
|
if let Some(bi) = b.interval_mins {
|
|
backup_interval_mins = bi;
|
|
}
|
|
if let Some(bo) = b.out_dir {
|
|
backup_dir = PathBuf::from(bo);
|
|
}
|
|
}
|
|
if let Some(bu) = toml_config.base_url {
|
|
base_url = Some(bu);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Load .env if present
|
|
if load_dotenv {
|
|
let _ = dotenvy::dotenv();
|
|
if fs::metadata("config/.env").is_ok() {
|
|
let _ = dotenvy::from_path("config/.env");
|
|
}
|
|
}
|
|
|
|
// 4. Load from Environment Variables (taking precedence over config file)
|
|
if let Ok(h) = env::var("HOST") {
|
|
host = h;
|
|
}
|
|
if let Ok(p_str) = env::var("PORT") {
|
|
if let Ok(p) = p_str.parse::<u16>() {
|
|
port = p;
|
|
}
|
|
}
|
|
if let Ok(d_str) = env::var(NX9_BZOD_DATA_DIR_ENV) {
|
|
if !d_str.trim().is_empty() {
|
|
resolved_data_dir = Some(PathBuf::from(d_str));
|
|
data_dir_source = Some(DataDirSource::Env);
|
|
}
|
|
}
|
|
if let Ok(u) = env::var("ADMIN_USERNAME") {
|
|
admin_username = u;
|
|
}
|
|
if let Ok(s) = env::var("BOOTSTRAP_PASSWORD_SHA256") {
|
|
bootstrap_password_sha256 = s;
|
|
}
|
|
if let Ok(sec) = env::var("SESSION_SECRET") {
|
|
session_secret = sec;
|
|
}
|
|
if let Ok(c_str) = env::var("COOKIE_SECURE") {
|
|
if let Ok(c) = c_str.parse::<bool>() {
|
|
cookie_secure = c;
|
|
}
|
|
}
|
|
if let Ok(ret_str) = env::var("DATA_RETENTION_DAYS") {
|
|
if ret_str.eq_ignore_ascii_case("unlimited") {
|
|
data_retention_days = None;
|
|
} else if let Ok(parsed) = ret_str.parse::<i64>() {
|
|
data_retention_days = Some(parsed);
|
|
}
|
|
}
|
|
if let Ok(lc_str) = env::var("LINK_CHECK_INTERVAL_MINS") {
|
|
if let Ok(lc) = lc_str.parse::<u64>() {
|
|
link_check_interval_mins = lc;
|
|
}
|
|
}
|
|
if let Ok(ag_str) = env::var("AGGREGATION_INTERVAL_MINS") {
|
|
if let Ok(ag) = ag_str.parse::<u64>() {
|
|
aggregation_interval_mins = ag;
|
|
}
|
|
}
|
|
if let Ok(be_str) = env::var("BACKUP_ENABLED") {
|
|
if let Ok(be) = be_str.parse::<bool>() {
|
|
backup_enabled = be;
|
|
}
|
|
}
|
|
if let Ok(bi_str) = env::var("BACKUP_INTERVAL_MINS") {
|
|
if let Ok(bi) = bi_str.parse::<u64>() {
|
|
backup_interval_mins = bi;
|
|
}
|
|
}
|
|
if let Ok(bo_str) = env::var("BACKUP_DIR") {
|
|
backup_dir = PathBuf::from(bo_str);
|
|
}
|
|
if let Ok(bu) = env::var("BASE_URL") {
|
|
base_url = Some(bu);
|
|
}
|
|
|
|
// 5. CLI override (highest precedence)
|
|
if let Some(cli_dir) = cli_data_dir {
|
|
let path_ref = cli_dir.as_ref();
|
|
if !path_ref.as_os_str().is_empty() {
|
|
resolved_data_dir = Some(path_ref.to_path_buf());
|
|
data_dir_source = Some(DataDirSource::Cli);
|
|
}
|
|
}
|
|
|
|
let data_dir = match resolved_data_dir {
|
|
Some(dir) => dir,
|
|
None => return Err(ConfigError::MissingDataDir),
|
|
};
|
|
|
|
if let Some(source) = data_dir_source {
|
|
tracing::info!("Data directory: {}", data_dir.display());
|
|
tracing::info!("Data directory source: {}", source);
|
|
}
|
|
|
|
Ok(Self {
|
|
host,
|
|
port,
|
|
data_dir,
|
|
admin_username,
|
|
bootstrap_password_sha256,
|
|
session_secret,
|
|
cookie_secure,
|
|
data_retention_days,
|
|
link_check_interval_mins,
|
|
aggregation_interval_mins,
|
|
backup_enabled,
|
|
backup_interval_mins,
|
|
backup_dir,
|
|
base_url,
|
|
})
|
|
}
|
|
}
|