use anyhow::Result;
use config::{Config, Environment, File};
use serde::Deserialize;
use validator::Validate;
#[derive(Debug, Clone, Deserialize, Validate)]
pub struct ProviderCfg {
#[validate(length(min = 1))]
pub kind: String,
#[validate(length(min = 1))]
pub zone: String,
#[validate(length(min = 1))]
pub record: String,
#[serde(default)]
pub alias: Option<String>,
#[serde(default)]
pub record_type: String,
#[serde(default)]
pub ttl: u32,
#[serde(default)]
pub token: String,
#[serde(default)]
pub access_key: Option<String>,
#[serde(default)]
pub access_secret: Option<String>,
#[serde(default)]
pub region: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum DetectCfg {
Http {
url: String,
#[serde(default)]
timeout: Option<u64>,
#[serde(default)]
priority: Option<u32>,
},
Interface {
iface: String,
#[serde(default)]
priority: Option<u32>,
},
Command {
cmd: String,
#[serde(default)]
timeout: Option<u64>,
#[serde(default)]
priority: Option<u32>,
},
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct SchedulerCfg {
pub cron: Option<String>,
pub concurrency: Option<usize>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AuthCfg {
pub username: String,
pub password: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct HttpCfg {
#[serde(default = "default_listen")]
pub listen: String,
#[serde(default = "default_true")]
pub intranet_only: bool,
#[serde(default = "default_secret")]
pub jwt_secret: String,
#[serde(default = "default_ttl")]
pub token_ttl_sec: u64,
#[serde(default)]
pub auth: Option<AuthCfg>,
}
fn default_listen() -> String {
"0.0.0.0:8080".to_string()
}
fn default_true() -> bool {
true
}
fn default_secret() -> String {
"dev_only_change_me".into()
}
fn default_ttl() -> u64 {
24 * 60 * 60
}
impl Default for HttpCfg {
fn default() -> Self {
Self {
listen: default_listen(),
intranet_only: true,
jwt_secret: default_secret(),
token_ttl_sec: default_ttl(),
auth: None,
}
}
}
#[derive(Debug, Deserialize)]
struct Root {
#[serde(default)]
http: Option<HttpCfg>,
#[serde(default)]
scheduler: Option<SchedulerCfg>,
#[serde(default)]
detect: Vec<DetectCfg>,
provider: Vec<ProviderCfg>,
}
#[derive(Debug, Clone)]
pub struct AppConfig {
pub http: HttpCfg,
pub scheduler: SchedulerCfg,
pub detect: Vec<DetectCfg>,
pub provider: Vec<ProviderCfg>,
}
pub fn load_config(path: &str) -> Result<AppConfig> {
use std::path::Path;
let mut builder =
Config::builder().add_source(Environment::with_prefix("DDNS").separator("__"));
if Path::new(path).exists() {
builder = builder.add_source(File::with_name(path).required(true));
} else {
tracing::info!("config file `{path}` not found; environment-only mode");
}
let root: Root = builder.build()?.try_deserialize()?;
Ok(AppConfig {
http: root.http.unwrap_or_default(),
scheduler: root.scheduler.unwrap_or_default(),
detect: root.detect,
provider: root.provider,
})
}