use anyhow::Result;
use clap::Args;
use confique::Config;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub fn system_app_dir() -> PathBuf {
#[cfg(target_os = "linux")]
{
PathBuf::from("/var/lib/witmproxy")
}
#[cfg(not(target_os = "linux"))]
{
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".witmproxy")
}
}
pub fn expand_home_in_path(path: &Path) -> Result<PathBuf> {
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let path_str = path
.to_str()
.ok_or_else(|| anyhow::anyhow!("Invalid UTF-8 in path"))?;
if path_str.contains("$HOME") {
let expanded = path_str.replace("$HOME", home_dir.to_str().unwrap_or("."));
Ok(PathBuf::from(expanded))
} else {
Ok(path.to_path_buf())
}
}
#[derive(Config, Clone, Default, Serialize, Deserialize)]
#[config(layer_attr(derive(Args, Serialize, Clone)))]
pub struct AppConfig {
#[config(nested, layer_attr(command(flatten)))]
pub proxy: ProxyConfig,
#[config(nested, layer_attr(command(flatten)))]
pub db: DbConfig,
#[config(nested, layer_attr(command(flatten)))]
pub tls: TlsConfig,
#[config(nested, layer_attr(command(flatten)))]
pub plugins: PluginConfig,
#[config(nested, layer_attr(command(flatten)))]
pub web: WebConfig,
#[config(nested, layer_attr(command(flatten)))]
pub auth: AuthConfig,
#[config(nested, layer_attr(command(flatten)))]
pub transparent: TransparentProxyConfig,
#[config(nested, layer_attr(command(flatten)))]
pub update: UpdateConfig,
}
#[derive(Clone, Config, Deserialize, Serialize, Default)]
#[config(layer_attr(derive(Args, Clone, Serialize,)))]
pub struct ProxyConfig {
#[config(env = "PROXY_BIND_ADDR", layer_attr(arg(long)))]
pub proxy_bind_addr: Option<String>,
#[config(default = "ip-mapping", layer_attr(arg(skip)))]
pub tenant_resolver: crate::proxy::tenant_resolver::TenantResolverKind,
#[config(layer_attr(arg(skip)))]
pub tenant_header: Option<String>,
}
#[derive(Clone, Config, Deserialize, Serialize, Default)]
#[config(layer_attr(derive(Args, Clone, Serialize,)))]
pub struct AuthConfig {
#[config(default = false, layer_attr(arg(skip)))]
pub enabled: bool,
#[config(layer_attr(arg(skip)))]
pub jwks_url: Option<String>,
#[config(layer_attr(arg(skip)))]
pub jwt_issuer: Option<String>,
#[config(layer_attr(arg(skip)))]
pub jwt_audience: Option<String>,
#[config(env = "AUTH_JWT_SECRET", layer_attr(arg(skip)))]
pub jwt_secret: Option<String>,
}
#[derive(Clone, Config, Deserialize, Serialize, Default)]
#[config(layer_attr(derive(Args, Clone, Serialize,)))]
pub struct TransparentProxyConfig {
#[config(default = false, layer_attr(arg(skip)))]
pub enabled: bool,
#[config(layer_attr(arg(skip)))]
pub listen_addr: Option<String>,
#[config(layer_attr(arg(skip)))]
pub interface: Option<String>,
#[config(default = true, layer_attr(arg(skip)))]
pub auto_iptables: bool,
}
#[derive(Clone, Config, Deserialize, Serialize, Default)]
#[config(layer_attr(derive(Args, Clone, Serialize,)))]
pub struct DbConfig {
#[cfg_attr(
target_os = "linux",
config(
default = "/var/lib/witmproxy/witmproxy.db",
layer_attr(arg(long, default_value = "/var/lib/witmproxy/witmproxy.db"))
)
)]
#[cfg_attr(
not(target_os = "linux"),
config(
default = "$HOME/.witmproxy/db.sqlite",
layer_attr(arg(long, default_value = "$HOME/.witmproxy/db.sqlite"))
)
)]
pub db_path: PathBuf,
#[config(env = "DB_PASSWORD", layer_attr(arg(long)))]
pub db_password: String,
}
#[derive(Clone, Config, Deserialize, Serialize, Default)]
#[config(layer_attr(derive(Args, Clone, Serialize,)))]
pub struct TlsConfig {
#[config(default = 2048, layer_attr(arg(long, default_value = "2048")))]
pub key_size: u32,
#[config(default = 1024, layer_attr(arg(long, default_value = "1024")))]
pub cache_size: usize,
#[cfg_attr(
target_os = "linux",
config(
default = "/var/lib/witmproxy/certs",
layer_attr(arg(long, default_value = "/var/lib/witmproxy/certs"))
)
)]
#[cfg_attr(
not(target_os = "linux"),
config(
default = "$HOME/.witmproxy/certs",
layer_attr(arg(long, default_value = "$HOME/.witmproxy/certs"))
)
)]
pub cert_dir: PathBuf,
}
#[derive(Clone, Config, Deserialize, Serialize, Default)]
#[config(layer_attr(derive(Args, Clone, Serialize,)))]
pub struct PluginConfig {
#[config(default = true, layer_attr(arg(long, default_value = "true")))]
pub enabled: bool,
#[config(default = 1000, layer_attr(arg(long, default_value = "1000")))]
pub timeout_ms: u64,
#[config(default = 1024, layer_attr(arg(long, default_value = "1024")))]
pub max_memory_mb: u64,
#[config(default = 1_000_000, layer_attr(arg(long, default_value = "1000000")))]
pub max_fuel: u64,
}
#[derive(Clone, Config, Deserialize, Serialize, Default)]
#[config(layer_attr(derive(Args, Clone, Serialize,)))]
pub struct WebConfig {
#[config(layer_attr(arg(long)))]
pub web_bind_addr: Option<String>,
}
#[derive(Clone, Config, Deserialize, Serialize, Default)]
#[config(layer_attr(derive(Args, Clone, Serialize,)))]
pub struct UpdateConfig {
#[config(default = true, layer_attr(arg(skip)))]
pub auto_update: bool,
#[config(default = 21600, layer_attr(arg(skip)))]
pub check_interval_seconds: u64,
#[config(default = true, layer_attr(arg(skip)))]
pub cli_update_warning: bool,
#[config(default = true, layer_attr(arg(skip)))]
pub prefer_prebuilt: bool,
}
impl AppConfig {
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
let config: AppConfig = toml::from_str(&content)?;
Ok(config)
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let content = toml::to_string_pretty(self)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn with_resolved_paths(mut self) -> Result<Self> {
self.db.db_path = expand_home_in_path(&self.db.db_path)?;
self.tls.cert_dir = expand_home_in_path(&self.tls.cert_dir)?;
Ok(self)
}
}