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>
186 lines
5 KiB
Rust
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);
|
|
}
|
|
}
|