1 parent
698e3c4a1b
commit
e3e844ef4f
31 files changed
+287
-75
No files matched your search
@@ -46,6 +46,9 @@ pub struct AppConfig {
|
||||
#[serde(default)]
|
||||
pub log_dir: Option<String>,
|
||||
|
||||
#[serde(default = "default_log_level")]
|
||||
pub log_level: String,
|
||||
|
||||
#[serde(default = "default_database_backend")]
|
||||
pub database_backend: String,
|
||||
|
||||
@@ -76,6 +79,12 @@ pub struct AppConfig {
|
||||
#[serde(default)]
|
||||
pub cluster_mode: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub cluster_shared_data_dir: bool,
|
||||
|
||||
#[serde(default = "default_search_index_backend")]
|
||||
pub search_index_backend: String,
|
||||
|
||||
#[serde(default = "default_session_timeout_hours")]
|
||||
pub session_timeout_hours: u64,
|
||||
|
||||
@@ -118,6 +127,9 @@ pub struct AppConfig {
|
||||
#[serde(default)]
|
||||
pub outgoing_headers: Vec<String>,
|
||||
|
||||
#[serde(default = "default_pgp_enabled")]
|
||||
pub pgp_enabled: bool,
|
||||
|
||||
#[serde(default = "default_pgp_wkd_enabled")]
|
||||
pub pgp_wkd_enabled: bool,
|
||||
|
||||
@@ -199,6 +211,10 @@ fn default_data_dir() -> String {
|
||||
"/data".to_string()
|
||||
}
|
||||
|
||||
fn default_log_level() -> String {
|
||||
"info".to_string()
|
||||
}
|
||||
|
||||
fn default_database_backend() -> String {
|
||||
"sqlite".to_string()
|
||||
}
|
||||
@@ -207,6 +223,10 @@ fn default_session_backend() -> String {
|
||||
"database".to_string()
|
||||
}
|
||||
|
||||
fn default_search_index_backend() -> String {
|
||||
"tantivy".to_string()
|
||||
}
|
||||
|
||||
fn default_session_timeout_hours() -> u64 {
|
||||
24
|
||||
}
|
||||
@@ -242,6 +262,10 @@ fn default_sieve_timeout_secs() -> u64 {
|
||||
10
|
||||
}
|
||||
|
||||
fn default_pgp_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_pgp_wkd_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
@@ -270,6 +294,7 @@ impl Default for AppConfig {
|
||||
max_message_size_mb: default_max_message_size_mb(),
|
||||
data_dir: default_data_dir(),
|
||||
log_dir: None,
|
||||
log_level: default_log_level(),
|
||||
database_backend: default_database_backend(),
|
||||
database_url: None,
|
||||
session_backend: default_session_backend(),
|
||||
@@ -280,6 +305,8 @@ impl Default for AppConfig {
|
||||
redis_password: None,
|
||||
redis_sentinel_password: None,
|
||||
cluster_mode: false,
|
||||
cluster_shared_data_dir: false,
|
||||
search_index_backend: default_search_index_backend(),
|
||||
session_timeout_hours: default_session_timeout_hours(),
|
||||
remember_me_enabled: default_remember_me_enabled(),
|
||||
remember_me_days: default_remember_me_days(),
|
||||
@@ -294,6 +321,7 @@ impl Default for AppConfig {
|
||||
sieve_timeout_secs: default_sieve_timeout_secs(),
|
||||
trusted_proxies: None,
|
||||
outgoing_headers: Vec::new(),
|
||||
pgp_enabled: default_pgp_enabled(),
|
||||
pgp_wkd_enabled: default_pgp_wkd_enabled(),
|
||||
pgp_hkp_enabled: false,
|
||||
pgp_hkp_url: default_pgp_hkp_url(),
|
||||
@@ -446,6 +474,16 @@ impl AppConfig {
|
||||
return Err("LOG_DIR / log_dir must be an absolute path".to_string());
|
||||
}
|
||||
}
|
||||
let log_level = self.log_level.trim().to_ascii_lowercase();
|
||||
if !matches!(
|
||||
log_level.as_str(),
|
||||
"trace" | "debug" | "info" | "warn" | "error" | "off"
|
||||
) {
|
||||
return Err(
|
||||
"LOG_LEVEL / log_level must be one of: trace, debug, info, warn, error, off"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if self.max_attachment_size_mb == 0 || self.max_attachment_size_mb > 512 {
|
||||
return Err("MAX_ATTACHMENT_SIZE_MB must be between 1 and 512".to_string());
|
||||
}
|
||||
@@ -492,12 +530,33 @@ impl AppConfig {
|
||||
return Err("Redis sessions require REDIS_URL or REDIS_SENTINEL_NODES + REDIS_SENTINEL_MASTER".to_string());
|
||||
}
|
||||
}
|
||||
if !matches!(self.search_index_backend.as_str(), "tantivy" | "disabled") {
|
||||
return Err("SEARCH_INDEX_BACKEND must be one of: tantivy, disabled".to_string());
|
||||
}
|
||||
if self.cluster_mode && self.database_backend == "sqlite" {
|
||||
return Err(
|
||||
"CLUSTER_MODE requires a shared mysql/pgsql application database; Redis only replaces session persistence"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if self.cluster_mode && !self.cluster_shared_data_dir {
|
||||
return Err(
|
||||
"CLUSTER_MODE requires CLUSTER_SHARED_DATA_DIR=true after data_dir is mounted as shared storage on every node; draft attachments are stored below data_dir"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if self.cluster_mode && self.search_index_backend != "disabled" {
|
||||
return Err(
|
||||
"CLUSTER_MODE requires SEARCH_INDEX_BACKEND=disabled; the built-in Tantivy index is local and does not coordinate writers across nodes"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if self.cluster_mode && self.pgp_enabled {
|
||||
return Err(
|
||||
"CLUSTER_MODE requires PGP_ENABLED=false; OpenPGP private keys use node-local filesystem storage"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if (self.cluster_mode
|
||||
|| self.session_backend == "redis"
|
||||
|| self.database_backend != "sqlite")
|
||||
|
||||
Reference in new issue
Block a user