use {
clap::{ArgAction, Parser},
core::convert::Infallible,
mosaik::*,
};
#[derive(Debug, Parser)]
struct Opts {
#[clap(
short,
long,
value_parser = parse_secret_key,
env = "MOSAIK_BOOTSTRAP_SECRET"
)]
secret: Option<SecretKey>,
#[clap(short, long, env = "MOSAIK_BOOTSTRAP_NETWORK_ID")]
network_id: Option<NetworkId>,
#[clap(short, long, env = "MOSAIK_BOOTSTRAP_PEERS")]
peers: Vec<PeerId>,
#[clap(
short,
long,
default_value = "bootstrap",
env = "MOSAIK_BOOTSTRAP_TAGS"
)]
tags: Vec<Tag>,
#[clap(long, default_value_t = false, env = "MOSAIK_BOOTSTRAP_NO_RELAY")]
no_relay: bool,
#[clap(
short,
action = ArgAction::Count,
env = "MOSAIK_BOOTSTRAP_LOGGING",
help = "Use verbose output (-vv very verbose)",
group = "logging"
)]
verbose: u8,
#[clap(short, long, env = "MOSAIK_BOOTSTRAP_QUIET", group = "logging")]
quiet: bool,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let opts = Opts::parse();
configure_logging(&opts);
let secret = opts.secret.unwrap_or_else(|| {
tracing::warn!("No secret key provided, generating random key");
SecretKey::generate(&mut rand::rng())
});
let network_id = opts.network_id.unwrap_or_else(|| {
tracing::warn!("No network id provided, generating random network id");
NetworkId::random()
});
let mut builder = Network::builder(network_id)
.with_secret_key(secret)
.with_discovery(
discovery::Config::builder()
.with_tags(opts.tags.clone())
.with_bootstrap(opts.peers.clone()),
);
if opts.no_relay {
builder = builder.with_relay_mode(iroh::RelayMode::Disabled);
}
let network = builder.build().await?;
tracing::info!("Bootstrap node started");
tracing::info!("Public Id: {}", network.local().id());
tracing::info!("Network Id: {:?}", network.network_id());
tracing::info!("Tags: {:?}", opts.tags);
tracing::info!("Bootstrap peers: {:?}", opts.peers);
if opts.no_relay {
tracing::info!("Relays: Disabled");
} else {
tracing::info!("Relays: Enabled");
}
tracing::info!("Hit Ctrl+C to stop the bootstrap node.");
core::future::pending::<()>().await;
Ok(())
}
fn configure_logging(opts: &Opts) {
use {
tracing::Level,
tracing_subscriber::{
Layer,
filter::filter_fn,
layer::SubscriberExt,
util::SubscriberInitExt,
},
};
if opts.quiet {
return;
}
let log_level = match opts.verbose {
1 => Level::DEBUG,
2 => Level::TRACE,
_ => Level::INFO,
};
tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::layer()
.with_filter(filter_fn(move |metadata| metadata.level() <= &log_level)),
)
.init();
}
#[expect(
clippy::unnecessary_wraps,
reason = "clap parser requires this signature"
)]
fn parse_secret_key(s: &str) -> Result<SecretKey, Infallible> {
let bytes = Digest::from(s);
Ok(SecretKey::from_bytes(bytes.as_bytes()))
}