1use std::net::IpAddr;
6use std::path::{Path, PathBuf};
7
8use serde::{Deserialize, Serialize};
9
10use crate::error::{Error, Result};
11use crate::smtp::tls::TlsConfig;
12
13#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
16pub enum BindHost {
17 Loopback,
19 AllInterfaces,
21}
22
23impl BindHost {
24 pub fn as_ip(self) -> IpAddr {
25 match self {
26 BindHost::Loopback => IpAddr::from([127, 0, 0, 1]),
27 BindHost::AllInterfaces => IpAddr::from([0, 0, 0, 0]),
28 }
29 }
30}
31
32#[derive(Debug, Clone)]
34pub struct CoreConfig {
35 pub data_dir: PathBuf,
36 pub db_path: PathBuf,
37 pub blobs_dir: PathBuf,
38 pub default_smtp_port: u16,
39 pub http_port: u16,
40 pub bind_host: BindHost,
41 pub max_message_bytes: u64,
42 pub ephemeral_port_range: (u16, u16),
43 pub ehlo_hostname: String,
45 pub smtp_max_line_bytes: usize,
47 pub data_spill_bytes: usize,
49 pub ingest_channel_capacity: usize,
51 pub tls: TlsConfig,
54}
55
56impl CoreConfig {
57 pub fn for_data_dir(data_dir: impl Into<PathBuf>) -> Result<Self> {
59 let data_dir = data_dir.into();
60 let db_path = data_dir.join("postcrate.sqlite");
61 let blobs_dir = data_dir.join("blobs");
62 Ok(Self {
63 data_dir,
64 db_path,
65 blobs_dir,
66 default_smtp_port: 1025,
67 http_port: 1080,
68 bind_host: BindHost::Loopback,
69 max_message_bytes: 50 * 1024 * 1024,
70 ephemeral_port_range: (1100, 1199),
71 ehlo_hostname: "postcrate.local".to_string(),
72 smtp_max_line_bytes: 1000,
73 data_spill_bytes: 256 * 1024,
74 ingest_channel_capacity: 1024,
75 tls: TlsConfig::default(),
76 })
77 }
78
79 pub fn default_data_dir() -> Result<PathBuf> {
81 if let Ok(home) = std::env::var("HOME") {
85 #[cfg(target_os = "macos")]
86 {
87 let p = Path::new(&home).join("Library/Application Support/Postcrate");
88 return Ok(p);
89 }
90 #[cfg(target_os = "linux")]
91 {
92 if let Ok(xdg) = std::env::var("XDG_DATA_HOME") {
93 return Ok(Path::new(&xdg).join("postcrate"));
94 }
95 return Ok(Path::new(&home).join(".local/share/postcrate"));
96 }
97 #[cfg(not(any(target_os = "macos", target_os = "linux")))]
98 {
99 return Ok(Path::new(&home).join(".postcrate"));
100 }
101 }
102 if let Ok(appdata) = std::env::var("APPDATA") {
103 return Ok(Path::new(&appdata).join("Postcrate"));
104 }
105 Err(Error::Internal(
106 "could not resolve a default data directory; pass one explicitly".into(),
107 ))
108 }
109
110 pub(crate) fn raw_dir(&self) -> PathBuf {
111 self.blobs_dir.join("raw")
112 }
113
114 pub(crate) fn incoming_dir(&self) -> PathBuf {
115 self.blobs_dir.join("raw").join("incoming")
116 }
117
118 pub(crate) fn att_dir(&self) -> PathBuf {
119 self.blobs_dir.join("att")
120 }
121
122 pub(crate) async fn ensure_dirs(&self) -> Result<()> {
123 for dir in [
124 &self.data_dir,
125 &self.blobs_dir,
126 &self.raw_dir(),
127 &self.incoming_dir(),
128 &self.att_dir(),
129 ] {
130 tokio::fs::create_dir_all(dir).await?;
131 }
132 Ok(())
133 }
134}