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)]
11#[serde(rename_all = "kebab-case")]
12pub struct Ssl {
13 pub private_key: String,
17 pub certificate_chain: String,
21}
22
23#[derive(Deserialize, Serialize, Debug, Clone)]
24#[serde(tag = "transport", rename_all = "kebab-case")]
25pub enum Interface {
26 Tcp {
27 listen: SocketAddr,
28 external: SocketAddr,
37 #[serde(default = "Interface::idle_timeout")]
44 idle_timeout: u32,
45 #[serde(default)]
49 ssl: Option<Ssl>,
50 },
51 Udp {
52 listen: SocketAddr,
53 external: SocketAddr,
62 #[serde(default = "Interface::idle_timeout")]
69 idle_timeout: u32,
70 #[serde(default = "Interface::mtu")]
75 mtu: usize,
76 },
77}
78
79impl Interface {
80 fn mtu() -> usize {
81 1500
82 }
83
84 fn idle_timeout() -> u32 {
85 20
86 }
87}
88
89#[derive(Deserialize, Debug, Clone)]
90#[serde(rename_all = "kebab-case")]
91pub struct Server {
92 #[serde(default = "Server::port_range")]
96 pub port_range: PortRange,
97 #[serde(default = "Server::max_threads")]
101 pub max_threads: usize,
102 #[serde(default = "Server::realm")]
111 pub realm: String,
112 #[serde(default)]
120 pub interfaces: Vec<Interface>,
121}
122
123impl Server {
124 pub fn get_interface_addrs(&self) -> Vec<InterfaceAddr> {
125 self.interfaces
126 .iter()
127 .map(|item| match item {
128 Interface::Tcp {
129 listen, external, ..
130 } => InterfaceAddr {
131 addr: *listen,
132 external: *external,
133 transport: Transport::Tcp,
134 },
135 Interface::Udp {
136 listen, external, ..
137 } => InterfaceAddr {
138 addr: *listen,
139 external: *external,
140 transport: Transport::Udp,
141 },
142 })
143 .collect()
144 }
145}
146
147impl Server {
148 fn realm() -> String {
149 "localhost".to_string()
150 }
151
152 fn port_range() -> PortRange {
153 PortRange::default()
154 }
155
156 fn max_threads() -> usize {
157 num_cpus::get()
158 }
159}
160
161impl Default for Server {
162 fn default() -> Self {
163 Self {
164 realm: Self::realm(),
165 interfaces: Default::default(),
166 port_range: Self::port_range(),
167 max_threads: Self::max_threads(),
168 }
169 }
170}
171
172#[derive(Deserialize, Debug, Clone)]
173#[serde(rename_all = "kebab-case")]
174pub struct Hooks {
175 #[serde(default = "Hooks::max_channel_size")]
176 pub max_channel_size: usize,
177 pub endpoint: String,
178 #[serde(default)]
179 pub ssl: Option<Ssl>,
180 #[serde(default = "Hooks::timeout")]
181 pub timeout: u32,
182}
183
184impl Hooks {
185 fn max_channel_size() -> usize {
186 1024
187 }
188
189 fn timeout() -> u32 {
190 5
191 }
192}
193
194#[derive(Deserialize, Debug, Clone)]
195#[serde(rename_all = "kebab-case")]
196pub struct Api {
197 #[serde(default = "Api::bind")]
204 pub listen: SocketAddr,
205 #[serde(default)]
206 pub ssl: Option<Ssl>,
207 #[serde(default = "Api::timeout")]
208 pub timeout: u32,
209}
210
211impl Api {
212 fn bind() -> SocketAddr {
213 "127.0.0.1:3000"
214 .parse()
215 .expect("Invalid default API bind address")
216 }
217
218 fn timeout() -> u32 {
219 5
220 }
221}
222
223impl Default for Api {
224 fn default() -> Self {
225 Self {
226 timeout: Self::timeout(),
227 listen: Self::bind(),
228 ssl: None,
229 }
230 }
231}
232
233#[derive(Deserialize, Debug, Clone)]
234#[serde(rename_all = "kebab-case")]
235pub struct Prometheus {
236 #[serde(default = "Prometheus::bind")]
243 pub listen: SocketAddr,
244 #[serde(default)]
250 pub ssl: Option<Ssl>,
251}
252
253impl Prometheus {
254 fn bind() -> SocketAddr {
255 "127.0.0.1:9184"
256 .parse()
257 .expect("Invalid default Prometheus bind address")
258 }
259}
260
261impl Default for Prometheus {
262 fn default() -> Self {
263 Self {
264 listen: Self::bind(),
265 ssl: None,
266 }
267 }
268}
269
270#[derive(Deserialize, Debug, Clone, Copy)]
271#[serde(rename_all = "lowercase")]
272pub enum LogLevel {
273 Error,
274 Warn,
275 Info,
276 Debug,
277 Trace,
278}
279
280impl FromStr for LogLevel {
281 type Err = String;
282
283 fn from_str(value: &str) -> Result<Self, Self::Err> {
284 Ok(match value {
285 "trace" => Self::Trace,
286 "debug" => Self::Debug,
287 "info" => Self::Info,
288 "warn" => Self::Warn,
289 "error" => Self::Error,
290 _ => return Err(format!("unknown log level: {value}")),
291 })
292 }
293}
294
295impl Default for LogLevel {
296 fn default() -> Self {
297 Self::Info
298 }
299}
300
301impl From<LogLevel> for log::LevelFilter {
302 fn from(val: LogLevel) -> Self {
303 match val {
304 LogLevel::Error => log::LevelFilter::Error,
305 LogLevel::Debug => log::LevelFilter::Debug,
306 LogLevel::Trace => log::LevelFilter::Trace,
307 LogLevel::Warn => log::LevelFilter::Warn,
308 LogLevel::Info => log::LevelFilter::Info,
309 }
310 }
311}
312
313#[derive(Deserialize, Debug, Default, Clone)]
314#[serde(rename_all = "kebab-case")]
315pub struct Log {
316 #[serde(default)]
322 pub level: LogLevel,
323 #[serde(default = "Log::stdout")]
328 pub stdout: bool,
329 #[serde(default)]
334 pub file_directory: Option<String>,
335}
336
337impl Log {
338 fn stdout() -> bool {
339 true
340 }
341}
342
343#[derive(Deserialize, Debug, Default, Clone)]
344#[serde(rename_all = "kebab-case")]
345pub struct Auth {
346 #[serde(default)]
357 pub static_credentials: HashMap<String, String>,
358 pub static_auth_secret: Option<String>,
366 #[serde(default)]
367 pub enable_hooks_auth: bool,
368}
369
370#[derive(Deserialize, Debug, Default, Clone)]
371#[serde(rename_all = "kebab-case")]
372pub struct Config {
373 #[serde(default)]
374 pub server: Server,
375 #[serde(default)]
376 pub api: Option<Api>,
377 #[serde(default)]
378 pub prometheus: Option<Prometheus>,
379 #[serde(default)]
380 pub hooks: Option<Hooks>,
381 #[serde(default)]
382 pub log: Log,
383 #[serde(default)]
384 pub auth: Auth,
385}
386
387#[derive(Parser, Debug)]
388#[command(
389 about = env!("CARGO_PKG_DESCRIPTION"),
390 version = env!("CARGO_PKG_VERSION"),
391 author = env!("CARGO_PKG_AUTHORS"),
392)]
393struct Cli {
394 #[arg(long, short)]
400 config: String,
401}
402
403impl Config {
404 pub fn load() -> Result<Self> {
412 Ok(toml::from_str::<Self>(&read_to_string(
413 &Cli::parse().config,
414 )?)?)
415 }
416}