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}