feat(config): split apihost/email/email_queue/githost config sections
ZhenYi committed
Modified config/default.toml config/default.toml +33 −2 View config/default.toml
@@ -164,7 +164,7 @@
164164[git.ssh]
165165bind_addr = "0.0.0.0"
166166port = 22
167host_key_path = "/etc/gitdata/ssh_host_ed25519"
167host_key_path = "/opt/gitdata/ssh_host_ed25519"
168168banner = ""
169169max_sessions = 64
170170auth_rejection_time = 2
@@ -183,4 +183,35 @@
183183from_address = ""
184184from_name = ""
185185reply_to = ""
186request_timeout_secs = 30
No newline at end of file
186request_timeout_secs = 30
187
188[email]
189enabled = false
190smtp_host = "smtp.example.com"
191smtp_port = 587
192smtp_username = ""
193smtp_password = ""
194from_address = "[email protected]"
195from_name = "GitData"
196connection_timeout_ms = 5000
197response_timeout_ms = 30000
198max_attachment_bytes = 26214400
199default_locale = "en"
200template_dir = "config/email_templates"
201
202[email_queue]
203enabled = false
204channel_prefix = "email"
205subscriber_group_id = "email-worker"
206max_retries = 3
207retry_backoff_ms = 500
208
209[apihost]
210enabled = true
211bind_addr = "0.0.0.0"
212port = 3000
213
214[githost]
215enabled = true
216http_bind_addr = "0.0.0.0"
217http_port = 9418
No newline at end of file
Added lib/config/src/apihost.rs lib/config/src/apihost.rs +39 −0 View lib/config/src/apihost.rs
@@ -0,0 +1,39 @@
1use std::net::IpAddr;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Default, Serialize, Deserialize)]
6#[serde(default)]
7pub struct ApihostConfig {
8 pub enabled: Option<bool>,
9 pub bind_addr: Option<String>,
10 pub port: Option<u16>,
11}
12
13impl ApihostConfig {
14 pub fn enabled(&self) -> bool {
15 self.enabled.unwrap_or(true)
16 }
17
18 pub fn bind_addr(&self) -> &str {
19 self.bind_addr.as_deref().unwrap_or("0.0.0.0")
20 }
21
22 pub fn port(&self) -> u16 {
23 self.port.unwrap_or(3000)
24 }
25
26 pub fn is_valid(&self) -> Result<(), String> {
27 if !self.enabled() {
28 return Ok(());
29 }
30 let bind = self.bind_addr();
31 if bind.parse::<IpAddr>().is_err() {
32 return Err(format!("apihost.bind_addr is not a valid IP: {bind:?}"));
33 }
34 if self.port == Some(0) {
35 return Err("apihost.port must be greater than 0".to_string());
36 }
37 Ok(())
38 }
39}
No newline at end of file
Added lib/config/src/email.rs lib/config/src/email.rs +102 −0 View lib/config/src/email.rs
@@ -0,0 +1,102 @@
1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Default, Serialize, Deserialize)]
6#[serde(default)]
7pub struct EmailConfig {
8 pub enabled: Option<bool>,
9 pub smtp_host: Option<String>,
10 pub smtp_port: Option<u16>,
11 pub smtp_username: Option<String>,
12 pub smtp_password: Option<String>,
13 pub from_address: Option<String>,
14 pub from_name: Option<String>,
15 pub connection_timeout_ms: Option<u64>,
16 pub response_timeout_ms: Option<u64>,
17 pub max_attachment_bytes: Option<usize>,
18 pub default_locale: Option<String>,
19 pub template_dir: Option<PathBuf>,
20}
21
22impl EmailConfig {
23 pub fn enabled(&self) -> bool {
24 self.enabled.unwrap_or(true)
25 }
26
27 pub fn smtp_host(&self) -> &str {
28 self.smtp_host.as_deref().unwrap_or("")
29 }
30
31 pub fn smtp_port(&self) -> u16 {
32 self.smtp_port.unwrap_or(587)
33 }
34
35 pub fn smtp_username(&self) -> Option<&str> {
36 self.smtp_username.as_deref().filter(|s| !s.is_empty())
37 }
38
39 pub fn smtp_password(&self) -> Option<&str> {
40 self.smtp_password.as_deref().filter(|s| !s.is_empty())
41 }
42
43 pub fn from_address(&self) -> Option<&str> {
44 self.from_address.as_deref().filter(|s| !s.is_empty())
45 }
46
47 pub fn from_name(&self) -> Option<&str> {
48 self.from_name.as_deref().filter(|s| !s.is_empty())
49 }
50
51 pub fn connection_timeout_ms(&self) -> u64 {
52 self.connection_timeout_ms.unwrap_or(5_000)
53 }
54
55 pub fn response_timeout_ms(&self) -> u64 {
56 self.response_timeout_ms.unwrap_or(30_000)
57 }
58
59 pub fn max_attachment_bytes(&self) -> usize {
60 self.max_attachment_bytes.unwrap_or(26_214_400)
61 }
62
63 pub fn default_locale(&self) -> &str {
64 self.default_locale.as_deref().unwrap_or("en")
65 }
66
67 pub fn template_dir(&self) -> Option<&PathBuf> {
68 self.template_dir.as_ref()
69 }
70
71 pub fn is_valid(&self) -> Result<(), String> {
72 if !self.enabled() {
73 return Ok(());
74 }
75 let host = self.smtp_host();
76 if host.is_empty() {
77 return Err("email.smtp_host must be set when enabled".to_string());
78 }
79 let port = self.smtp_port();
80 if port == 0 {
81 return Err("email.smtp_port must be greater than 0".to_string());
82 }
83 match self.from_address() {
84 None => return Err("email.from_address must be set when enabled".to_string()),
85 Some(addr) => {
86 if !addr.contains('@') {
87 return Err(format!("email.from_address is invalid: {addr:?}"));
88 }
89 }
90 }
91 if self.from_name.as_deref().is_some_and(|s| s.is_empty()) {
92 return Err("email.from_name must not be empty".to_string());
93 }
94 if matches!(self.connection_timeout_ms, Some(0)) {
95 return Err("email.connection_timeout_ms must be greater than 0".to_string());
96 }
97 if matches!(self.response_timeout_ms, Some(0)) {
98 return Err("email.response_timeout_ms must be greater than 0".to_string());
99 }
100 Ok(())
101 }
102}
No newline at end of file
Added lib/config/src/email_queue.rs lib/config/src/email_queue.rs +62 −0 View lib/config/src/email_queue.rs
@@ -0,0 +1,62 @@
1use std::time::Duration;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Default, Serialize, Deserialize)]
6#[serde(default)]
7pub struct EmailQueueConfig {
8 pub enabled: Option<bool>,
9 pub channel_prefix: Option<String>,
10 pub subscriber_group_id: Option<String>,
11 pub max_retries: Option<u32>,
12 pub retry_backoff_ms: Option<u64>,
13}
14
15impl EmailQueueConfig {
16 pub fn enabled(&self) -> bool {
17 self.enabled.unwrap_or(true)
18 }
19
20 pub fn channel_prefix(&self) -> &str {
21 self.channel_prefix.as_deref().unwrap_or("email")
22 }
23
24 pub fn subscriber_group_id(&self) -> &str {
25 self.subscriber_group_id
26 .as_deref()
27 .unwrap_or("email-worker")
28 }
29
30 pub fn max_retries(&self) -> u32 {
31 self.max_retries.unwrap_or(3)
32 }
33
34 pub fn retry_backoff(&self) -> Duration {
35 Duration::from_millis(self.retry_backoff_ms.unwrap_or(500))
36 }
37
38 pub fn channel(&self, name: &str) -> String {
39 format!("{}.{}", self.channel_prefix(), name)
40 }
41
42 pub fn is_valid(&self) -> Result<(), String> {
43 if !self.enabled() {
44 return Ok(());
45 }
46 if self.channel_prefix.as_deref().is_some_and(|s| s.is_empty()) {
47 return Err("email_queue.channel_prefix must not be empty".to_string());
48 }
49 if self.subscriber_group_id.as_deref().is_some_and(|s| s.is_empty()) {
50 return Err(
51 "email_queue.subscriber_group_id must not be empty when enabled".to_string(),
52 );
53 }
54 if matches!(self.max_retries, Some(0)) {
55 return Err("email_queue.max_retries must be greater than 0".to_string());
56 }
57 if matches!(self.retry_backoff_ms, Some(0)) {
58 return Err("email_queue.retry_backoff_ms must be greater than 0".to_string());
59 }
60 Ok(())
61 }
62}
No newline at end of file
Modified lib/config/src/git.rs lib/config/src/git.rs +1 −1 View lib/config/src/git.rs
@@ -73,7 +73,7 @@
7373 Self {
7474 bind_addr: "0.0.0.0".to_string(),
7575 port: 22,
76 host_key_path: "/etc/gitdata/ssh_host_ed25519".to_string(),
76 host_key_path: "/opt/gitdata/ssh_host_ed25519".to_string(),
7777 banner: None,
7878 max_sessions: 64,
7979 auth_rejection_time: Duration::from_secs(2),
Added lib/config/src/githost.rs lib/config/src/githost.rs +35 −0 View lib/config/src/githost.rs
@@ -0,0 +1,35 @@
1use std::net::IpAddr;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Default, Serialize, Deserialize)]
6#[serde(default)]
7pub struct GithostConfig {
8 pub enabled: Option<bool>,
9 pub http_bind_addr: Option<IpAddr>,
10 pub http_port: Option<u16>,
11}
12
13impl GithostConfig {
14 pub fn enabled(&self) -> bool {
15 self.enabled.unwrap_or(true)
16 }
17
18 pub fn http_bind_addr(&self) -> IpAddr {
19 self.http_bind_addr.unwrap_or_else(|| "0.0.0.0".parse().expect("static ip"))
20 }
21
22 pub fn http_port(&self) -> u16 {
23 self.http_port.unwrap_or(9418)
24 }
25
26 pub fn is_valid(&self) -> Result<(), String> {
27 if !self.enabled() {
28 return Ok(());
29 }
30 if self.http_port() == 0 {
31 return Err("githost.http_port must be greater than 0".to_string());
32 }
33 Ok(())
34 }
35}
No newline at end of file
Modified lib/config/src/lib.rs lib/config/src/lib.rs +31 −16 View lib/config/src/lib.rs
@@ -1,19 +1,3 @@
1use crate::auth::AuthConfig;
2use crate::cache::CacheConfig;
3use crate::database::DatabaseConfig;
4use crate::git::GitConfig;
5use crate::issues::IssueConfig;
6use crate::mailbaby::MailbabyConfig;
7use crate::pow::PowConfig;
8use crate::redis::RedisConfig;
9use crate::s3::S3Config;
10use crate::session::SessionConfig;
11use crate::space::SpaceConfig;
12use crate::user::UserConfig;
13use crate::web::WebConfig;
14
15use serde::Deserialize;
16
171pub mod auth;
182pub mod s3;
193pub mod redis;
@@ -27,12 +11,35 @@
2711pub mod space;
2812pub mod issues;
2913pub mod git;
14pub mod email;
15pub mod email_queue;
16pub mod apihost;
17pub mod githost;
3018
3119pub mod error;
3220pub mod loader;
3321
3422pub use error::ConfigError;
3523
24use crate::apihost::ApihostConfig;
25use crate::auth::AuthConfig;
26use crate::cache::CacheConfig;
27use crate::database::DatabaseConfig;
28use crate::email::EmailConfig;
29use crate::email_queue::EmailQueueConfig;
30use crate::githost::GithostConfig;
31use crate::git::GitConfig;
32use crate::issues::IssueConfig;
33use crate::mailbaby::MailbabyConfig;
34use crate::pow::PowConfig;
35use crate::redis::RedisConfig;
36use crate::s3::S3Config;
37use crate::session::SessionConfig;
38use crate::space::SpaceConfig;
39use crate::user::UserConfig;
40use crate::web::WebConfig;
41use serde::Deserialize;
42
3643#[derive(Clone, Debug, Default, Deserialize)]
3744#[serde(default)]
3845pub struct AppConfig {
@@ -49,6 +56,10 @@
4956 pub issues: IssueConfig,
5057 pub git: GitConfig,
5158 pub mailbaby: MailbabyConfig,
59 pub email: EmailConfig,
60 pub email_queue: EmailQueueConfig,
61 pub apihost: ApihostConfig,
62 pub githost: GithostConfig,
5263}
5364
5465impl AppConfig {
@@ -63,6 +74,10 @@
6374 self.auth.validate().map_err(ConfigError::Validation)?;
6475 self.cache.validate().map_err(ConfigError::Validation)?;
6576 self.mailbaby.validate().map_err(ConfigError::Validation)?;
77 self.email.is_valid().map_err(ConfigError::Validation)?;
78 self.email_queue.is_valid().map_err(ConfigError::Validation)?;
79 self.apihost.is_valid().map_err(ConfigError::Validation)?;
80 self.githost.is_valid().map_err(ConfigError::Validation)?;
6681 Ok(())
6782 }
6883}
No newline at end of file