//! Chainfire distributed KVS server use anyhow::Result; use chainfire_server::config::ServerConfig; use clap::Parser; use metrics_exporter_prometheus::PrometheusBuilder; use std::path::PathBuf; use tracing::info; /// Chainfire distributed Key-Value Store #[derive(Parser, Debug)] #[command(name = "chainfire")] #[command(author, version, about, long_about = None)] struct Args { /// Configuration file path #[arg(short, long, default_value = "chainfire.toml")] config: PathBuf, /// Node ID (overrides config) #[arg(long)] node_id: Option, /// Data directory (overrides config) #[arg(long)] data_dir: Option, /// API listen address (overrides config) #[arg(long)] api_addr: Option, /// Raft listen address (overrides config) #[arg(long)] raft_addr: Option, /// Gossip listen address (overrides config) #[arg(long)] gossip_addr: Option, /// Initial cluster members for bootstrap (comma-separated node_id=addr pairs) #[arg(long)] initial_cluster: Option, /// Enable verbose logging #[arg(short, long)] verbose: bool, /// Metrics port for Prometheus scraping #[arg(long, default_value = "9091")] metrics_port: u16, } #[tokio::main] async fn main() -> Result<()> { let args = Args::parse(); // Initialize logging let filter = if args.verbose { "chainfire=debug,tower_http=debug" } else { "chainfire=info" }; tracing_subscriber::fmt() .with_env_filter(filter) .with_target(true) .init(); info!("Chainfire v{}", env!("CARGO_PKG_VERSION")); // Initialize Prometheus metrics exporter let metrics_addr = format!("0.0.0.0:{}", args.metrics_port); let builder = PrometheusBuilder::new(); builder .with_http_listener(metrics_addr.parse::()?) .install() .expect("Failed to install Prometheus metrics exporter"); info!( "Prometheus metrics available at http://{}/metrics", metrics_addr ); // Register chainfire metrics metrics::describe_counter!( "chainfire_kv_requests_total", "Total number of KV requests by operation type" ); metrics::describe_counter!( "chainfire_kv_bytes_read", "Total bytes read from KV store" ); metrics::describe_counter!( "chainfire_kv_bytes_written", "Total bytes written to KV store" ); metrics::describe_histogram!( "chainfire_kv_request_duration_seconds", "KV request duration in seconds" ); metrics::describe_gauge!( "chainfire_raft_term", "Current Raft term" ); metrics::describe_gauge!( "chainfire_raft_is_leader", "Whether this node is the Raft leader (1=yes, 0=no)" ); metrics::describe_counter!( "chainfire_watch_events_total", "Total number of watch events emitted" ); use config::{Config as Cfg, Environment, File, FileFormat}; use toml; // Import toml for serializing defaults // ... (rest of existing imports) // Load configuration using config-rs let mut settings = Cfg::builder() // Layer 1: Application defaults. Serialize ServerConfig::default() into TOML. .add_source(File::from_str( toml::to_string(&ServerConfig::default())?.as_str(), FileFormat::Toml, )) // Layer 2: Environment variables (e.g., CHAINFIRE_NODE__ID, CHAINFIRE_NETWORK__API_ADDR) .add_source( Environment::with_prefix("CHAINFIRE") .separator("__") // Use double underscore for nested fields ); // Layer 3: Configuration file (if specified) if args.config.exists() { info!("Loading config from file: {}", args.config.display()); settings = settings.add_source(File::from(args.config.as_path())); } else { info!("Config file not found, using defaults and environment variables."); } let mut config: ServerConfig = settings .build()? .try_deserialize()?; // Apply command line overrides (Layer 4: highest precedence) if let Some(node_id) = args.node_id { config.node.id = node_id; } if let Some(data_dir) = args.data_dir { config.storage.data_dir = data_dir; } if let Some(api_addr) = args.api_addr { config.network.api_addr = api_addr.parse()?; } if let Some(raft_addr) = args.raft_addr { config.network.raft_addr = raft_addr.parse()?; } if let Some(gossip_addr) = args.gossip_addr { config.network.gossip_addr = gossip_addr.parse()?; } info!(node_id = config.node.id, "Starting node"); info!(api_addr = %config.network.api_addr, "API address"); info!(raft_addr = %config.network.raft_addr, "Raft address"); info!(gossip_addr = %config.network.gossip_addr, "Gossip address"); // Start the server let server = chainfire_server::server::Server::new(config).await?; server.run().await?; Ok(()) }