photoncloud-monorepo/chainfire/crates/chainfire-server/src/config.rs
centra 3eeb303dcb feat: Batch commit for T039.S3 deployment
Includes all pending changes needed for nixos-anywhere:
- fiberlb: L7 policy, rule, certificate types
- deployer: New service for cluster management
- nix-nos: Generic network modules
- Various service updates and fixes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 04:34:51 +09:00

186 lines
5 KiB
Rust

//! 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<TlsConfig>,
}
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<String>,
/// 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<MemberConfig>,
/// 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<Self> {
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);
}
}