feat(config): integrate etcd-based configuration management

- Add etcd-client dependency with TLS support
- Implement EtcdConfig struct for reading config values with priority: etcd > env > default
- Add ServiceRegistry for service discovery registration in etcd
- Create from_etcd method in AppConfig for loading SMTP configuration
- Update main.rs to use etcd-based config loading with fallback mechanism
- Add etcd module with client connection and key-value operations
- Modify Dockerfile to use cargo-chef for faster builds
- Add docker-compose.yaml for emailks service deployment
- Include AGENTS.md with development guidelines and best practices
- Add build.sh script for podman-based container building
- Update dependencies in Cargo.toml and Cargo.lock
This commit is contained in:
zhenyi
2026-06-12 16:21:04 +08:00
parent c4824ef261
commit 7b8d0714e7
10 changed files with 759 additions and 51 deletions
+37
View File
@@ -54,6 +54,43 @@ pub enum SmtpTls {
}
impl AppConfig {
pub fn from_etcd(
host: String, port: u16, username: String, password: String,
from_email: String, from_name: String, reply_to: String,
tls: String, timeout_secs: u64, helo_name: String, allow_request_from: bool,
queue_capacity: Option<usize>,
listen_addr_str: &str,
) -> Result<Self, ConfigError> {
let tls = match tls.trim().to_ascii_lowercase().as_str() {
"none" | "false" | "0" => SmtpTls::None,
"starttls" | "start_tls" | "start-tls" => SmtpTls::StartTls,
"tls" | "ssl" | "smtps" => SmtpTls::Tls,
_ => SmtpTls::StartTls,
};
validate_port("PORT", port)?;
Ok(Self {
smtp: SmtpConfig {
host,
port,
username: if username.is_empty() { None } else { Some(username) },
password: if password.is_empty() { None } else { Some(password) },
from_email: if from_email.is_empty() { None } else { Some(from_email) },
from_name: if from_name.is_empty() { None } else { Some(from_name) },
reply_to: if reply_to.is_empty() { None } else { Some(reply_to) },
tls,
timeout: std::time::Duration::from_secs(timeout_secs),
helo_name: if helo_name.is_empty() { None } else { Some(helo_name) },
allow_request_from,
},
queue_capacity,
listen_addr: listen_addr_str
.parse::<std::net::SocketAddr>()
.map_err(|e: std::net::AddrParseError| ConfigError::InvalidEnv { name: "LISTEN_ADDR", reason: e.to_string() })?,
})
}
pub fn from_env() -> Result<Self, ConfigError> {
let _ = dotenvy::dotenv();