minikv 2.0.0

Distributed key-value store with Raft consensus, encryption at rest, and pluggable storage backends.
use clap::{Parser, Subcommand};
use minikv::volume::server::{VolumeServer, VolumeServerConfig};
use std::net::SocketAddr;
use std::path::PathBuf;
use std::time::Duration;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[derive(Parser)]
#[command(name = "minikv-volume")]
#[command(about = "minikv volume server")]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    Serve {
        #[arg(long)]
        id: String,

        #[arg(long, default_value = "0.0.0.0:6000")]
        bind: SocketAddr,

        #[arg(long, default_value = "0.0.0.0:6001")]
        grpc: SocketAddr,

        #[arg(long, default_value = "./vol-data")]
        data: PathBuf,

        #[arg(long, default_value = "./vol-wal")]
        wal: PathBuf,

        #[arg(long, value_delimiter = ',', required = true)]
        coordinators: Vec<String>,

        #[arg(long)]
        advertise: Option<String>,

        #[arg(long, default_value = "1000")]
        heartbeat_ms: u64,
    },
}

fn advertised_address(grpc: SocketAddr, advertise: Option<String>) -> String {
    match advertise {
        Some(addr) if addr.starts_with("http://") || addr.starts_with("https://") => addr,
        Some(addr) => format!("http://{}", addr),
        None if grpc.ip().is_unspecified() => format!("http://127.0.0.1:{}", grpc.port()),
        None => format!("http://{}", grpc),
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::registry()
        .with(
            tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()),
        )
        .with(tracing_subscriber::fmt::layer())
        .init();

    let cli = Cli::parse();

    match cli.command {
        Commands::Serve {
            id,
            bind,
            grpc,
            data,
            wal,
            coordinators,
            advertise,
            heartbeat_ms,
        } => {
            let config = VolumeServerConfig {
                volume_id: id,
                http_addr: bind,
                grpc_addr: grpc,
                advertise_grpc: advertised_address(grpc, advertise),
                data_path: data,
                wal_path: wal,
                coordinators: coordinators
                    .into_iter()
                    .map(|c| c.trim().to_string())
                    .filter(|c| !c.is_empty())
                    .collect(),
                heartbeat_interval: Duration::from_millis(heartbeat_ms.max(50)),
            };
            VolumeServer::open(config)?.serve().await?;
        }
    }

    Ok(())
}