1use serde::{Deserialize, Serialize};
10use std::net::SocketAddr;
11use std::path::{Path, PathBuf};
12use std::time::Duration;
13use thiserror::Error;
14
15#[derive(Error, Debug)]
17pub enum ConfigError {
18 #[error("Failed to read configuration file: {0}")]
19 ReadFile(#[from] std::io::Error),
20
21 #[error("Failed to parse TOML: {0}")]
22 ParseToml(#[from] toml::de::Error),
23
24 #[error("Validation error: {0}")]
25 Validation(String),
26
27 #[error("Invalid socket address: {0}")]
28 InvalidAddress(#[from] std::net::AddrParseError),
29}
30
31pub type ConfigResult<T> = Result<T, ConfigError>;
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ServerConfig {
36 pub server: ServerSettings,
38
39 pub storage: StorageSettings,
41
42 pub network: NetworkSettings,
44
45 #[serde(default)]
47 pub cluster: Option<ClusterSettings>,
48
49 pub logging: LoggingSettings,
51
52 pub metrics: MetricsSettings,
54
55 #[serde(default)]
57 pub auth: AuthSettings,
58
59 #[serde(default)]
61 pub authz: AuthorizationSettings,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ServerSettings {
67 pub bind_address: String,
69
70 pub data_dir: PathBuf,
72
73 #[serde(default = "default_pid_file")]
75 pub pid_file: PathBuf,
76
77 #[serde(default = "default_max_connections")]
79 pub max_connections: usize,
80
81 #[serde(default = "default_shutdown_timeout")]
83 pub shutdown_timeout_secs: u64,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct StorageSettings {
89 #[serde(default = "default_storage_engine")]
91 pub engine: String,
92
93 #[serde(default)]
95 pub wal: WalSettings,
96
97 #[serde(default = "default_memtable_size")]
99 pub memtable_size_mb: usize,
100
101 #[serde(default = "default_block_cache_size")]
103 pub block_cache_size_mb: usize,
104
105 #[serde(default)]
107 pub compaction: CompactionSettings,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct WalSettings {
113 #[serde(default = "default_true")]
115 pub enabled: bool,
116
117 #[serde(default = "default_wal_dir")]
119 pub dir: PathBuf,
120
121 #[serde(default = "default_wal_segment_size")]
123 pub segment_size_mb: usize,
124
125 #[serde(default = "default_sync_mode")]
127 pub sync_mode: String,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct CompactionSettings {
133 #[serde(default = "default_compaction_strategy")]
135 pub strategy: String,
136
137 #[serde(default = "default_num_levels")]
139 pub num_levels: usize,
140
141 #[serde(default = "default_level_multiplier")]
143 pub level_multiplier: usize,
144
145 #[serde(default = "default_max_compactions")]
147 pub max_concurrent: usize,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct NetworkSettings {
153 #[serde(default = "default_false")]
155 pub tls_enabled: bool,
156
157 pub tls_cert: Option<PathBuf>,
159
160 pub tls_key: Option<PathBuf>,
162
163 pub tls_ca: Option<PathBuf>,
165
166 #[serde(default = "default_false")]
168 pub require_client_cert: bool,
169
170 #[serde(default = "default_connection_timeout")]
172 pub connection_timeout_secs: u64,
173
174 #[serde(default = "default_keepalive_interval")]
176 pub keepalive_interval_secs: u64,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct ClusterSettings {
182 #[serde(default = "default_true")]
184 pub enabled: bool,
185
186 pub node_id: u64,
188
189 pub peers: Vec<String>,
191
192 #[serde(default = "default_heartbeat_interval")]
194 pub heartbeat_interval_ms: u64,
195
196 #[serde(default = "default_election_timeout")]
198 pub election_timeout_ms: u64,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct LoggingSettings {
204 #[serde(default = "default_log_level")]
206 pub level: String,
207
208 #[serde(default = "default_log_format")]
210 pub format: String,
211
212 #[serde(default = "default_false")]
214 pub file_enabled: bool,
215
216 pub file_path: Option<PathBuf>,
218
219 #[serde(default)]
221 pub rotation: LogRotationSettings,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct LogRotationSettings {
227 #[serde(default = "default_true")]
229 pub enabled: bool,
230
231 #[serde(default = "default_log_max_size")]
233 pub max_size_mb: usize,
234
235 #[serde(default = "default_log_max_backups")]
237 pub max_backups: usize,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct MetricsSettings {
243 #[serde(default = "default_true")]
245 pub enabled: bool,
246
247 #[serde(default = "default_metrics_address")]
249 pub bind_address: String,
250
251 #[serde(default = "default_metrics_interval")]
253 pub export_interval_secs: u64,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct AuthSettings {
259 #[serde(default = "default_false")]
261 pub enabled: bool,
262
263 #[serde(default = "default_auth_methods")]
265 pub methods: Vec<String>,
266
267 #[serde(default)]
269 pub mtls: MtlsSettings,
270
271 #[serde(default)]
273 pub jwt: JwtSettings,
274
275 #[serde(default)]
277 pub api_key: ApiKeySettings,
278
279 #[serde(default = "default_true")]
281 pub reject_unauthenticated: bool,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct MtlsSettings {
287 #[serde(default = "default_false")]
289 pub enabled: bool,
290
291 pub ca_certs_dir: Option<PathBuf>,
293
294 pub crl_path: Option<PathBuf>,
296
297 #[serde(default = "default_true")]
299 pub verify_cn: bool,
300
301 #[serde(default)]
303 pub allowed_organizations: Vec<String>,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct JwtSettings {
309 #[serde(default = "default_false")]
311 pub enabled: bool,
312
313 pub secret: Option<String>,
315
316 pub public_key_path: Option<PathBuf>,
318
319 #[serde(default = "default_jwt_algorithm")]
321 pub algorithm: String,
322
323 #[serde(default = "default_jwt_expiration")]
325 pub expiration_secs: u64,
326
327 pub issuer: Option<String>,
329
330 pub audience: Option<String>,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct ApiKeySettings {
337 #[serde(default = "default_false")]
339 pub enabled: bool,
340
341 pub keys_file: Option<PathBuf>,
343
344 #[serde(default = "default_api_key_header")]
346 pub header_name: String,
347
348 #[serde(default = "default_true")]
350 pub hash_keys: bool,
351}
352
353#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct AuthorizationSettings {
356 #[serde(default = "default_true")]
358 pub enabled: bool,
359
360 #[serde(default = "default_user_role")]
362 pub default_role: String,
363
364 pub roles_file: Option<PathBuf>,
366
367 pub policies_file: Option<PathBuf>,
369
370 #[serde(default = "default_true")]
372 pub collection_permissions: bool,
373
374 #[serde(default = "default_permission_mode")]
376 pub default_mode: String,
377
378 #[serde(default = "default_true")]
380 pub audit_enabled: bool,
381
382 pub audit_log_path: Option<PathBuf>,
384}
385
386fn default_pid_file() -> PathBuf {
388 PathBuf::from("/var/run/amaters-server.pid")
389}
390
391fn default_max_connections() -> usize {
392 1000
393}
394
395fn default_shutdown_timeout() -> u64 {
396 30
397}
398
399fn default_storage_engine() -> String {
400 "lsm".to_string()
401}
402
403fn default_memtable_size() -> usize {
404 64
405}
406
407fn default_block_cache_size() -> usize {
408 256
409}
410
411fn default_wal_dir() -> PathBuf {
412 PathBuf::from("wal")
413}
414
415fn default_wal_segment_size() -> usize {
416 64
417}
418
419fn default_sync_mode() -> String {
420 "interval".to_string()
421}
422
423fn default_compaction_strategy() -> String {
424 "leveled".to_string()
425}
426
427fn default_num_levels() -> usize {
428 7
429}
430
431fn default_level_multiplier() -> usize {
432 10
433}
434
435fn default_max_compactions() -> usize {
436 4
437}
438
439fn default_connection_timeout() -> u64 {
440 30
441}
442
443fn default_keepalive_interval() -> u64 {
444 60
445}
446
447fn default_heartbeat_interval() -> u64 {
448 100
449}
450
451fn default_election_timeout() -> u64 {
452 300
453}
454
455fn default_log_level() -> String {
456 "info".to_string()
457}
458
459fn default_log_format() -> String {
460 "pretty".to_string()
461}
462
463fn default_log_max_size() -> usize {
464 100
465}
466
467fn default_log_max_backups() -> usize {
468 10
469}
470
471fn default_metrics_address() -> String {
472 "127.0.0.1:9090".to_string()
473}
474
475fn default_metrics_interval() -> u64 {
476 60
477}
478
479fn default_true() -> bool {
480 true
481}
482
483fn default_false() -> bool {
484 false
485}
486
487fn default_auth_methods() -> Vec<String> {
488 vec!["mtls".to_string()]
489}
490
491fn default_jwt_algorithm() -> String {
492 "HS256".to_string()
493}
494
495fn default_jwt_expiration() -> u64 {
496 3600 }
498
499fn default_api_key_header() -> String {
500 "X-API-Key".to_string()
501}
502
503fn default_user_role() -> String {
504 "user".to_string()
505}
506
507fn default_permission_mode() -> String {
508 "deny-by-default".to_string()
509}
510
511impl Default for ServerConfig {
512 fn default() -> Self {
513 Self {
514 server: ServerSettings {
515 bind_address: "0.0.0.0:7878".to_string(),
516 data_dir: PathBuf::from("./data"),
517 pid_file: default_pid_file(),
518 max_connections: default_max_connections(),
519 shutdown_timeout_secs: default_shutdown_timeout(),
520 },
521 storage: StorageSettings {
522 engine: default_storage_engine(),
523 wal: WalSettings::default(),
524 memtable_size_mb: default_memtable_size(),
525 block_cache_size_mb: default_block_cache_size(),
526 compaction: CompactionSettings::default(),
527 },
528 network: NetworkSettings {
529 tls_enabled: false,
530 tls_cert: None,
531 tls_key: None,
532 tls_ca: None,
533 require_client_cert: false,
534 connection_timeout_secs: default_connection_timeout(),
535 keepalive_interval_secs: default_keepalive_interval(),
536 },
537 cluster: None,
538 logging: LoggingSettings {
539 level: default_log_level(),
540 format: default_log_format(),
541 file_enabled: false,
542 file_path: None,
543 rotation: LogRotationSettings::default(),
544 },
545 metrics: MetricsSettings {
546 enabled: true,
547 bind_address: default_metrics_address(),
548 export_interval_secs: default_metrics_interval(),
549 },
550 auth: AuthSettings::default(),
551 authz: AuthorizationSettings::default(),
552 }
553 }
554}
555
556impl Default for WalSettings {
557 fn default() -> Self {
558 Self {
559 enabled: true,
560 dir: default_wal_dir(),
561 segment_size_mb: default_wal_segment_size(),
562 sync_mode: default_sync_mode(),
563 }
564 }
565}
566
567impl Default for CompactionSettings {
568 fn default() -> Self {
569 Self {
570 strategy: default_compaction_strategy(),
571 num_levels: default_num_levels(),
572 level_multiplier: default_level_multiplier(),
573 max_concurrent: default_max_compactions(),
574 }
575 }
576}
577
578impl Default for LogRotationSettings {
579 fn default() -> Self {
580 Self {
581 enabled: true,
582 max_size_mb: default_log_max_size(),
583 max_backups: default_log_max_backups(),
584 }
585 }
586}
587
588impl Default for AuthSettings {
589 fn default() -> Self {
590 Self {
591 enabled: false,
592 methods: default_auth_methods(),
593 mtls: MtlsSettings::default(),
594 jwt: JwtSettings::default(),
595 api_key: ApiKeySettings::default(),
596 reject_unauthenticated: true,
597 }
598 }
599}
600
601impl Default for MtlsSettings {
602 fn default() -> Self {
603 Self {
604 enabled: false,
605 ca_certs_dir: None,
606 crl_path: None,
607 verify_cn: true,
608 allowed_organizations: Vec::new(),
609 }
610 }
611}
612
613impl Default for JwtSettings {
614 fn default() -> Self {
615 Self {
616 enabled: false,
617 secret: None,
618 public_key_path: None,
619 algorithm: default_jwt_algorithm(),
620 expiration_secs: default_jwt_expiration(),
621 issuer: None,
622 audience: None,
623 }
624 }
625}
626
627impl Default for ApiKeySettings {
628 fn default() -> Self {
629 Self {
630 enabled: false,
631 keys_file: None,
632 header_name: default_api_key_header(),
633 hash_keys: true,
634 }
635 }
636}
637
638impl Default for AuthorizationSettings {
639 fn default() -> Self {
640 Self {
641 enabled: true,
642 default_role: default_user_role(),
643 roles_file: None,
644 policies_file: None,
645 collection_permissions: true,
646 default_mode: default_permission_mode(),
647 audit_enabled: true,
648 audit_log_path: None,
649 }
650 }
651}
652
653impl ServerConfig {
654 pub fn from_file(path: impl AsRef<Path>) -> ConfigResult<Self> {
656 let contents = std::fs::read_to_string(path)?;
657 let config: ServerConfig = toml::from_str(&contents)?;
658 config.validate()?;
659 Ok(config)
660 }
661
662 pub fn from_file_with_env(path: impl AsRef<Path>) -> ConfigResult<Self> {
664 let mut config = Self::from_file(path)?;
665 config.apply_env_overrides();
666 config.validate()?;
667 Ok(config)
668 }
669
670 pub fn apply_env_overrides(&mut self) {
672 if let Ok(bind) = std::env::var("AMATERS_BIND_ADDRESS") {
673 self.server.bind_address = bind;
674 }
675 if let Ok(data_dir) = std::env::var("AMATERS_DATA_DIR") {
676 self.server.data_dir = PathBuf::from(data_dir);
677 }
678 if let Ok(log_level) = std::env::var("AMATERS_LOG_LEVEL") {
679 self.logging.level = log_level;
680 }
681 if let Ok(tls_enabled) = std::env::var("AMATERS_TLS_ENABLED") {
682 self.network.tls_enabled = tls_enabled.parse().unwrap_or(false);
683 }
684 }
685
686 pub fn validate(&self) -> ConfigResult<()> {
688 let _: SocketAddr = self
690 .server
691 .bind_address
692 .parse()
693 .map_err(|e| ConfigError::Validation(format!("Invalid bind address: {}", e)))?;
694
695 if self.server.data_dir.as_os_str().is_empty() {
697 return Err(ConfigError::Validation(
698 "Data directory cannot be empty".to_string(),
699 ));
700 }
701
702 match self.storage.engine.as_str() {
704 "memory" | "lsm" => {}
705 other => {
706 return Err(ConfigError::Validation(format!(
707 "Invalid storage engine: {}. Must be 'memory' or 'lsm'",
708 other
709 )));
710 }
711 }
712
713 if self.network.tls_enabled {
715 if self.network.tls_cert.is_none() {
716 return Err(ConfigError::Validation(
717 "TLS enabled but no certificate file specified".to_string(),
718 ));
719 }
720 if self.network.tls_key.is_none() {
721 return Err(ConfigError::Validation(
722 "TLS enabled but no key file specified".to_string(),
723 ));
724 }
725 if self.network.require_client_cert && self.network.tls_ca.is_none() {
726 return Err(ConfigError::Validation(
727 "Client certificate required but no CA file specified".to_string(),
728 ));
729 }
730 }
731
732 if let Some(ref cluster) = self.cluster {
734 if cluster.enabled && cluster.peers.is_empty() {
735 return Err(ConfigError::Validation(
736 "Cluster enabled but no peers specified".to_string(),
737 ));
738 }
739 }
740
741 match self.logging.level.to_lowercase().as_str() {
743 "trace" | "debug" | "info" | "warn" | "error" => {}
744 other => {
745 return Err(ConfigError::Validation(format!(
746 "Invalid log level: {}. Must be one of: trace, debug, info, warn, error",
747 other
748 )));
749 }
750 }
751
752 let _: SocketAddr = self
754 .metrics
755 .bind_address
756 .parse()
757 .map_err(|e| ConfigError::Validation(format!("Invalid metrics address: {}", e)))?;
758
759 if self.auth.enabled {
761 let has_enabled_method = (self.auth.mtls.enabled
763 && self.auth.methods.contains(&"mtls".to_string()))
764 || (self.auth.jwt.enabled && self.auth.methods.contains(&"jwt".to_string()))
765 || (self.auth.api_key.enabled
766 && self.auth.methods.contains(&"api_key".to_string()));
767
768 if !has_enabled_method {
769 return Err(ConfigError::Validation(
770 "Authentication enabled but no valid auth methods configured".to_string(),
771 ));
772 }
773
774 if self.auth.jwt.enabled {
776 match self.auth.jwt.algorithm.as_str() {
777 "HS256" => {
778 if self.auth.jwt.secret.is_none() {
779 return Err(ConfigError::Validation(
780 "JWT HS256 enabled but no secret key provided".to_string(),
781 ));
782 }
783 }
784 "RS256" => {
785 if self.auth.jwt.public_key_path.is_none() {
786 return Err(ConfigError::Validation(
787 "JWT RS256 enabled but no public key path provided".to_string(),
788 ));
789 }
790 }
791 other => {
792 return Err(ConfigError::Validation(format!(
793 "Invalid JWT algorithm: {}. Supported: HS256, RS256",
794 other
795 )));
796 }
797 }
798 }
799
800 if self.auth.api_key.enabled && self.auth.api_key.keys_file.is_none() {
802 return Err(ConfigError::Validation(
803 "API key auth enabled but no keys file specified".to_string(),
804 ));
805 }
806
807 if self.auth.mtls.enabled && self.auth.mtls.ca_certs_dir.is_none() {
809 return Err(ConfigError::Validation(
810 "mTLS enabled but no CA certificates directory specified".to_string(),
811 ));
812 }
813 }
814
815 if self.authz.enabled {
817 match self.authz.default_mode.as_str() {
818 "deny-by-default" | "allow-by-default" => {}
819 other => {
820 return Err(ConfigError::Validation(format!(
821 "Invalid authorization default mode: {}. Must be 'deny-by-default' or 'allow-by-default'",
822 other
823 )));
824 }
825 }
826 }
827
828 Ok(())
829 }
830
831 pub fn shutdown_timeout(&self) -> Duration {
833 Duration::from_secs(self.server.shutdown_timeout_secs)
834 }
835
836 pub fn connection_timeout(&self) -> Duration {
838 Duration::from_secs(self.network.connection_timeout_secs)
839 }
840
841 pub fn keepalive_interval(&self) -> Duration {
843 Duration::from_secs(self.network.keepalive_interval_secs)
844 }
845
846 pub fn save_to_file(&self, path: impl AsRef<Path>) -> ConfigResult<()> {
848 let contents = toml::to_string_pretty(self)
849 .map_err(|e| ConfigError::Validation(format!("Failed to serialize config: {}", e)))?;
850 std::fs::write(path, contents)?;
851 Ok(())
852 }
853
854 pub fn example() -> Self {
856 Self::default()
857 }
858}
859
860#[cfg(test)]
861mod tests {
862 use super::*;
863 use std::env;
864
865 #[test]
866 fn test_default_config() {
867 let config = ServerConfig::default();
868 assert_eq!(config.server.bind_address, "0.0.0.0:7878");
869 assert_eq!(config.storage.engine, "lsm");
870 assert_eq!(config.logging.level, "info");
871 }
872
873 #[test]
874 fn test_config_validation() {
875 let config = ServerConfig::default();
876 assert!(config.validate().is_ok());
877 }
878
879 #[test]
880 fn test_invalid_bind_address() {
881 let mut config = ServerConfig::default();
882 config.server.bind_address = "invalid".to_string();
883 assert!(config.validate().is_err());
884 }
885
886 #[test]
887 fn test_invalid_storage_engine() {
888 let mut config = ServerConfig::default();
889 config.storage.engine = "invalid".to_string();
890 assert!(config.validate().is_err());
891 }
892
893 #[test]
894 fn test_tls_validation() {
895 let mut config = ServerConfig::default();
896 config.network.tls_enabled = true;
897 assert!(config.validate().is_err()); }
899
900 #[test]
901 fn test_env_overrides() {
902 unsafe {
903 env::set_var("AMATERS_BIND_ADDRESS", "127.0.0.1:9999");
904 env::set_var("AMATERS_LOG_LEVEL", "debug");
905 }
906
907 let mut config = ServerConfig::default();
908 config.apply_env_overrides();
909
910 assert_eq!(config.server.bind_address, "127.0.0.1:9999");
911 assert_eq!(config.logging.level, "debug");
912
913 unsafe {
914 env::remove_var("AMATERS_BIND_ADDRESS");
915 env::remove_var("AMATERS_LOG_LEVEL");
916 }
917 }
918
919 #[test]
920 fn test_save_and_load() {
921 let temp_dir = env::temp_dir();
922 let config_path = temp_dir.join("test_config.toml");
923
924 let config = ServerConfig::default();
925 config
926 .save_to_file(&config_path)
927 .expect("Failed to save config");
928
929 let loaded = ServerConfig::from_file(&config_path).expect("Failed to load config");
930 assert_eq!(config.server.bind_address, loaded.server.bind_address);
931
932 std::fs::remove_file(&config_path).ok();
933 }
934}