Skip to main content

starla_common/
config.rs

1//! Configuration management
2
3use serde::{Deserialize, Serialize};
4
5/// Main probe configuration
6#[derive(Debug, Clone, Default, Serialize, Deserialize)]
7pub struct ProbeConfig {
8    #[serde(default)]
9    pub probe: ProbeSettings,
10
11    #[serde(default)]
12    pub network: NetworkSettings,
13
14    #[serde(default)]
15    pub controller: ControllerSettings,
16
17    #[serde(default)]
18    pub storage: StorageSettings,
19
20    #[serde(default)]
21    pub metrics: MetricsSettings,
22
23    #[serde(default)]
24    pub logging: LoggingSettings,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ProbeSettings {
29    /// Log level
30    #[serde(default = "default_log_level")]
31    pub log_level: String,
32}
33
34impl Default for ProbeSettings {
35    fn default() -> Self {
36        Self {
37            log_level: default_log_level(),
38        }
39    }
40}
41
42#[derive(Debug, Clone, Default, Serialize, Deserialize)]
43pub struct NetworkSettings {
44    #[serde(default)]
45    pub rxtxrpt: bool,
46
47    /// Enable the status socket for tray app communication.
48    /// Only needed on desktop systems. Default: false.
49    #[serde(default)]
50    pub status_socket: bool,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ControllerSettings {
55    #[serde(default = "default_registration_servers")]
56    pub registration_servers: Vec<String>,
57
58    #[serde(default = "default_ssh_timeout")]
59    pub ssh_timeout: u64,
60
61    #[serde(default = "default_keepalive_interval")]
62    pub keepalive_interval: u64,
63}
64
65impl Default for ControllerSettings {
66    fn default() -> Self {
67        Self {
68            registration_servers: default_registration_servers(),
69            ssh_timeout: default_ssh_timeout(),
70            keepalive_interval: default_keepalive_interval(),
71        }
72    }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct StorageSettings {
77    /// Maximum number of results to hold in the upload queue
78    #[serde(default = "default_max_queue_size")]
79    pub max_queue_size: usize,
80
81    #[serde(default = "default_retention_days")]
82    pub retention_days: u32,
83}
84
85impl Default for StorageSettings {
86    fn default() -> Self {
87        Self {
88            max_queue_size: default_max_queue_size(),
89            retention_days: default_retention_days(),
90        }
91    }
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct MetricsSettings {
96    #[serde(default = "default_metrics_enabled")]
97    pub enabled: bool,
98
99    #[serde(default = "default_metrics_listen_addr")]
100    pub listen_addr: String,
101}
102
103impl Default for MetricsSettings {
104    fn default() -> Self {
105        Self {
106            enabled: default_metrics_enabled(),
107            listen_addr: default_metrics_listen_addr(),
108        }
109    }
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct LoggingSettings {
114    #[serde(default = "default_log_format")]
115    pub format: String,
116
117    #[serde(default = "default_log_output")]
118    pub output: String,
119
120    #[serde(default = "default_max_file_size_mb")]
121    pub max_file_size_mb: u64,
122
123    #[serde(default = "default_max_files")]
124    pub max_files: u32,
125}
126
127impl Default for LoggingSettings {
128    fn default() -> Self {
129        Self {
130            format: default_log_format(),
131            output: default_log_output(),
132            max_file_size_mb: default_max_file_size_mb(),
133            max_files: default_max_files(),
134        }
135    }
136}
137
138// Default value functions
139fn default_log_level() -> String {
140    "info".to_string()
141}
142fn default_registration_servers() -> Vec<String> {
143    vec![
144        // RIPE's production registration servers since release 5130
145        // (config/probe/reg_servers.sh.prod, 2026-07-13), which replaced
146        // reg03/reg04. Hostname, IPv4, IPv6 for each, in that order.
147        "reg-05.prod.atlas.ripe.net:443".to_string(),
148        "193.0.19.75:443".to_string(),
149        "[2001:67c:2e8:11::c100:134b]:443".to_string(),
150        "reg-06.prod.atlas.ripe.net:443".to_string(),
151        "193.0.19.76:443".to_string(),
152        "[2001:67c:2e8:11::c100:134c]:443".to_string(),
153    ]
154}
155fn default_ssh_timeout() -> u64 {
156    30
157}
158fn default_keepalive_interval() -> u64 {
159    60
160}
161fn default_max_queue_size() -> usize {
162    10000
163}
164fn default_retention_days() -> u32 {
165    30
166}
167fn default_metrics_enabled() -> bool {
168    true
169}
170fn default_metrics_listen_addr() -> String {
171    "127.0.0.1:9695".to_string()
172}
173fn default_log_format() -> String {
174    "json".to_string()
175}
176fn default_log_output() -> String {
177    "stdout".to_string()
178}
179fn default_max_file_size_mb() -> u64 {
180    10
181}
182fn default_max_files() -> u32 {
183    5
184}
185
186impl ProbeConfig {
187    /// Load configuration from TOML file
188    pub fn from_file(path: &std::path::Path) -> Result<Self, crate::Error> {
189        let content = std::fs::read_to_string(path)
190            .map_err(|e| crate::Error::Config(format!("Failed to read config: {}", e)))?;
191
192        let config: ProbeConfig = toml::from_str(&content)
193            .map_err(|e| crate::Error::Config(format!("Failed to parse config: {}", e)))?;
194
195        Ok(config)
196    }
197
198    /// Save configuration to TOML file
199    pub fn to_file(&self, path: &std::path::Path) -> Result<(), crate::Error> {
200        let content = toml::to_string_pretty(self)
201            .map_err(|e| crate::Error::Config(format!("Failed to serialize config: {}", e)))?;
202
203        std::fs::write(path, content)
204            .map_err(|e| crate::Error::Config(format!("Failed to write config: {}", e)))?;
205
206        Ok(())
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_default_config() {
216        let config = ProbeConfig::default();
217        assert_eq!(config.probe.log_level, "info");
218        assert!(!config.network.rxtxrpt);
219    }
220
221    #[test]
222    fn test_config_serde() {
223        let config = ProbeConfig::default();
224        let toml_str = toml::to_string(&config).unwrap();
225        let parsed: ProbeConfig = toml::from_str(&toml_str).unwrap();
226        assert_eq!(parsed.probe.log_level, "info");
227    }
228}