Skip to main content

rusmes_config/
env_overrides.rs

1//! Environment variable override logic for [`crate::ServerConfig`].
2//!
3//! All `RUSMES_*` variable handling lives here, isolated from the struct
4//! definitions in `listeners.rs` and `runtime.rs`.
5
6use crate::listeners::{
7    ConnectionLimitsConfig, ImapServerConfig, JmapServerConfig, RateLimitConfig,
8};
9use crate::parse::{
10    default_idle_timeout, default_max_connections_per_ip, default_max_total_connections,
11    default_reaper_interval,
12};
13use crate::runtime::{LoggingConfig, MetricsConfig, OtlpProtocol, QueueConfig, TracingConfig};
14use crate::ServerConfig;
15
16impl ServerConfig {
17    /// Apply environment variable overrides to configuration.
18    ///
19    /// Environment variables follow the convention `RUSMES_SECTION_KEY`.
20    /// Priority: env vars > config file > defaults.
21    ///
22    /// Supported environment variables:
23    /// - `RUSMES_DOMAIN`
24    /// - `RUSMES_POSTMASTER`
25    /// - `RUSMES_SMTP_HOST`
26    /// - `RUSMES_SMTP_PORT`
27    /// - `RUSMES_SMTP_TLS_PORT`
28    /// - `RUSMES_SMTP_MAX_MESSAGE_SIZE`
29    /// - `RUSMES_SMTP_REQUIRE_AUTH`
30    /// - `RUSMES_SMTP_ENABLE_STARTTLS`
31    /// - `RUSMES_SMTP_RATE_LIMIT_MAX_CONNECTIONS_PER_IP`
32    /// - `RUSMES_SMTP_RATE_LIMIT_MAX_MESSAGES_PER_HOUR`
33    /// - `RUSMES_SMTP_RATE_LIMIT_WINDOW_DURATION`
34    /// - `RUSMES_IMAP_HOST`
35    /// - `RUSMES_IMAP_PORT`
36    /// - `RUSMES_IMAP_TLS_PORT`
37    /// - `RUSMES_JMAP_HOST`
38    /// - `RUSMES_JMAP_PORT`
39    /// - `RUSMES_JMAP_BASE_URL`
40    /// - `RUSMES_STORAGE_PATH` (for filesystem backend)
41    /// - `RUSMES_LOG_LEVEL`
42    /// - `RUSMES_LOG_FORMAT`
43    /// - `RUSMES_LOG_OUTPUT`
44    /// - `RUSMES_QUEUE_INITIAL_DELAY`
45    /// - `RUSMES_QUEUE_MAX_DELAY`
46    /// - `RUSMES_QUEUE_BACKOFF_MULTIPLIER`
47    /// - `RUSMES_QUEUE_MAX_ATTEMPTS`
48    /// - `RUSMES_QUEUE_WORKER_THREADS`
49    /// - `RUSMES_QUEUE_BATCH_SIZE`
50    /// - `RUSMES_METRICS_ENABLED`
51    /// - `RUSMES_METRICS_BIND_ADDRESS`
52    /// - `RUSMES_METRICS_PATH`
53    /// - `RUSMES_TRACING_ENABLED`
54    /// - `RUSMES_TRACING_ENDPOINT`
55    /// - `RUSMES_TRACING_PROTOCOL` (grpc or http)
56    /// - `RUSMES_TRACING_SERVICE_NAME`
57    /// - `RUSMES_TRACING_SAMPLE_RATIO`
58    /// - `RUSMES_CONNECTION_LIMITS_MAX_CONNECTIONS_PER_IP`
59    /// - `RUSMES_CONNECTION_LIMITS_MAX_TOTAL_CONNECTIONS`
60    /// - `RUSMES_CONNECTION_LIMITS_IDLE_TIMEOUT`
61    /// - `RUSMES_CONNECTION_LIMITS_REAPER_INTERVAL`
62    pub fn apply_env_overrides(&mut self) {
63        // Top-level fields
64        if let Ok(val) = std::env::var("RUSMES_DOMAIN") {
65            self.domain = val;
66        }
67        if let Ok(val) = std::env::var("RUSMES_POSTMASTER") {
68            self.postmaster = val;
69        }
70
71        // SMTP configuration
72        if let Ok(val) = std::env::var("RUSMES_SMTP_HOST") {
73            self.smtp.host = val;
74        }
75        if let Ok(val) = std::env::var("RUSMES_SMTP_PORT") {
76            if let Ok(port) = val.parse::<u16>() {
77                self.smtp.port = port;
78            }
79        }
80        if let Ok(val) = std::env::var("RUSMES_SMTP_TLS_PORT") {
81            if let Ok(port) = val.parse::<u16>() {
82                self.smtp.tls_port = Some(port);
83            }
84        }
85        if let Ok(val) = std::env::var("RUSMES_SMTP_MAX_MESSAGE_SIZE") {
86            self.smtp.max_message_size = val;
87        }
88        if let Ok(val) = std::env::var("RUSMES_SMTP_REQUIRE_AUTH") {
89            if let Ok(b) = val.parse::<bool>() {
90                self.smtp.require_auth = b;
91            }
92        }
93        if let Ok(val) = std::env::var("RUSMES_SMTP_ENABLE_STARTTLS") {
94            if let Ok(b) = val.parse::<bool>() {
95                self.smtp.enable_starttls = b;
96            }
97        }
98
99        // SMTP rate limit configuration
100        let has_rate_limit_max =
101            std::env::var("RUSMES_SMTP_RATE_LIMIT_MAX_MESSAGES_PER_HOUR").is_ok();
102        let has_rate_limit_window = std::env::var("RUSMES_SMTP_RATE_LIMIT_WINDOW_DURATION").is_ok();
103        let has_rate_limit_max_conn =
104            std::env::var("RUSMES_SMTP_RATE_LIMIT_MAX_CONNECTIONS_PER_IP").is_ok();
105
106        if has_rate_limit_max || has_rate_limit_window || has_rate_limit_max_conn {
107            // Create rate limit config if it doesn't exist
108            if self.smtp.rate_limit.is_none() {
109                self.smtp.rate_limit = Some(RateLimitConfig {
110                    max_connections_per_ip: 10,
111                    max_messages_per_hour: 100,
112                    window_duration: "1h".to_string(),
113                });
114            }
115
116            if let Some(ref mut rate_limit) = self.smtp.rate_limit {
117                if let Ok(val) = std::env::var("RUSMES_SMTP_RATE_LIMIT_MAX_CONNECTIONS_PER_IP") {
118                    if let Ok(n) = val.parse::<usize>() {
119                        rate_limit.max_connections_per_ip = n;
120                    }
121                }
122                if let Ok(val) = std::env::var("RUSMES_SMTP_RATE_LIMIT_MAX_MESSAGES_PER_HOUR") {
123                    if let Ok(n) = val.parse::<u32>() {
124                        rate_limit.max_messages_per_hour = n;
125                    }
126                }
127                if let Ok(val) = std::env::var("RUSMES_SMTP_RATE_LIMIT_WINDOW_DURATION") {
128                    rate_limit.window_duration = val;
129                }
130            }
131        }
132
133        // IMAP configuration
134        if let Ok(val) = std::env::var("RUSMES_IMAP_HOST") {
135            if self.imap.is_none() {
136                self.imap = Some(ImapServerConfig {
137                    host: "0.0.0.0".to_string(),
138                    port: 143,
139                    tls_port: None,
140                });
141            }
142            if let Some(ref mut imap) = self.imap {
143                imap.host = val;
144            }
145        }
146        if let Ok(val) = std::env::var("RUSMES_IMAP_PORT") {
147            if let Ok(port) = val.parse::<u16>() {
148                if self.imap.is_none() {
149                    self.imap = Some(ImapServerConfig {
150                        host: "0.0.0.0".to_string(),
151                        port,
152                        tls_port: None,
153                    });
154                } else if let Some(ref mut imap) = self.imap {
155                    imap.port = port;
156                }
157            }
158        }
159        if let Ok(val) = std::env::var("RUSMES_IMAP_TLS_PORT") {
160            if let Ok(port) = val.parse::<u16>() {
161                if self.imap.is_none() {
162                    self.imap = Some(ImapServerConfig {
163                        host: "0.0.0.0".to_string(),
164                        port: 143,
165                        tls_port: Some(port),
166                    });
167                } else if let Some(ref mut imap) = self.imap {
168                    imap.tls_port = Some(port);
169                }
170            }
171        }
172
173        // JMAP configuration
174        if let Ok(val) = std::env::var("RUSMES_JMAP_HOST") {
175            if self.jmap.is_none() {
176                self.jmap = Some(JmapServerConfig {
177                    host: "0.0.0.0".to_string(),
178                    port: 8080,
179                    base_url: "http://localhost:8080".to_string(),
180                    push: None,
181                });
182            }
183            if let Some(ref mut jmap) = self.jmap {
184                jmap.host = val;
185            }
186        }
187        if let Ok(val) = std::env::var("RUSMES_JMAP_PORT") {
188            if let Ok(port) = val.parse::<u16>() {
189                if self.jmap.is_none() {
190                    self.jmap = Some(JmapServerConfig {
191                        host: "0.0.0.0".to_string(),
192                        port,
193                        base_url: "http://localhost:8080".to_string(),
194                        push: None,
195                    });
196                } else if let Some(ref mut jmap) = self.jmap {
197                    jmap.port = port;
198                }
199            }
200        }
201        if let Ok(val) = std::env::var("RUSMES_JMAP_BASE_URL") {
202            if self.jmap.is_none() {
203                self.jmap = Some(JmapServerConfig {
204                    host: "0.0.0.0".to_string(),
205                    port: 8080,
206                    base_url: val,
207                    push: None,
208                });
209            } else if let Some(ref mut jmap) = self.jmap {
210                jmap.base_url = val;
211            }
212        }
213
214        // Storage configuration (only filesystem backend path)
215        if let Ok(val) = std::env::var("RUSMES_STORAGE_PATH") {
216            if let crate::StorageConfig::Filesystem { ref mut path } = self.storage {
217                *path = val;
218            }
219        }
220
221        // Logging configuration
222        if let Ok(val) = std::env::var("RUSMES_LOG_LEVEL") {
223            if self.logging.is_none() {
224                self.logging = Some(LoggingConfig {
225                    level: val,
226                    format: "text".to_string(),
227                    output: "stdout".to_string(),
228                    file: None,
229                });
230            } else if let Some(ref mut logging) = self.logging {
231                logging.level = val;
232            }
233        }
234        if let Ok(val) = std::env::var("RUSMES_LOG_FORMAT") {
235            if self.logging.is_none() {
236                self.logging = Some(LoggingConfig {
237                    level: "info".to_string(),
238                    format: val,
239                    output: "stdout".to_string(),
240                    file: None,
241                });
242            } else if let Some(ref mut logging) = self.logging {
243                logging.format = val;
244            }
245        }
246        if let Ok(val) = std::env::var("RUSMES_LOG_OUTPUT") {
247            if self.logging.is_none() {
248                self.logging = Some(LoggingConfig {
249                    level: "info".to_string(),
250                    format: "text".to_string(),
251                    output: val,
252                    file: None,
253                });
254            } else if let Some(ref mut logging) = self.logging {
255                logging.output = val;
256            }
257        }
258
259        // Queue configuration
260        if let Ok(val) = std::env::var("RUSMES_QUEUE_INITIAL_DELAY") {
261            if self.queue.is_none() {
262                self.queue = Some(QueueConfig {
263                    initial_delay: val,
264                    max_delay: "3600s".to_string(),
265                    backoff_multiplier: 2.0,
266                    max_attempts: 5,
267                    worker_threads: 4,
268                    batch_size: 100,
269                });
270            } else if let Some(ref mut queue) = self.queue {
271                queue.initial_delay = val;
272            }
273        }
274        if let Ok(val) = std::env::var("RUSMES_QUEUE_MAX_DELAY") {
275            if self.queue.is_none() {
276                self.queue = Some(QueueConfig {
277                    initial_delay: "60s".to_string(),
278                    max_delay: val,
279                    backoff_multiplier: 2.0,
280                    max_attempts: 5,
281                    worker_threads: 4,
282                    batch_size: 100,
283                });
284            } else if let Some(ref mut queue) = self.queue {
285                queue.max_delay = val;
286            }
287        }
288        if let Ok(val) = std::env::var("RUSMES_QUEUE_BACKOFF_MULTIPLIER") {
289            if let Ok(multiplier) = val.parse::<f64>() {
290                if self.queue.is_none() {
291                    self.queue = Some(QueueConfig {
292                        initial_delay: "60s".to_string(),
293                        max_delay: "3600s".to_string(),
294                        backoff_multiplier: multiplier,
295                        max_attempts: 5,
296                        worker_threads: 4,
297                        batch_size: 100,
298                    });
299                } else if let Some(ref mut queue) = self.queue {
300                    queue.backoff_multiplier = multiplier;
301                }
302            }
303        }
304        if let Ok(val) = std::env::var("RUSMES_QUEUE_MAX_ATTEMPTS") {
305            if let Ok(attempts) = val.parse::<u32>() {
306                if self.queue.is_none() {
307                    self.queue = Some(QueueConfig {
308                        initial_delay: "60s".to_string(),
309                        max_delay: "3600s".to_string(),
310                        backoff_multiplier: 2.0,
311                        max_attempts: attempts,
312                        worker_threads: 4,
313                        batch_size: 100,
314                    });
315                } else if let Some(ref mut queue) = self.queue {
316                    queue.max_attempts = attempts;
317                }
318            }
319        }
320        if let Ok(val) = std::env::var("RUSMES_QUEUE_WORKER_THREADS") {
321            if let Ok(threads) = val.parse::<usize>() {
322                if self.queue.is_none() {
323                    self.queue = Some(QueueConfig {
324                        initial_delay: "60s".to_string(),
325                        max_delay: "3600s".to_string(),
326                        backoff_multiplier: 2.0,
327                        max_attempts: 5,
328                        worker_threads: threads,
329                        batch_size: 100,
330                    });
331                } else if let Some(ref mut queue) = self.queue {
332                    queue.worker_threads = threads;
333                }
334            }
335        }
336        if let Ok(val) = std::env::var("RUSMES_QUEUE_BATCH_SIZE") {
337            if let Ok(batch_size) = val.parse::<usize>() {
338                if self.queue.is_none() {
339                    self.queue = Some(QueueConfig {
340                        initial_delay: "60s".to_string(),
341                        max_delay: "3600s".to_string(),
342                        backoff_multiplier: 2.0,
343                        max_attempts: 5,
344                        worker_threads: 4,
345                        batch_size,
346                    });
347                } else if let Some(ref mut queue) = self.queue {
348                    queue.batch_size = batch_size;
349                }
350            }
351        }
352
353        // Metrics configuration
354        if let Ok(val) = std::env::var("RUSMES_METRICS_ENABLED") {
355            if let Ok(enabled) = val.parse::<bool>() {
356                if self.metrics.is_none() {
357                    self.metrics = Some(MetricsConfig {
358                        enabled,
359                        bind_address: "0.0.0.0:9090".to_string(),
360                        path: "/metrics".to_string(),
361                        basic_auth: None,
362                    });
363                } else if let Some(ref mut metrics) = self.metrics {
364                    metrics.enabled = enabled;
365                }
366            }
367        }
368        if let Ok(val) = std::env::var("RUSMES_METRICS_BIND_ADDRESS") {
369            if self.metrics.is_none() {
370                self.metrics = Some(MetricsConfig {
371                    enabled: true,
372                    bind_address: val,
373                    path: "/metrics".to_string(),
374                    basic_auth: None,
375                });
376            } else if let Some(ref mut metrics) = self.metrics {
377                metrics.bind_address = val;
378            }
379        }
380        if let Ok(val) = std::env::var("RUSMES_METRICS_PATH") {
381            if self.metrics.is_none() {
382                self.metrics = Some(MetricsConfig {
383                    enabled: true,
384                    bind_address: "0.0.0.0:9090".to_string(),
385                    path: val,
386                    basic_auth: None,
387                });
388            } else if let Some(ref mut metrics) = self.metrics {
389                metrics.path = val;
390            }
391        }
392
393        // Tracing configuration
394        if let Ok(val) = std::env::var("RUSMES_TRACING_ENABLED") {
395            if let Ok(enabled) = val.parse::<bool>() {
396                if self.tracing.is_none() {
397                    self.tracing = Some(TracingConfig {
398                        enabled,
399                        ..Default::default()
400                    });
401                } else if let Some(ref mut tracing) = self.tracing {
402                    tracing.enabled = enabled;
403                }
404            }
405        }
406        if let Ok(val) = std::env::var("RUSMES_TRACING_ENDPOINT") {
407            if self.tracing.is_none() {
408                self.tracing = Some(TracingConfig {
409                    enabled: true,
410                    endpoint: val,
411                    ..Default::default()
412                });
413            } else if let Some(ref mut tracing) = self.tracing {
414                tracing.endpoint = val;
415            }
416        }
417        if let Ok(val) = std::env::var("RUSMES_TRACING_PROTOCOL") {
418            let protocol = match val.to_lowercase().as_str() {
419                "grpc" => OtlpProtocol::Grpc,
420                "http" => OtlpProtocol::Http,
421                _ => OtlpProtocol::Grpc,
422            };
423            if self.tracing.is_none() {
424                self.tracing = Some(TracingConfig {
425                    enabled: true,
426                    protocol,
427                    ..Default::default()
428                });
429            } else if let Some(ref mut tracing) = self.tracing {
430                tracing.protocol = protocol;
431            }
432        }
433        if let Ok(val) = std::env::var("RUSMES_TRACING_SERVICE_NAME") {
434            if self.tracing.is_none() {
435                self.tracing = Some(TracingConfig {
436                    enabled: true,
437                    service_name: val,
438                    ..Default::default()
439                });
440            } else if let Some(ref mut tracing) = self.tracing {
441                tracing.service_name = val;
442            }
443        }
444        if let Ok(val) = std::env::var("RUSMES_TRACING_SAMPLE_RATIO") {
445            if let Ok(ratio) = val.parse::<f64>() {
446                if self.tracing.is_none() {
447                    self.tracing = Some(TracingConfig {
448                        enabled: true,
449                        sample_ratio: ratio,
450                        ..Default::default()
451                    });
452                } else if let Some(ref mut tracing) = self.tracing {
453                    tracing.sample_ratio = ratio;
454                }
455            }
456        }
457
458        // Connection limits configuration
459        if let Ok(val) = std::env::var("RUSMES_CONNECTION_LIMITS_MAX_CONNECTIONS_PER_IP") {
460            if let Ok(max) = val.parse::<usize>() {
461                if self.connection_limits.is_none() {
462                    self.connection_limits = Some(ConnectionLimitsConfig {
463                        max_connections_per_ip: max,
464                        max_total_connections: default_max_total_connections(),
465                        idle_timeout: default_idle_timeout(),
466                        reaper_interval: default_reaper_interval(),
467                    });
468                } else if let Some(ref mut limits) = self.connection_limits {
469                    limits.max_connections_per_ip = max;
470                }
471            }
472        }
473        if let Ok(val) = std::env::var("RUSMES_CONNECTION_LIMITS_MAX_TOTAL_CONNECTIONS") {
474            if let Ok(max) = val.parse::<usize>() {
475                if self.connection_limits.is_none() {
476                    self.connection_limits = Some(ConnectionLimitsConfig {
477                        max_connections_per_ip: default_max_connections_per_ip(),
478                        max_total_connections: max,
479                        idle_timeout: default_idle_timeout(),
480                        reaper_interval: default_reaper_interval(),
481                    });
482                } else if let Some(ref mut limits) = self.connection_limits {
483                    limits.max_total_connections = max;
484                }
485            }
486        }
487        if let Ok(val) = std::env::var("RUSMES_CONNECTION_LIMITS_IDLE_TIMEOUT") {
488            if self.connection_limits.is_none() {
489                self.connection_limits = Some(ConnectionLimitsConfig {
490                    max_connections_per_ip: default_max_connections_per_ip(),
491                    max_total_connections: default_max_total_connections(),
492                    idle_timeout: val,
493                    reaper_interval: default_reaper_interval(),
494                });
495            } else if let Some(ref mut limits) = self.connection_limits {
496                limits.idle_timeout = val;
497            }
498        }
499        if let Ok(val) = std::env::var("RUSMES_CONNECTION_LIMITS_REAPER_INTERVAL") {
500            if self.connection_limits.is_none() {
501                self.connection_limits = Some(ConnectionLimitsConfig {
502                    max_connections_per_ip: default_max_connections_per_ip(),
503                    max_total_connections: default_max_total_connections(),
504                    idle_timeout: default_idle_timeout(),
505                    reaper_interval: val,
506                });
507            } else if let Some(ref mut limits) = self.connection_limits {
508                limits.reaper_interval = val;
509            }
510        }
511    }
512}