- netboot-base.nix with SSH key auth - Launch scripts for node01/02/03 - Node configuration.nix and disko.nix - Nix modules for first-boot automation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
170 lines
5 KiB
Rust
170 lines
5 KiB
Rust
//! 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<u64>,
|
|
|
|
/// Data directory (overrides config)
|
|
#[arg(long)]
|
|
data_dir: Option<PathBuf>,
|
|
|
|
/// API listen address (overrides config)
|
|
#[arg(long)]
|
|
api_addr: Option<String>,
|
|
|
|
/// Raft listen address (overrides config)
|
|
#[arg(long)]
|
|
raft_addr: Option<String>,
|
|
|
|
/// Gossip listen address (overrides config)
|
|
#[arg(long)]
|
|
gossip_addr: Option<String>,
|
|
|
|
/// Initial cluster members for bootstrap (comma-separated node_id=addr pairs)
|
|
#[arg(long)]
|
|
initial_cluster: Option<String>,
|
|
|
|
/// 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::<std::net::SocketAddr>()?)
|
|
.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(())
|
|
}
|