1use std::{collections::HashMap, fs::read_to_string, net::SocketAddr, str::FromStr};
2
3use anyhow::Result;
4use clap::Parser;
5use serde::{Deserialize, Serialize};
6
7use crate::service::{InterfaceAddr, Transport, session::ports::PortRange};
8
9#[derive(Deserialize, Serialize, Debug, Clone)]
16#[serde(rename_all = "kebab-case")]
17pub struct Ssl {
18 pub private_key: String,
22 pub certificate_chain: String,
26}
27
28#[derive(Deserialize, Serialize, Debug, Clone)]
29#[serde(tag = "transport", rename_all = "kebab-case")]
30pub enum Interface {
31 Tcp {
32 listen: SocketAddr,
33 external: SocketAddr,
42 #[serde(default = "Interface::idle_timeout")]
49 idle_timeout: u32,
50 #[serde(default)]
58 ssl: Option<Ssl>,
59 },
60 Udp {
61 listen: SocketAddr,
62 external: SocketAddr,
71 #[serde(default = "Interface::idle_timeout")]
78 idle_timeout: u32,
79 #[serde(default = "Interface::mtu")]
84 mtu: usize,
85 },
86}
87
88impl Interface {
89 fn mtu() -> usize {
90 1500
91 }
92
93 fn idle_timeout() -> u32 {
94 20
95 }
96}
97
98#[derive(Deserialize, Debug, Clone)]
99#[serde(rename_all = "kebab-case")]
100pub struct Server {
101 #[serde(default = "Server::port_range")]
105 pub port_range: PortRange,
106 #[serde(default = "Server::max_threads")]
110 pub max_threads: usize,
111 #[serde(default = "Server::realm")]
120 pub realm: String,
121 #[serde(default)]
129 pub interfaces: Vec<Interface>,
130}
131
132impl Server {
133 pub fn get_interface_addrs(&self) -> Vec<InterfaceAddr> {
134 self.interfaces
135 .iter()
136 .map(|item| match item {
137 Interface::Tcp {
138 listen, external, ..
139 } => InterfaceAddr {
140 addr: *listen,
141 external: *external,
142 transport: Transport::Tcp,
143 },
144 Interface::Udp {
145 listen, external, ..
146 } => InterfaceAddr {
147 addr: *listen,
148 external: *external,
149 transport: Transport::Udp,
150 },
151 })
152 .collect()
153 }
154}
155
156impl Server {
157 fn realm() -> String {
158 "localhost".to_string()
159 }
160
161 fn port_range() -> PortRange {
162 PortRange::default()
163 }
164
165 fn max_threads() -> usize {
166 num_cpus::get()
167 }
168}
169
170impl Default for Server {
171 fn default() -> Self {
172 Self {
173 realm: Self::realm(),
174 interfaces: Default::default(),
175 port_range: Self::port_range(),
176 max_threads: Self::max_threads(),
177 }
178 }
179}
180
181#[derive(Deserialize, Debug, Clone)]
182#[serde(rename_all = "kebab-case")]
183pub struct Hooks {
184 #[serde(default = "Hooks::max_channel_size")]
185 pub max_channel_size: usize,
186 pub endpoint: String,
187 #[serde(default)]
188 pub ssl: Option<Ssl>,
189 #[serde(default = "Hooks::timeout")]
190 pub timeout: u32,
191}
192
193impl Hooks {
194 fn max_channel_size() -> usize {
195 1024
196 }
197
198 fn timeout() -> u32 {
199 5
200 }
201}
202
203#[derive(Deserialize, Debug, Clone)]
204#[serde(rename_all = "kebab-case")]
205pub struct Api {
206 #[serde(default = "Api::bind")]
213 pub listen: SocketAddr,
214 #[serde(default)]
215 pub ssl: Option<Ssl>,
216 #[serde(default = "Api::timeout")]
217 pub timeout: u32,
218}
219
220impl Api {
221 fn bind() -> SocketAddr {
222 "127.0.0.1:3000"
223 .parse()
224 .expect("Invalid default API bind address")
225 }
226
227 fn timeout() -> u32 {
228 5
229 }
230}
231
232impl Default for Api {
233 fn default() -> Self {
234 Self {
235 timeout: Self::timeout(),
236 listen: Self::bind(),
237 ssl: None,
238 }
239 }
240}
241
242#[derive(Deserialize, Debug, Clone)]
243#[serde(rename_all = "kebab-case")]
244pub struct Prometheus {
245 #[serde(default = "Prometheus::bind")]
252 pub listen: SocketAddr,
253 #[serde(default)]
259 pub ssl: Option<Ssl>,
260}
261
262impl Prometheus {
263 fn bind() -> SocketAddr {
264 "127.0.0.1:9184"
265 .parse()
266 .expect("Invalid default Prometheus bind address")
267 }
268}
269
270impl Default for Prometheus {
271 fn default() -> Self {
272 Self {
273 listen: Self::bind(),
274 ssl: None,
275 }
276 }
277}
278
279#[derive(Deserialize, Debug, Clone, Copy)]
280#[serde(rename_all = "lowercase")]
281pub enum LogLevel {
282 Error,
283 Warn,
284 Info,
285 Debug,
286 Trace,
287}
288
289impl FromStr for LogLevel {
290 type Err = String;
291
292 fn from_str(value: &str) -> Result<Self, Self::Err> {
293 Ok(match value {
294 "trace" => Self::Trace,
295 "debug" => Self::Debug,
296 "info" => Self::Info,
297 "warn" => Self::Warn,
298 "error" => Self::Error,
299 _ => return Err(format!("unknown log level: {value}")),
300 })
301 }
302}
303
304impl Default for LogLevel {
305 fn default() -> Self {
306 Self::Info
307 }
308}
309
310impl From<LogLevel> for log::LevelFilter {
311 fn from(val: LogLevel) -> Self {
312 match val {
313 LogLevel::Error => log::LevelFilter::Error,
314 LogLevel::Debug => log::LevelFilter::Debug,
315 LogLevel::Trace => log::LevelFilter::Trace,
316 LogLevel::Warn => log::LevelFilter::Warn,
317 LogLevel::Info => log::LevelFilter::Info,
318 }
319 }
320}
321
322#[derive(Deserialize, Debug, Default, Clone)]
323#[serde(rename_all = "kebab-case")]
324pub struct Log {
325 #[serde(default)]
331 pub level: LogLevel,
332 #[serde(default = "Log::stdout")]
337 pub stdout: bool,
338 #[serde(default)]
343 pub file_directory: Option<String>,
344}
345
346impl Log {
347 fn stdout() -> bool {
348 true
349 }
350}
351
352#[derive(Deserialize, Debug, Default, Clone)]
353#[serde(rename_all = "kebab-case")]
354pub struct Auth {
355 #[serde(default)]
366 pub static_credentials: HashMap<String, String>,
367 pub static_auth_secret: Option<String>,
375 #[serde(default)]
376 pub enable_hooks_auth: bool,
377}
378
379#[derive(Deserialize, Debug, Default, Clone)]
380#[serde(rename_all = "kebab-case")]
381pub struct Config {
382 #[serde(default)]
383 pub server: Server,
384 #[serde(default)]
385 pub api: Option<Api>,
386 #[serde(default)]
387 pub prometheus: Option<Prometheus>,
388 #[serde(default)]
389 pub hooks: Option<Hooks>,
390 #[serde(default)]
391 pub log: Log,
392 #[serde(default)]
393 pub auth: Auth,
394}
395
396#[derive(Parser, Debug)]
397#[command(
398 about = env!("CARGO_PKG_DESCRIPTION"),
399 version = env!("CARGO_PKG_VERSION"),
400 author = env!("CARGO_PKG_AUTHORS"),
401)]
402struct Cli {
403 #[arg(long, short)]
409 config: String,
410}
411
412impl Config {
413 pub fn load() -> Result<Self> {
421 Ok(toml::from_str::<Self>(&read_to_string(
422 &Cli::parse().config,
423 )?)?)
424 }
425}