use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use validator::Validate;
use zentinel_common::types::{TlsVersion, TraceIdFormat};
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ServerConfig {
#[serde(default = "default_worker_threads")]
pub worker_threads: usize,
#[serde(default = "default_max_connections")]
pub max_connections: usize,
#[serde(default = "default_graceful_shutdown_timeout")]
pub graceful_shutdown_timeout_secs: u64,
#[serde(default)]
pub daemon: bool,
pub pid_file: Option<PathBuf>,
pub user: Option<String>,
pub group: Option<String>,
pub working_directory: Option<PathBuf>,
#[serde(default)]
pub trace_id_format: TraceIdFormat,
#[serde(default)]
pub auto_reload: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ListenerConfig {
pub id: String,
#[validate(custom(function = "crate::validation::validate_socket_addr"))]
pub address: String,
pub protocol: ListenerProtocol,
pub tls: Option<TlsConfig>,
pub default_route: Option<String>,
#[serde(default = "default_request_timeout")]
pub request_timeout_secs: u64,
#[serde(default = "default_keepalive_timeout")]
pub keepalive_timeout_secs: u64,
#[serde(default = "default_max_concurrent_streams")]
pub max_concurrent_streams: u32,
#[serde(default)]
pub keepalive_max_requests: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ListenerProtocol {
Http,
Https,
#[serde(rename = "h2")]
Http2,
#[serde(rename = "h3")]
Http3,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct TlsConfig {
pub cert_file: Option<PathBuf>,
pub key_file: Option<PathBuf>,
#[serde(default)]
pub additional_certs: Vec<SniCertificate>,
pub ca_file: Option<PathBuf>,
#[serde(default = "default_min_tls_version")]
pub min_version: TlsVersion,
pub max_version: Option<TlsVersion>,
#[serde(default)]
pub cipher_suites: Vec<String>,
#[serde(default)]
pub client_auth: bool,
#[serde(default = "default_ocsp_stapling")]
pub ocsp_stapling: bool,
#[serde(default = "default_session_resumption")]
pub session_resumption: bool,
pub acme: Option<AcmeConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct AcmeConfig {
#[validate(email)]
pub email: String,
#[validate(length(min = 1, message = "at least one domain is required"))]
pub domains: Vec<String>,
#[serde(default)]
pub staging: bool,
#[serde(default = "default_acme_storage")]
pub storage: PathBuf,
#[serde(default = "default_renewal_days")]
pub renew_before_days: u32,
#[serde(default)]
pub challenge_type: AcmeChallengeType,
pub dns_provider: Option<DnsProviderConfig>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum AcmeChallengeType {
#[default]
Http01,
Dns01,
}
impl AcmeChallengeType {
pub fn is_dns01(&self) -> bool {
matches!(self, Self::Dns01)
}
pub fn is_http01(&self) -> bool {
matches!(self, Self::Http01)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DnsProviderConfig {
pub provider: DnsProviderType,
pub credentials_file: Option<PathBuf>,
pub credentials_env: Option<String>,
#[serde(default = "default_dns_api_timeout")]
pub api_timeout_secs: u64,
#[serde(default)]
pub propagation: PropagationCheckConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum DnsProviderType {
Hetzner,
Webhook {
url: String,
auth_header: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropagationCheckConfig {
#[serde(default = "default_propagation_initial_delay")]
pub initial_delay_secs: u64,
#[serde(default = "default_propagation_check_interval")]
pub check_interval_secs: u64,
#[serde(default = "default_propagation_timeout")]
pub timeout_secs: u64,
#[serde(default)]
pub nameservers: Vec<String>,
}
impl Default for PropagationCheckConfig {
fn default() -> Self {
Self {
initial_delay_secs: default_propagation_initial_delay(),
check_interval_secs: default_propagation_check_interval(),
timeout_secs: default_propagation_timeout(),
nameservers: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SniCertificate {
pub hostnames: Vec<String>,
pub priority_hostnames: Vec<String>,
pub cert_file: PathBuf,
pub key_file: PathBuf,
}
pub(crate) fn default_worker_threads() -> usize {
0
}
pub(crate) fn default_max_connections() -> usize {
10000
}
pub(crate) fn default_graceful_shutdown_timeout() -> u64 {
30
}
pub(crate) fn default_request_timeout() -> u64 {
60
}
pub(crate) fn default_keepalive_timeout() -> u64 {
75
}
pub(crate) fn default_max_concurrent_streams() -> u32 {
100
}
fn default_min_tls_version() -> TlsVersion {
TlsVersion::Tls12
}
fn default_ocsp_stapling() -> bool {
true
}
fn default_session_resumption() -> bool {
true
}
pub(crate) fn default_acme_storage() -> PathBuf {
PathBuf::from("/var/lib/zentinel/acme")
}
pub(crate) fn default_renewal_days() -> u32 {
30
}
fn default_dns_api_timeout() -> u64 {
30
}
fn default_propagation_initial_delay() -> u64 {
10
}
fn default_propagation_check_interval() -> u64 {
5
}
fn default_propagation_timeout() -> u64 {
120
}