use std::path::{Path, PathBuf};
use std::time::Duration;
use config::{ConfigError, File};
use serde::Deserialize;
use tokio_listener::ListenerAddress;
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Config {
pub listener: ListenerAddress,
pub storage_dir: PathBuf,
#[serde(deserialize_with = "deserialize_duration_secs")]
pub timeout: Duration,
pub v1: Option<V1Config>,
#[cfg(feature = "telemetry")]
pub telemetry: Option<TelemetryConfig>,
#[cfg(feature = "acme")]
pub acme: Option<AcmeConfig>,
#[cfg(feature = "access-control")]
pub access_control: Option<AccessControlConfig>,
}
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct V1Config {
#[cfg(feature = "access-control")]
pub blocked_addresses_path: Option<PathBuf>,
#[cfg(feature = "access-control")]
pub blocked_addresses_url: Option<String>,
#[cfg(feature = "access-control")]
pub blocked_addresses_refresh_secs: Option<u64>,
}
#[cfg(feature = "telemetry")]
#[derive(Debug, Clone, Deserialize)]
pub struct TelemetryConfig {
pub endpoint: String,
pub auth_token: String,
pub operator_domain: String,
}
#[cfg(feature = "acme")]
#[derive(Debug, Clone, Deserialize)]
pub struct AcmeConfig {
pub domains: Vec<String>,
pub contact: Vec<String>,
#[serde(default)]
pub directory_url: Option<String>,
}
#[cfg(feature = "access-control")]
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct AccessControlConfig {
pub geo_db_path: Option<PathBuf>,
pub blocked_regions: Vec<String>,
pub blocked_ips: Vec<String>,
}
#[cfg(feature = "acme")]
impl AcmeConfig {
pub fn into_rustls_config(
self,
storage_dir: &Path,
) -> tokio_rustls_acme::AcmeConfig<std::io::Error, std::io::Error> {
let cache_dir = storage_dir.join("acme");
let config = tokio_rustls_acme::AcmeConfig::new(self.domains)
.contact(self.contact)
.cache(tokio_rustls_acme::caches::DirCache::new(cache_dir));
match self.directory_url {
Some(url) => config.directory(url),
None => config.directory_lets_encrypt(true),
}
}
}
impl Default for Config {
fn default() -> Self {
Self {
listener: "[::]:8080".parse().expect("valid default listener address"),
storage_dir: PathBuf::from("./data"),
timeout: Duration::from_secs(30),
v1: None,
#[cfg(feature = "telemetry")]
telemetry: None,
#[cfg(feature = "acme")]
acme: None,
#[cfg(feature = "access-control")]
access_control: None,
}
}
}
fn deserialize_duration_secs<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: serde::Deserializer<'de>,
{
let secs = u64::deserialize(deserializer)?;
Ok(Duration::from_secs(secs))
}
impl Config {
pub fn new(
listener: ListenerAddress,
storage_dir: PathBuf,
timeout: Duration,
v1: Option<V1Config>,
) -> Self {
Self {
listener,
storage_dir,
timeout,
v1,
#[cfg(feature = "telemetry")]
telemetry: None,
#[cfg(feature = "acme")]
acme: None,
#[cfg(feature = "access-control")]
access_control: None,
}
}
pub fn from_file(path: &Path) -> Result<Self, ConfigError> {
config::Config::builder()
.add_source(File::from(path).required(false))
.add_source(
config::Environment::with_prefix("PJ")
.separator("__")
.prefix_separator("_")
.list_separator(",")
.with_list_parse_key("acme.domains")
.with_list_parse_key("acme.contact")
.with_list_parse_key("access_control.blocked_regions")
.with_list_parse_key("access_control.blocked_ips")
.try_parsing(true),
)
.build()?
.try_deserialize()
}
}