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::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")]
74 mtu: usize,
75 },
76}
77
78impl Interface {
79 fn mtu() -> usize {
80 1500
81 }
82
83 fn idle_timeout() -> u32 {
84 20
85 }
86}
87
88#[derive(Deserialize, Debug, Clone)]
89#[serde(rename_all = "kebab-case")]
90pub struct Server {
91 #[serde(default = "Server::port_range")]
95 pub port_range: PortRange,
96 #[serde(default = "Server::max_threads")]
100 pub max_threads: usize,
101 #[serde(default = "Server::realm")]
110 pub realm: String,
111 #[serde(default)]
119 pub interfaces: Vec<Interface>,
120}
121
122impl Server {
123 pub fn get_external_addresses(&self) -> Vec<SocketAddr> {
124 self.interfaces
125 .iter()
126 .map(|item| match item {
127 Interface::Tcp { external, .. } => *external,
128 Interface::Udp { external, .. } => *external,
129 })
130 .collect()
131 }
132}
133
134impl Server {
135 fn realm() -> String {
136 "localhost".to_string()
137 }
138
139 fn port_range() -> PortRange {
140 PortRange::default()
141 }
142
143 fn max_threads() -> usize {
144 num_cpus::get()
145 }
146}
147
148impl Default for Server {
149 fn default() -> Self {
150 Self {
151 realm: Self::realm(),
152 interfaces: Default::default(),
153 port_range: Self::port_range(),
154 max_threads: Self::max_threads(),
155 }
156 }
157}
158
159#[derive(Deserialize, Debug, Clone)]
160#[serde(rename_all = "kebab-case")]
161pub struct Hooks {
162 #[serde(default = "Hooks::max_channel_size")]
163 pub max_channel_size: usize,
164 pub endpoint: String,
165 #[serde(default)]
166 pub ssl: Option<Ssl>,
167 #[serde(default = "Hooks::timeout")]
168 pub timeout: u32,
169}
170
171impl Hooks {
172 fn max_channel_size() -> usize {
173 1024
174 }
175
176 fn timeout() -> u32 {
177 5
178 }
179}
180
181#[derive(Deserialize, Debug, Clone)]
182#[serde(rename_all = "kebab-case")]
183pub struct Api {
184 #[serde(default = "Api::bind")]
191 pub listen: SocketAddr,
192 #[serde(default)]
193 pub ssl: Option<Ssl>,
194 #[serde(default = "Api::timeout")]
195 pub timeout: u32,
196}
197
198impl Api {
199 fn bind() -> SocketAddr {
200 "127.0.0.1:3000"
201 .parse()
202 .expect("Invalid default API bind address")
203 }
204
205 fn timeout() -> u32 {
206 5
207 }
208}
209
210impl Default for Api {
211 fn default() -> Self {
212 Self {
213 timeout: Self::timeout(),
214 listen: Self::bind(),
215 ssl: None,
216 }
217 }
218}
219
220#[derive(Deserialize, Debug, Clone)]
221#[serde(rename_all = "kebab-case")]
222pub struct Prometheus {
223 #[serde(default = "Prometheus::bind")]
230 pub listen: SocketAddr,
231 #[serde(default)]
237 pub ssl: Option<Ssl>,
238}
239
240impl Prometheus {
241 fn bind() -> SocketAddr {
242 "127.0.0.1:9184"
243 .parse()
244 .expect("Invalid default Prometheus bind address")
245 }
246}
247
248impl Default for Prometheus {
249fn default() -> Self {
250 Self {
251 listen: Self::bind(),
252 ssl: None,
253 }
254 }
255}
256
257#[derive(Deserialize, Debug, Clone, Copy)]
258#[serde(rename_all = "lowercase")]
259pub enum LogLevel {
260 Error,
261 Warn,
262 Info,
263 Debug,
264 Trace,
265}
266
267impl FromStr for LogLevel {
268 type Err = String;
269
270 fn from_str(value: &str) -> Result<Self, Self::Err> {
271 Ok(match value {
272 "trace" => Self::Trace,
273 "debug" => Self::Debug,
274 "info" => Self::Info,
275 "warn" => Self::Warn,
276 "error" => Self::Error,
277 _ => return Err(format!("unknown log level: {value}")),
278 })
279 }
280}
281
282impl Default for LogLevel {
283 fn default() -> Self {
284 Self::Info
285 }
286}
287
288impl From<LogLevel> for log::LevelFilter {
289 fn from(val: LogLevel) -> Self {
290 match val {
291 LogLevel::Error => log::LevelFilter::Error,
292 LogLevel::Debug => log::LevelFilter::Debug,
293 LogLevel::Trace => log::LevelFilter::Trace,
294 LogLevel::Warn => log::LevelFilter::Warn,
295 LogLevel::Info => log::LevelFilter::Info,
296 }
297 }
298}
299
300#[derive(Deserialize, Debug, Default, Clone)]
301#[serde(rename_all = "kebab-case")]
302pub struct Log {
303 #[serde(default)]
309 pub level: LogLevel,
310 #[serde(default = "Log::stdout")]
315 pub stdout: bool,
316 #[serde(default)]
321 pub file_directory: Option<String>,
322}
323
324impl Log {
325 fn stdout() -> bool {
326 true
327 }
328}
329
330#[derive(Deserialize, Debug, Default, Clone)]
331#[serde(rename_all = "kebab-case")]
332pub struct Auth {
333 #[serde(default)]
344 pub static_credentials: HashMap<String, String>,
345 pub static_auth_secret: Option<String>,
353 #[serde(default)]
354 pub enable_hooks_auth: bool,
355}
356
357#[derive(Deserialize, Debug, Default, Clone)]
358#[serde(rename_all = "kebab-case")]
359pub struct Config {
360 #[serde(default)]
361 pub server: Server,
362 #[serde(default)]
363 pub api: Option<Api>,
364 #[serde(default)]
365 pub prometheus: Option<Prometheus>,
366 #[serde(default)]
367 pub hooks: Option<Hooks>,
368 #[serde(default)]
369 pub log: Log,
370 #[serde(default)]
371 pub auth: Auth,
372}
373
374#[derive(Parser, Debug)]
375#[command(
376 about = env!("CARGO_PKG_DESCRIPTION"),
377 version = env!("CARGO_PKG_VERSION"),
378 author = env!("CARGO_PKG_AUTHORS"),
379)]
380struct Cli {
381 #[arg(long, short)]
387 config: String,
388}
389
390impl Config {
391 pub fn load() -> Result<Self> {
399 Ok(toml::from_str::<Self>(&read_to_string(
400 &Cli::parse().config,
401 )?)?)
402 }
403}