kitsune2_bootstrap_srv 0.5.0-dev.6

p2p / dht communication WAN discovery bootstrapping server
Documentation
//! The binary kitsune2-bootstrap-srv.

use kitsune2_bootstrap_srv::*;

#[derive(clap::Parser, Debug)]
#[command(version)]
pub struct Args {
    /// By default, kitsune2-boot-srv runs in "testing" configuration
    /// with much lighter resource usage settings. This testing mode
    /// should be more than enough for most developer application testing
    /// and continuous integration or automated tests.
    ///
    /// To set up the server to be ready to use most of the resources available
    /// on a single given machine, you can set this "production" mode.
    #[arg(long)]
    pub production: bool,

    /// Output tracing in json format.
    #[arg(long)]
    pub json: bool,

    /// The address(es) at which to listen.
    ///
    /// Defaults: testing = "[127.0.0.1:0]", production = "[0.0.0.0:443, [::]:443]"
    #[arg(long)]
    pub listen: Vec<std::net::SocketAddr>,

    /// The path to a TLS certificate file.
    ///
    /// The certificate must be PEM encoded.
    #[arg(long, requires = "tls_key")]
    pub tls_cert: Option<std::path::PathBuf>,

    /// The path to a TLS key file.
    ///
    /// The key must be PEM encoded.
    #[arg(long, requires = "tls_cert")]
    pub tls_key: Option<std::path::PathBuf>,

    /// The number of worker threads to use.
    ///
    /// Defaults: testing = 2, production = 4 * cpu_count
    #[arg(long)]
    pub worker_thread_count: Option<usize>,

    /// The maximum agent info entry count per space.
    ///
    /// Defaults: testing = 32, production = 32
    #[arg(long)]
    pub max_entries_per_space: Option<usize>,

    /// The number of milliseconds that worker threads will block waiting for incoming connections
    /// before checking to see if the server is shutting down.
    ///
    /// Defaults: testing = 10ms, production = 2s
    #[arg(long)]
    pub request_listen_duration_ms: Option<u32>,

    /// The interval at which expired agents are purged from the cache.
    ///
    /// Defaults: testing = 10s, production = 60s
    #[arg(long)]
    pub prune_interval_ms: Option<u32>,

    /// The allowed origins for CORS requests.
    ///
    /// If `None`, defaults to allowing any origin.
    #[arg(long)]
    pub allowed_origins: Option<Vec<String>>,

    /// If specified, this will enable exporting metrics to an OpenTelemetry endpoint.
    #[arg(long)]
    pub otlp_endpoint: Option<String>,

    /// The authentication "Hook Server" as defined by
    /// <https://github.com/holochain/sbd/blob/main/spec-auth.md>
    #[arg(long)]
    pub authentication_hook_server: Option<String>,

    /// The address on which the QUIC Address Discovery (QAD) server should bind.
    ///
    /// QAD allows iroh clients to discover their public IP address via a QUIC
    /// connection. When TLS cert/key files are configured they are used for
    /// QUIC; otherwise a self-signed certificate is generated for local
    /// development and testing.
    ///
    /// Default port is 7842 (iroh's default for QAD).
    #[arg(long)]
    pub quic_bind_addr: Option<std::net::SocketAddr>,

    /// Sustained inbound byte rate per relay client connection (bytes/s).
    ///
    /// When set, the bootstrap server's embedded iroh relay handler will
    /// pace inbound WebSocket frames per connection. Use together with
    /// `--relay-client-rx-burst-bytes` to control burst allowance; if
    /// burst is omitted it defaults to about one tenth of this value.
    ///
    /// Default: unlimited.
    #[cfg(feature = "iroh-relay")]
    #[arg(long)]
    pub relay_client_rx_bytes_per_second: Option<std::num::NonZeroU32>,

    /// Maximum allowed inbound burst per relay client connection (bytes).
    ///
    /// Has effect only when `--relay-client-rx-bytes-per-second` is also
    /// set.
    ///
    /// Default: derived as bps/10 when the sustained rate is set.
    #[cfg(feature = "iroh-relay")]
    #[arg(long)]
    pub relay_client_rx_burst_bytes: Option<std::num::NonZeroU32>,
}

fn main() {
    let args = <Args as clap::Parser>::parse();

    let t = tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::builder()
                .with_default_directive(tracing::Level::DEBUG.into())
                .from_env_lossy(),
        )
        .with_file(true)
        .with_line_number(true);

    if args.json {
        t.json().try_init()
    } else {
        t.try_init()
    }
    .expect("failed to init tracing");

    let mut config = if args.production {
        Config::production()
    } else {
        Config::testing()
    };

    // Install the crypto provider if TLS certs are provided, or if iroh-relay
    // feature is enabled (iroh-relay uses rustls internally and needs the provider).
    if args.tls_cert.is_some()
        || args.tls_key.is_some()
        || cfg!(feature = "iroh-relay")
    {
        rustls::crypto::ring::default_provider()
            .install_default()
            .expect("Failed to configure default TLS provider");
    }

    // Apply bootstrap command line arguments
    config.tls_cert = args.tls_cert;
    config.tls_key = args.tls_key;
    if !args.listen.is_empty() {
        config.listen_address_list = args.listen;
    }
    if let Some(count) = args.worker_thread_count {
        config.worker_thread_count = count;
    }
    if let Some(count) = args.max_entries_per_space {
        config.max_entries_per_space = count;
    }
    if let Some(ms) = args.request_listen_duration_ms {
        config.request_listen_duration =
            std::time::Duration::from_millis(ms as u64);
    }
    if let Some(ms) = args.prune_interval_ms {
        config.prune_interval = std::time::Duration::from_millis(ms as u64);
    }
    if let Some(allowed_origins) = args.allowed_origins {
        config.allowed_origins = Some(allowed_origins);
    }

    // Setup opentelemetry metrics
    let meter_provider =
        metrics::enable_otlp_metrics(args.otlp_endpoint.as_deref())
            .expect("Failed to initialize OTLP metrics");

    #[cfg(feature = "iroh-relay")]
    {
        if let Some(quic_bind_addr) = args.quic_bind_addr {
            config.quic_bind_addr = Some(quic_bind_addr);
        }
        if let Some(bps) = args.relay_client_rx_bytes_per_second {
            config.relay_client_rx_bytes_per_second = Some(bps);
        }
        if let Some(burst) = args.relay_client_rx_burst_bytes {
            config.relay_client_rx_burst_bytes = Some(burst);
        }
    }

    config.auth.authentication_hook_server = args.authentication_hook_server;

    tracing::info!(?config);

    let (send, recv) = std::sync::mpsc::channel();

    ctrlc::set_handler(move || {
        send.send(()).unwrap();
    })
    .unwrap();

    let srv = BootstrapSrv::new(config);

    if let Ok(ref server) = srv {
        server.print_addrs();
    }

    let _ = recv.recv();

    tracing::info!("Terminating...");
    drop(srv);

    if let Some(ref provider) = meter_provider
        && let Err(e) = provider.force_flush()
    {
        tracing::warn!("Failed to flush OTEL metrics on shutdown: {e}");
    }

    tracing::info!("Exit Process.");
    std::process::exit(0);
}