//! Server configuration use anyhow::Result; use chainfire_types::RaftRole; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; use std::path::{Path, PathBuf}; /// Server configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerConfig { /// Node configuration pub node: NodeConfig, /// Storage configuration pub storage: StorageConfig, /// Network configuration pub network: NetworkConfig, /// Cluster configuration pub cluster: ClusterConfig, /// Raft configuration #[serde(default)] pub raft: RaftConfig, } /// Node-specific configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NodeConfig { /// Unique node ID pub id: u64, /// Human-readable name pub name: String, /// Node role (control_plane or worker) pub role: String, } /// Storage configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StorageConfig { /// Data directory pub data_dir: PathBuf, } /// Network configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NetworkConfig { /// API listen address (gRPC) pub api_addr: SocketAddr, /// HTTP REST API listen address #[serde(default = "default_http_addr")] pub http_addr: SocketAddr, /// Raft listen address pub raft_addr: SocketAddr, /// Gossip listen address (UDP) pub gossip_addr: SocketAddr, /// TLS configuration (optional) #[serde(default)] pub tls: Option, } fn default_http_addr() -> SocketAddr { "127.0.0.1:8081".parse().unwrap() } /// TLS configuration for gRPC servers #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TlsConfig { /// Path to server certificate file (PEM format) pub cert_file: String, /// Path to server private key file (PEM format) pub key_file: String, /// Path to CA certificate file for client verification (optional, enables mTLS) pub ca_file: Option, /// Require client certificates (mTLS mode) #[serde(default)] pub require_client_cert: bool, } /// Cluster configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClusterConfig { /// Cluster ID pub id: u64, /// Initial cluster members pub initial_members: Vec, /// Whether to bootstrap a new cluster pub bootstrap: bool, } /// Cluster member configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MemberConfig { /// Node ID pub id: u64, /// Raft address pub raft_addr: String, } /// Raft-specific configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RaftConfig { /// Raft participation role: "voter", "learner", or "none" /// /// - `voter`: Full voting member in Raft consensus /// - `learner`: Non-voting replica that receives log replication /// - `none`: No Raft participation, node acts as agent/proxy only #[serde(default)] pub role: RaftRole, } impl Default for RaftConfig { fn default() -> Self { Self { role: RaftRole::Voter, } } } impl Default for ServerConfig { fn default() -> Self { Self { node: NodeConfig { id: 1, name: "chainfire-1".into(), role: "control_plane".into(), }, storage: StorageConfig { data_dir: PathBuf::from("./data"), }, network: NetworkConfig { api_addr: "127.0.0.1:2379".parse().unwrap(), http_addr: "127.0.0.1:8081".parse().unwrap(), raft_addr: "127.0.0.1:2380".parse().unwrap(), gossip_addr: "127.0.0.1:2381".parse().unwrap(), tls: None, }, cluster: ClusterConfig { id: 1, initial_members: vec![], bootstrap: true, }, raft: RaftConfig::default(), } } } impl ServerConfig { /// Load configuration from a file pub fn load(path: &Path) -> Result { let contents = std::fs::read_to_string(path)?; let config: ServerConfig = toml::from_str(&contents)?; Ok(config) } /// Save configuration to a file pub fn save(&self, path: &Path) -> Result<()> { let contents = toml::to_string_pretty(self)?; std::fs::write(path, contents)?; Ok(()) } } #[cfg(test)] mod tests { use super::*; use tempfile::tempdir; #[test] fn test_default_config() { let config = ServerConfig::default(); assert_eq!(config.node.id, 1); assert!(config.cluster.bootstrap); } #[test] fn test_config_roundtrip() { let dir = tempdir().unwrap(); let path = dir.path().join("config.toml"); let config = ServerConfig::default(); config.save(&path).unwrap(); let loaded = ServerConfig::load(&path).unwrap(); assert_eq!(loaded.node.id, config.node.id); assert_eq!(loaded.network.api_addr, config.network.api_addr); } }