Skip to main content

kipuka/config/
server.rs

1//! Server listener configuration.
2//!
3//! Supports three listener modes:
4//!
5//! 1. **TCP** — standard `host:port` binding (default).
6//! 2. **Unix socket** — path prefixed with `unix:` or starting with `/`.
7//! 3. **systemd socket activation** — file descriptor passed via `LISTEN_FDS`.
8
9use serde::Deserialize;
10
11/// `[server]` section — network listener and general server tuning.
12#[derive(Debug, Clone, Deserialize)]
13#[serde(deny_unknown_fields)]
14pub struct ServerConfig {
15    /// Listen address.
16    ///
17    /// - `"0.0.0.0:8443"` or `"[::]:8443"` for TCP.
18    /// - `"unix:/run/kipuka/kipuka.sock"` or `"/run/kipuka/kipuka.sock"` for Unix.
19    /// - `"fd:3"` for systemd socket activation (`LISTEN_FDS`).
20    ///
21    /// The `KIPUKA_LISTEN` environment variable overrides this field.
22    #[serde(default = "default_listen_addr")]
23    pub listen_addr: String,
24
25    /// TCP listen port (ignored when `listen_addr` is a Unix socket or fd).
26    ///
27    /// When set, overrides the port portion of `listen_addr`.
28    /// Useful for separating the bind address from the port in deployment configs.
29    pub listen_port: Option<u16>,
30
31    /// Unix socket path (alternative to embedding it in `listen_addr`).
32    ///
33    /// When set, the server listens on this Unix domain socket instead of TCP.
34    /// Mutually exclusive with `listen_port`.
35    pub unix_socket: Option<String>,
36
37    /// Maximum HTTP request body size in bytes.
38    ///
39    /// EST CSR payloads (PKCS#10) are typically 1–4 KB; Full CMC requests
40    /// can be larger.  Default: 65536 (64 KiB).
41    #[serde(default = "default_max_body_size")]
42    pub max_body_size: usize,
43
44    /// Number of tokio worker threads.
45    ///
46    /// `0` (the default) uses `num_cpus` threads.
47    #[serde(default)]
48    pub worker_threads: usize,
49
50    /// Graceful shutdown timeout in seconds.
51    ///
52    /// After receiving SIGTERM/SIGINT, the server waits this long for
53    /// in-flight requests to complete before forcing shutdown.
54    /// Default: 30 seconds.
55    #[serde(default = "default_shutdown_timeout_secs")]
56    pub shutdown_timeout_secs: u64,
57}
58
59fn default_listen_addr() -> String {
60    "0.0.0.0:8443".to_string()
61}
62
63fn default_max_body_size() -> usize {
64    65536
65}
66
67fn default_shutdown_timeout_secs() -> u64 {
68    30
69}
70
71impl Default for ServerConfig {
72    fn default() -> Self {
73        Self {
74            listen_addr: default_listen_addr(),
75            listen_port: None,
76            unix_socket: None,
77            max_body_size: default_max_body_size(),
78            worker_threads: 0,
79            shutdown_timeout_secs: default_shutdown_timeout_secs(),
80        }
81    }
82}
83
84impl ServerConfig {
85    /// Resolve the effective listen target, accounting for overrides.
86    ///
87    /// Priority: `unix_socket` > env `KIPUKA_LISTEN` > `listen_addr`.
88    pub fn effective_listen_addr(&self) -> String {
89        if let Some(ref sock) = self.unix_socket {
90            return format!("unix:{sock}");
91        }
92        if let Ok(env_addr) = std::env::var("KIPUKA_LISTEN") {
93            return env_addr;
94        }
95        if let Some(port) = self.listen_port {
96            // Replace port in listen_addr
97            if let Some(colon) = self.listen_addr.rfind(':') {
98                return format!("{}:{port}", &self.listen_addr[..colon]);
99            }
100        }
101        self.listen_addr.clone()
102    }
103
104    /// Returns `true` when the effective listen target is a Unix domain socket.
105    pub fn is_unix_socket(&self) -> bool {
106        let addr = self.effective_listen_addr();
107        addr.starts_with("unix:") || addr.starts_with('/')
108    }
109
110    /// Returns `true` when the effective listen target uses systemd fd passing.
111    pub fn is_systemd_fd(&self) -> bool {
112        self.effective_listen_addr().starts_with("fd:")
113    }
114}