1use cidr::IpCidr;
8use kanidm_proto::backup::BackupCompression;
9use kanidm_proto::constants::DEFAULT_SERVER_ADDRESS;
10use kanidm_proto::internal::FsType;
11use serde::Deserialize;
12use serde_with::{formats::PreferOne, serde_as, OneOrMany};
13use sketching::LogLevel;
14use std::fmt::{self, Display};
15use std::fs::File;
16use std::io::Read;
17use std::net::IpAddr;
18use std::path::{Path, PathBuf};
19use std::str::FromStr;
20use std::sync::Arc;
21use url::Url;
22
23use crate::repl::config::ReplicationConfiguration;
24
25#[derive(Debug, Deserialize)]
26struct VersionDetection {
27 #[serde(default)]
28 version: Version,
29}
30
31#[derive(Debug, Deserialize, Default)]
32pub enum Version {
34 #[serde(rename = "2")]
35 V2,
36
37 #[default]
38 Legacy,
39}
40
41#[allow(clippy::large_enum_variant)]
43pub enum ServerConfigUntagged {
44 Version(ServerConfigVersion),
45 Legacy(ServerConfig),
46}
47
48pub enum ServerConfigVersion {
49 V2 { values: ServerConfigV2 },
50}
51
52#[derive(Deserialize, Debug, Clone)]
53pub struct OnlineBackup {
54 pub path: Option<PathBuf>,
56 pub schedule: String,
74 #[serde(default = "default_online_backup_versions")]
75 pub versions: usize,
77 #[serde(default = "default_online_backup_enabled")]
79 pub enabled: bool,
80
81 #[serde(default)]
82 pub compression: BackupCompression,
83}
84
85impl Default for OnlineBackup {
86 fn default() -> Self {
87 OnlineBackup {
88 path: None, schedule: default_online_backup_schedule(),
90 versions: default_online_backup_versions(),
91 enabled: default_online_backup_enabled(),
92 compression: BackupCompression::default(),
93 }
94 }
95}
96
97fn default_online_backup_enabled() -> bool {
98 true
99}
100
101fn default_online_backup_schedule() -> String {
102 "@daily".to_string()
103}
104
105fn default_online_backup_versions() -> usize {
106 7
107}
108
109#[derive(Deserialize, Debug, Clone)]
110pub struct TlsConfiguration {
111 pub chain: PathBuf,
112 pub key: PathBuf,
113 pub client_ca: Option<PathBuf>,
114}
115
116#[derive(Debug, Default)]
117pub enum TcpAddressInfo {
118 #[default]
119 None,
120 ProxyV2(Vec<IpCidr>),
121 ProxyV1(Vec<IpCidr>),
122}
123
124#[derive(Deserialize, Debug, Clone, Default)]
125pub enum LdapAddressInfo {
126 #[default]
127 None,
128 #[serde(rename = "proxy-v2")]
129 ProxyV2(Vec<IpCidr>),
130 #[serde(rename = "proxy-v1")]
131 ProxyV1(Vec<IpCidr>),
132}
133
134impl LdapAddressInfo {
135 pub fn trusted_tcp_info(&self) -> Arc<TcpAddressInfo> {
136 Arc::new(match self {
137 LdapAddressInfo::None => TcpAddressInfo::None,
138 LdapAddressInfo::ProxyV2(trusted) => TcpAddressInfo::ProxyV2(trusted.clone()),
139 LdapAddressInfo::ProxyV1(trusted) => TcpAddressInfo::ProxyV1(trusted.clone()),
140 })
141 }
142}
143
144impl Display for LdapAddressInfo {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 match self {
147 Self::None => f.write_str("none"),
148 Self::ProxyV2(trusted) => {
149 f.write_str("proxy-v2 [ ")?;
150 for ip in trusted {
151 write!(f, "{ip} ")?;
152 }
153 f.write_str("]")
154 }
155 Self::ProxyV1(trusted) => {
156 f.write_str("proxy-v1 [ ")?;
157 for ip in trusted {
158 write!(f, "{ip} ")?;
159 }
160 f.write_str("]")
161 }
162 }
163 }
164}
165
166pub(crate) enum AddressSet {
167 NonContiguousIpSet(Vec<IpCidr>),
168 All,
169}
170
171impl AddressSet {
172 pub(crate) fn contains(&self, ip_addr: &IpAddr) -> bool {
173 match self {
174 Self::All => true,
175 Self::NonContiguousIpSet(range) => {
176 range.iter().any(|ip_cidr| ip_cidr.contains(ip_addr))
177 }
178 }
179 }
180}
181
182#[derive(Deserialize, Debug, Clone, Default)]
183pub enum HttpAddressInfo {
184 #[default]
185 None,
186 #[serde(rename = "x-forward-for")]
187 XForwardFor(Vec<IpCidr>),
188 #[serde(rename = "x-forward-for-all-source-trusted")]
191 XForwardForAllSourcesTrusted,
192 #[serde(rename = "proxy-v2")]
193 ProxyV2(Vec<IpCidr>),
194 #[serde(rename = "proxy-v1")]
195 ProxyV1(Vec<IpCidr>),
196}
197
198impl HttpAddressInfo {
199 pub(crate) fn trusted_x_forward_for(&self) -> Option<AddressSet> {
200 match self {
201 Self::XForwardForAllSourcesTrusted => Some(AddressSet::All),
202 Self::XForwardFor(trusted) => Some(AddressSet::NonContiguousIpSet(trusted.clone())),
203 _ => None,
204 }
205 }
206
207 pub fn trusted_tcp_info(&self) -> Arc<TcpAddressInfo> {
208 Arc::new(match self {
209 Self::ProxyV2(trusted) => TcpAddressInfo::ProxyV2(trusted.clone()),
210 Self::ProxyV1(trusted) => TcpAddressInfo::ProxyV1(trusted.clone()),
211 Self::None | Self::XForwardFor(_) | Self::XForwardForAllSourcesTrusted => {
212 TcpAddressInfo::None
213 }
214 })
215 }
216}
217
218impl Display for HttpAddressInfo {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 match self {
221 Self::None => f.write_str("none"),
222
223 Self::XForwardFor(trusted) => {
224 f.write_str("x-forward-for [ ")?;
225 for ip in trusted {
226 write!(f, "{ip} ")?;
227 }
228 f.write_str("]")
229 }
230 Self::XForwardForAllSourcesTrusted => {
231 f.write_str("x-forward-for [ ALL SOURCES TRUSTED ]")
232 }
233 Self::ProxyV2(trusted) => {
234 f.write_str("proxy-v2 [ ")?;
235 for ip in trusted {
236 write!(f, "{ip} ")?;
237 }
238 f.write_str("]")
239 }
240 Self::ProxyV1(trusted) => {
241 f.write_str("proxy-v1 [ ")?;
242 for ip in trusted {
243 write!(f, "{ip} ")?;
244 }
245 f.write_str("]")
246 }
247 }
248 }
249}
250
251#[derive(Debug, Deserialize, Clone, Copy, Default, Eq, PartialEq)]
252pub enum ServerRole {
253 #[default]
254 WriteReplica,
255 WriteReplicaNoUI,
256 ReadOnlyReplica,
257}
258
259impl Display for ServerRole {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 match self {
262 ServerRole::WriteReplica => f.write_str("write replica"),
263 ServerRole::WriteReplicaNoUI => f.write_str("write replica (no ui)"),
264 ServerRole::ReadOnlyReplica => f.write_str("read only replica"),
265 }
266 }
267}
268
269impl FromStr for ServerRole {
270 type Err = &'static str;
271
272 fn from_str(s: &str) -> Result<Self, Self::Err> {
273 match s {
274 "write_replica" => Ok(ServerRole::WriteReplica),
275 "write_replica_no_ui" => Ok(ServerRole::WriteReplicaNoUI),
276 "read_only_replica" => Ok(ServerRole::ReadOnlyReplica),
277 _ => Err("Must be one of write_replica, write_replica_no_ui, read_only_replica"),
278 }
279 }
280}
281
282#[derive(Debug, Deserialize, Default)]
291#[serde(deny_unknown_fields)]
292pub struct ServerConfig {
293 domain: Option<String>,
295 origin: Option<Url>,
297 db_path: Option<PathBuf>,
299 db_fs_type: Option<kanidm_proto::internal::FsType>,
301
302 tls_chain: Option<PathBuf>,
304 tls_key: Option<PathBuf>,
306
307 tls_client_ca: Option<PathBuf>,
309
310 bindaddress: Option<String>,
314 ldapbindaddress: Option<String>,
320 role: Option<ServerRole>,
322 log_level: Option<LogLevel>,
324
325 online_backup: Option<OnlineBackup>,
327
328 trust_x_forward_for: Option<bool>,
330
331 adminbindpath: Option<String>,
333
334 thread_count: Option<usize>,
337
338 maximum_request_size_bytes: Option<usize>,
340
341 #[allow(dead_code)]
343 db_arc_size: Option<usize>,
344 #[serde(default)]
345 #[serde(rename = "replication")]
346 repl_config: Option<ReplicationConfiguration>,
348 otel_grpc_url: Option<String>,
350}
351
352impl ServerConfigUntagged {
353 pub fn new<P: AsRef<Path>>(config_path: P) -> Result<Self, std::io::Error> {
355 let mut f: File = File::open(config_path.as_ref()).inspect_err(|e| {
357 eprintln!("Unable to open config file [{e:?}] 🥺");
358 let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
359 eprintln!("{diag}");
360 })?;
361
362 let mut contents = String::new();
363
364 f.read_to_string(&mut contents).inspect_err(|e| {
365 eprintln!("unable to read contents {e:?}");
366 let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
367 eprintln!("{diag}");
368 })?;
369
370 let config_version = toml::from_str::<VersionDetection>(contents.as_str())
372 .map(|vd| vd.version)
373 .map_err(|err| {
374 eprintln!(
375 "Unable to parse config version from '{:?}': {:?}",
376 config_path.as_ref(),
377 err
378 );
379 std::io::Error::new(std::io::ErrorKind::InvalidData, err)
380 })?;
381
382 match config_version {
383 Version::V2 => toml::from_str::<ServerConfigV2>(contents.as_str())
384 .map(|values| ServerConfigUntagged::Version(ServerConfigVersion::V2 { values })),
385 Version::Legacy => {
386 toml::from_str::<ServerConfig>(contents.as_str()).map(ServerConfigUntagged::Legacy)
387 }
388 }
389 .map_err(|err| {
390 eprintln!(
391 "Unable to parse config from '{:?}': {:?}",
392 config_path.as_ref(),
393 err
394 );
395 std::io::Error::new(std::io::ErrorKind::InvalidData, err)
396 })
397 }
398}
399
400#[serde_as]
401#[derive(Debug, Deserialize, Default)]
402#[serde(deny_unknown_fields)]
403pub struct ServerConfigV2 {
404 #[allow(dead_code)]
405 version: String,
406 domain: Option<String>,
407 origin: Option<Url>,
408 db_path: Option<PathBuf>,
409 db_fs_type: Option<kanidm_proto::internal::FsType>,
410 tls_chain: Option<PathBuf>,
411 tls_key: Option<PathBuf>,
412 tls_client_ca: Option<PathBuf>,
413
414 migration_path: Option<PathBuf>,
415
416 #[serde_as(as = "Option<OneOrMany<_, PreferOne>>")]
417 bindaddress: Option<Vec<String>>,
418 #[serde_as(as = "Option<OneOrMany<_, PreferOne>>")]
419 ldapbindaddress: Option<Vec<String>>,
420
421 role: Option<ServerRole>,
422 log_level: Option<LogLevel>,
423 online_backup: Option<OnlineBackup>,
424
425 http_client_address_info: Option<HttpAddressInfo>,
426 ldap_client_address_info: Option<LdapAddressInfo>,
427
428 adminbindpath: Option<String>,
429 thread_count: Option<usize>,
430 maximum_request_size_bytes: Option<usize>,
431 #[allow(dead_code)]
432 db_arc_size: Option<usize>,
433 #[serde(default)]
434 #[serde(rename = "replication")]
435 repl_config: Option<ReplicationConfiguration>,
436 otel_grpc_url: Option<String>,
437}
438
439#[derive(Debug, Clone)]
440pub struct IntegrationTestConfig {
441 pub admin_user: String,
442 pub admin_password: String,
443 pub idm_admin_user: String,
444 pub idm_admin_password: String,
445}
446
447#[derive(Debug, Clone)]
448pub struct IntegrationReplConfig {
449 }
455
456#[derive(Debug, Clone)]
458pub struct Configuration {
459 pub address: Vec<String>,
460 pub ldapbindaddress: Option<Vec<String>>,
461 pub adminbindpath: String,
462 pub threads: usize,
463 pub db_path: Option<PathBuf>,
465 pub db_fs_type: Option<FsType>,
466 pub db_arc_size: Option<usize>,
467 pub maximum_request: usize,
468
469 pub migration_path: Option<PathBuf>,
470
471 pub http_client_address_info: HttpAddressInfo,
472 pub ldap_client_address_info: LdapAddressInfo,
473
474 pub tls_config: Option<TlsConfiguration>,
475 pub integration_test_config: Option<Box<IntegrationTestConfig>>,
476 pub online_backup: Option<OnlineBackup>,
477 pub domain: String,
478 pub origin: Url,
479 pub role: ServerRole,
480 pub log_level: LogLevel,
481 pub repl_config: Option<ReplicationConfiguration>,
483 pub integration_repl_config: Option<Box<IntegrationReplConfig>>,
485 pub otel_grpc_url: Option<String>,
486}
487
488impl Configuration {
489 pub fn build() -> ConfigurationBuilder {
490 ConfigurationBuilder {
491 bindaddress: None,
492 ldapbindaddress: None,
493 adminbindpath: env!("KANIDM_SERVER_ADMIN_BIND_PATH").to_string(),
495 threads: std::thread::available_parallelism()
496 .map(|t| t.get())
497 .unwrap_or_else(|_e| {
498 eprintln!("WARNING: Unable to read number of available CPUs, defaulting to 4");
499 4
500 }),
501 db_path: None,
502 db_fs_type: None,
503 db_arc_size: None,
504 migration_path: None,
505 maximum_request: 256 * 1024, http_client_address_info: HttpAddressInfo::default(),
507 ldap_client_address_info: LdapAddressInfo::default(),
508 tls_key: None,
509 tls_chain: None,
510 tls_client_ca: None,
511 online_backup: None,
512 domain: None,
513 origin: None,
514 log_level: None,
515 role: None,
516 repl_config: None,
517 otel_grpc_url: None,
518 }
519 }
520
521 pub fn new_for_test() -> Self {
522 #[allow(clippy::expect_used)]
523 Configuration {
524 address: vec![DEFAULT_SERVER_ADDRESS.to_string()],
525 ldapbindaddress: None,
526 adminbindpath: env!("KANIDM_SERVER_ADMIN_BIND_PATH").to_string(),
527 threads: 1,
528 db_path: None,
529 db_fs_type: None,
530 db_arc_size: None,
531 migration_path: None,
532 maximum_request: 256 * 1024, http_client_address_info: HttpAddressInfo::default(),
534 ldap_client_address_info: LdapAddressInfo::default(),
535 tls_config: None,
536 integration_test_config: None,
537 online_backup: None,
538 domain: "idm.example.com".to_string(),
539 origin: Url::from_str("https://idm.example.com")
540 .expect("Failed to parse built-in string as URL"),
541 log_level: LogLevel::default(),
542 role: ServerRole::WriteReplicaNoUI,
543 repl_config: None,
544 integration_repl_config: None,
545 otel_grpc_url: None,
546 }
547 }
548}
549
550impl fmt::Display for Configuration {
551 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
552 for a in &self.address {
553 write!(f, "address: {a}, ")?;
554 }
555 write!(f, "domain: {}, ", self.domain)?;
556 match &self.ldapbindaddress {
557 Some(las) => {
558 for la in las {
559 write!(f, "ldap address: {la}, ")?;
560 }
561 }
562 None => write!(f, "ldap address: disabled, ")?,
563 };
564 write!(f, "origin: {} ", self.origin)?;
565 write!(f, "admin bind path: {}, ", self.adminbindpath)?;
566 write!(f, "thread count: {}, ", self.threads)?;
567 write!(
568 f,
569 "dbpath: {}, ",
570 self.db_path
571 .as_ref()
572 .map(|p| p.to_string_lossy().to_string())
573 .unwrap_or("MEMORY".to_string())
574 )?;
575 match self.db_arc_size {
576 Some(v) => write!(f, "arcsize: {v}, "),
577 None => write!(f, "arcsize: AUTO, "),
578 }?;
579 write!(f, "max request size: {}b, ", self.maximum_request)?;
580 write!(
581 f,
582 "http client address info: {}, ",
583 self.http_client_address_info
584 )?;
585 write!(
586 f,
587 "ldap client address info: {}, ",
588 self.ldap_client_address_info
589 )?;
590
591 write!(f, "with TLS: {}, ", self.tls_config.is_some())?;
592 match &self.online_backup {
593 Some(bck) => write!(
594 f,
595 "online_backup: enabled: {} - schedule: {} versions: {} path: {}, ",
596 bck.enabled,
597 bck.schedule,
598 bck.versions,
599 bck.path
600 .as_ref()
601 .map(|p| p.to_string_lossy().to_string())
602 .unwrap_or("<unset>".to_string())
603 ),
604 None => write!(f, "online_backup: disabled, "),
605 }?;
606 write!(
607 f,
608 "integration mode: {}, ",
609 self.integration_test_config.is_some()
610 )?;
611 write!(f, "log_level: {}", self.log_level)?;
612 write!(f, "role: {}, ", self.role)?;
613 match &self.repl_config {
614 Some(repl) => {
615 write!(f, "replication: enabled")?;
616 write!(f, "repl_origin: {} ", repl.origin)?;
617 write!(f, "repl_address: {} ", repl.bindaddress)?;
618 write!(
619 f,
620 "integration repl config mode: {}, ",
621 self.integration_repl_config.is_some()
622 )?;
623 }
624 None => {
625 write!(f, "replication: disabled, ")?;
626 }
627 }
628 write!(f, "otel_grpc_url: {:?}", self.otel_grpc_url)?;
629 Ok(())
630 }
631}
632
633#[derive(Debug, Clone)]
635pub struct ConfigurationBuilder {
636 bindaddress: Option<Vec<String>>,
637 ldapbindaddress: Option<Vec<String>>,
638 adminbindpath: String,
639 threads: usize,
640 db_path: Option<PathBuf>,
641 db_fs_type: Option<FsType>,
642 db_arc_size: Option<usize>,
643 migration_path: Option<PathBuf>,
644 maximum_request: usize,
645 http_client_address_info: HttpAddressInfo,
646 ldap_client_address_info: LdapAddressInfo,
647 tls_key: Option<PathBuf>,
648 tls_chain: Option<PathBuf>,
649 tls_client_ca: Option<PathBuf>,
650 online_backup: Option<OnlineBackup>,
651 domain: Option<String>,
652 origin: Option<Url>,
653 role: Option<ServerRole>,
654 log_level: Option<LogLevel>,
655 repl_config: Option<ReplicationConfiguration>,
656 otel_grpc_url: Option<String>,
657}
658
659impl ConfigurationBuilder {
660 #![allow(clippy::needless_pass_by_value)]
661 pub fn add_cli_config(mut self, cli_config: &kanidm_proto::cli::KanidmdCli) -> Self {
662 if let Some(log_level) = &cli_config.log_level {
664 self.log_level = Some(*log_level);
665 }
666
667 if let Some(otel_grpc_url) = &cli_config.otel_grpc_url {
668 self.otel_grpc_url = Some(otel_grpc_url.clone());
669 }
670
671 if let Some(domain) = &cli_config.domain {
673 self.domain = Some(domain.clone());
674 }
675
676 if let Some(origin) = &cli_config.origin {
677 self.origin = Some(origin.clone());
678 }
679
680 if cli_config.bindaddress.is_some() {
683 self.bindaddress = cli_config
684 .bindaddress
685 .clone()
686 .map(|s| s.split(',').map(|s| s.to_string()).collect())
687 }
688
689 if cli_config.ldapbindaddress.is_some() {
690 self.ldapbindaddress = cli_config
691 .ldapbindaddress
692 .clone()
693 .map(|s| s.split(',').map(|s| s.to_string()).collect())
694 }
695
696 if let Some(repl_origin) = cli_config.replication_origin.clone() {
699 if let Some(repl) = &mut self.repl_config {
700 repl.origin = repl_origin
701 } else {
702 self.repl_config = Some(ReplicationConfiguration {
703 origin: repl_origin,
704 ..Default::default()
705 });
706 }
707 }
708
709 if let Some(replication_bindaddress) = &cli_config.replication_bindaddress {
710 if let Some(repl_config) = &mut self.repl_config {
711 repl_config.bindaddress = *replication_bindaddress;
712 } else {
713 self.repl_config = Some(ReplicationConfiguration {
714 bindaddress: *replication_bindaddress,
715 ..Default::default()
716 });
717 }
718 }
719
720 if let Some(task_poll_interval) = cli_config.replication_task_poll_interval {
721 if let Some(repl) = &mut self.repl_config {
722 repl.task_poll_interval = Some(task_poll_interval);
723 } else {
724 self.repl_config = Some(ReplicationConfiguration {
725 task_poll_interval: Some(task_poll_interval),
726 ..Default::default()
727 });
728 }
729 }
730
731 if let Some(tls_key) = &cli_config.tls_key {
734 self.tls_key = Some(tls_key.clone());
735 }
736
737 if let Some(tls_chain) = &cli_config.tls_chain {
738 self.tls_chain = Some(tls_chain.clone());
739 }
740
741 if let Some(tls_client_ca) = &cli_config.tls_client_ca {
742 self.tls_client_ca = Some(tls_client_ca.clone());
743 }
744
745 if let Some(adminbindpath) = &cli_config.admin_bind_path {
747 self.adminbindpath = adminbindpath.clone();
748 }
749
750 if let Some(db_path) = &cli_config.db_path {
751 self.db_path = Some(db_path.clone());
752 }
753
754 if let Some(db_fs_type) = &cli_config.db_fs_type {
755 self.db_fs_type = Some(*db_fs_type);
756 }
757
758 if cli_config.db_arc_size.is_some() {
759 self.db_arc_size = cli_config.db_arc_size;
760 }
761
762 if let Some(online_backup_path) = &cli_config.online_backup_path {
764 if let Some(backup) = &mut self.online_backup {
765 backup.path = Some(online_backup_path.clone());
766 } else {
767 self.online_backup = Some(OnlineBackup {
768 path: Some(online_backup_path.clone()),
769 ..Default::default()
770 });
771 }
772 }
773
774 if let Some(online_backup_schedule) = &cli_config.online_backup_schedule {
775 if let Some(backup) = &mut self.online_backup {
776 backup.schedule = online_backup_schedule.clone();
777 } else {
778 self.online_backup = Some(OnlineBackup {
779 schedule: online_backup_schedule.clone(),
780 ..Default::default()
781 });
782 }
783 }
784
785 if let Some(online_backup_versions) = &cli_config.online_backup_versions {
786 if let Some(backup) = &mut self.online_backup {
787 backup.versions = *online_backup_versions;
788 } else {
789 self.online_backup = Some(OnlineBackup {
790 versions: *online_backup_versions,
791 ..Default::default()
792 });
793 }
794 }
795
796 if let Some(true) = cli_config.trust_all_x_forwarded_for {
798 self.http_client_address_info = HttpAddressInfo::XForwardForAllSourcesTrusted;
799 }
800
801 self
802 }
803
804 pub fn add_opt_toml_config(self, toml_config: Option<ServerConfigUntagged>) -> Self {
805 let Some(toml_config) = toml_config else {
807 return self;
808 };
809
810 match toml_config {
811 ServerConfigUntagged::Version(ServerConfigVersion::V2 { values }) => {
812 self.add_v2_config(values)
813 }
814 ServerConfigUntagged::Legacy(config) => self.add_legacy_config(config),
815 }
816 }
817
818 fn add_legacy_config(mut self, config: ServerConfig) -> Self {
819 if config.domain.is_some() {
820 self.domain = config.domain;
821 }
822
823 if config.origin.is_some() {
824 self.origin = config.origin;
825 }
826
827 if config.db_path.is_some() {
828 self.db_path = config.db_path;
829 }
830
831 if config.db_fs_type.is_some() {
832 self.db_fs_type = config.db_fs_type;
833 }
834
835 if config.tls_key.is_some() {
836 self.tls_key = config.tls_key;
837 }
838
839 if config.tls_chain.is_some() {
840 self.tls_chain = config.tls_chain;
841 }
842
843 if config.tls_client_ca.is_some() {
844 self.tls_client_ca = config.tls_client_ca;
845 }
846
847 if config.bindaddress.is_some() {
848 self.bindaddress = config.bindaddress.map(|a| vec![a]);
849 }
850
851 if config.ldapbindaddress.is_some() {
852 self.ldapbindaddress = config.ldapbindaddress.map(|a| vec![a]);
853 }
854
855 if let Some(adminbindpath) = config.adminbindpath {
856 self.adminbindpath = adminbindpath;
857 }
858
859 if config.role.is_some() {
860 self.role = config.role;
861 }
862
863 if config.log_level.is_some() {
864 self.log_level = config.log_level;
865 }
866
867 if let Some(threads) = config.thread_count {
868 self.threads = threads;
869 }
870
871 if let Some(maximum) = config.maximum_request_size_bytes {
872 self.maximum_request = maximum;
873 }
874
875 if config.db_arc_size.is_some() {
876 self.db_arc_size = config.db_arc_size;
877 }
878
879 if config.trust_x_forward_for == Some(true) {
880 self.http_client_address_info = HttpAddressInfo::XForwardForAllSourcesTrusted;
881 }
882
883 if config.online_backup.is_some() {
884 self.online_backup = config.online_backup;
885 }
886
887 if config.repl_config.is_some() {
888 self.repl_config = config.repl_config;
889 }
890
891 if config.otel_grpc_url.is_some() {
892 self.otel_grpc_url = config.otel_grpc_url;
893 }
894
895 self
896 }
897
898 fn add_v2_config(mut self, config: ServerConfigV2) -> Self {
899 if config.domain.is_some() {
900 self.domain = config.domain;
901 }
902
903 if config.origin.is_some() {
904 self.origin = config.origin;
905 }
906
907 if config.db_path.is_some() {
908 self.db_path = config.db_path;
909 }
910
911 if config.migration_path.is_some() {
912 self.migration_path = config.migration_path;
913 }
914
915 if config.db_fs_type.is_some() {
916 self.db_fs_type = config.db_fs_type;
917 }
918
919 if config.tls_key.is_some() {
920 self.tls_key = config.tls_key;
921 }
922
923 if config.tls_chain.is_some() {
924 self.tls_chain = config.tls_chain;
925 }
926
927 if config.tls_client_ca.is_some() {
928 self.tls_client_ca = config.tls_client_ca;
929 }
930
931 if config.bindaddress.is_some() {
932 self.bindaddress = config.bindaddress;
933 }
934
935 if config.ldapbindaddress.is_some() {
936 self.ldapbindaddress = config.ldapbindaddress;
937 }
938
939 if let Some(adminbindpath) = config.adminbindpath {
940 self.adminbindpath = adminbindpath;
941 }
942
943 if config.role.is_some() {
944 self.role = config.role;
945 }
946
947 if config.log_level.is_some() {
948 self.log_level = config.log_level;
949 }
950
951 if let Some(threads) = config.thread_count {
952 self.threads = threads;
953 }
954
955 if let Some(maximum) = config.maximum_request_size_bytes {
956 self.maximum_request = maximum;
957 }
958
959 if config.db_arc_size.is_some() {
960 self.db_arc_size = config.db_arc_size;
961 }
962
963 if let Some(http_client_address_info) = config.http_client_address_info {
964 self.http_client_address_info = http_client_address_info
965 }
966
967 if let Some(ldap_client_address_info) = config.ldap_client_address_info {
968 self.ldap_client_address_info = ldap_client_address_info
969 }
970
971 if config.online_backup.is_some() {
972 self.online_backup = config.online_backup;
973 }
974
975 if config.repl_config.is_some() {
976 self.repl_config = config.repl_config;
977 }
978
979 if config.otel_grpc_url.is_some() {
980 self.otel_grpc_url = config.otel_grpc_url;
981 }
982
983 self
984 }
985
986 pub fn is_server_mode(mut self, is_server: bool) -> Self {
988 if !is_server {
989 self.threads = 1;
990 }
991 self
992 }
993
994 pub fn finish(self) -> Option<Configuration> {
995 let ConfigurationBuilder {
996 bindaddress,
997 ldapbindaddress,
998 adminbindpath,
999 threads,
1000 db_path,
1001 db_fs_type,
1002 db_arc_size,
1003 migration_path,
1004 maximum_request,
1005 http_client_address_info,
1006 ldap_client_address_info,
1007 tls_key,
1008 tls_chain,
1009 tls_client_ca,
1010 mut online_backup,
1011 domain,
1012 origin,
1013 role,
1014 log_level,
1015 repl_config,
1016 otel_grpc_url,
1017 } = self;
1018
1019 let tls_config = match (tls_key, tls_chain, tls_client_ca) {
1020 (Some(key), Some(chain), client_ca) => Some(TlsConfiguration {
1021 chain,
1022 key,
1023 client_ca,
1024 }),
1025 _ => {
1026 eprintln!("ERROR: Tls Private Key and Certificate Chain are required.");
1027 return None;
1028 }
1029 };
1030
1031 let domain = domain.or_else(|| {
1032 eprintln!("ERROR: domain was not set.");
1033 None
1034 })?;
1035
1036 let origin = origin.or_else(|| {
1037 eprintln!("ERROR: origin was not set.");
1038 None
1039 })?;
1040
1041 if let Some(online_backup_ref) = online_backup.as_mut() {
1042 if online_backup_ref.path.is_none() {
1043 if let Some(db_path) = db_path.as_ref() {
1044 if let Some(db_parent_path) = db_path.parent() {
1045 online_backup_ref.path = Some(db_parent_path.to_path_buf());
1046 } else {
1047 eprintln!("ERROR: when db_path has no parent, and can not be used for online backups.");
1048 return None;
1049 }
1050 } else {
1051 eprintln!("ERROR: when db_path is unset (in memory) then online backup paths must be declared.");
1052 return None;
1053 }
1054 }
1055 };
1056
1057 let address = bindaddress.unwrap_or(vec![DEFAULT_SERVER_ADDRESS.to_string()]);
1059 let role = role.unwrap_or(ServerRole::WriteReplica);
1060 let log_level = log_level.unwrap_or_default();
1061
1062 Some(Configuration {
1063 address,
1064 ldapbindaddress,
1065 adminbindpath,
1066 threads,
1067 db_path,
1068 db_fs_type,
1069 db_arc_size,
1070 migration_path,
1071 maximum_request,
1072 http_client_address_info,
1073 ldap_client_address_info,
1074 tls_config,
1075 online_backup,
1076 domain,
1077 origin,
1078 role,
1079 log_level,
1080 repl_config,
1081 otel_grpc_url,
1082 integration_repl_config: None,
1083 integration_test_config: None,
1084 })
1085 }
1086}
1087
1088#[cfg(test)]
1089mod tests {
1090 use cidr::{IpCidr, Ipv4Cidr, Ipv6Cidr};
1091 use std::net::{Ipv4Addr, Ipv6Addr};
1092
1093 #[test]
1094 fn assert_cidr_parsing_behaviour() {
1095 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"127.0.0.1\"").unwrap();
1097 let expect_ip_cidr = IpCidr::from(Ipv4Addr::new(127, 0, 0, 1));
1098 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1099
1100 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"127.0.0.0/8\"").unwrap();
1101 let expect_ip_cidr = IpCidr::from(Ipv4Cidr::new(Ipv4Addr::new(127, 0, 0, 0), 8).unwrap());
1102 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1103
1104 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"2001:0db8::1\"").unwrap();
1106 let expect_ip_cidr = IpCidr::from(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x0001));
1107 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1108
1109 let parsed_ip_cidr: IpCidr = serde_json::from_str("\"2001:0db8::/64\"").unwrap();
1110 let expect_ip_cidr = IpCidr::from(
1111 Ipv6Cidr::new(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0), 64).unwrap(),
1112 );
1113 assert_eq!(parsed_ip_cidr, expect_ip_cidr);
1114 }
1115}