use std::{net::SocketAddr, str::FromStr};
use clap::{Parser, ValueEnum};
use iroh::{
EndpointId, SecretKey,
address_lookup::{
UserData,
dns::{N0_DNS_ENDPOINT_ORIGIN_PROD, N0_DNS_ENDPOINT_ORIGIN_STAGING},
pkarr::{N0_DNS_PKARR_RELAY_PROD, N0_DNS_PKARR_RELAY_STAGING, PkarrRelayClient},
},
endpoint_info::{EndpointIdExt, EndpointInfo, IROH_TXT_NAME},
};
use n0_error::{Result, StackResultExt};
use url::Url;
const DEV_PKARR_RELAY_URL: &str = "http://localhost:8080/pkarr";
const DEV_DNS_ORIGIN_DOMAIN: &str = "irohdns.example";
const EXAMPLE_RELAY_URL: &str = "https://relay.iroh.example";
#[derive(ValueEnum, Clone, Debug, Default, Copy, strum::Display)]
#[strum(serialize_all = "kebab-case")]
pub enum Env {
#[default]
Staging,
Prod,
Dev,
}
#[derive(Parser, Debug)]
struct Cli {
#[clap(value_enum, short, long, default_value_t = Env::Staging)]
env: Env,
#[clap(long, conflicts_with = "env")]
pkarr_relay_url: Option<Url>,
#[clap(short, long, conflicts_with = "no_relay_url")]
relay_url: Option<Url>,
#[clap(long)]
no_relay_url: bool,
#[clap(short, long)]
addr: Vec<SocketAddr>,
#[clap(short, long)]
user_data: Option<UserData>,
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let args = Cli::parse();
let secret_key = match std::env::var("IROH_SECRET") {
Ok(s) => SecretKey::from_str(&s)
.context("failed to parse IROH_SECRET environment variable as iroh secret key")?,
Err(_) => {
let s = SecretKey::generate(&mut rand::rng());
println!("Generated a new endpoint secret. To reuse, set");
println!(
"\tIROH_SECRET={}",
data_encoding::HEXLOWER.encode(&s.to_bytes())
);
s
}
};
let endpoint_id = secret_key.public();
let pkarr_relay_url = match (args.pkarr_relay_url, args.env) {
(Some(url), _) => url,
(None, Env::Staging) => N0_DNS_PKARR_RELAY_STAGING.parse().expect("valid url"),
(None, Env::Prod) => N0_DNS_PKARR_RELAY_PROD.parse().expect("valid url"),
(None, Env::Dev) => DEV_PKARR_RELAY_URL.parse().expect("valid url"),
};
let relay_url = if let Some(relay_url) = args.relay_url {
Some(relay_url)
} else if !args.no_relay_url {
Some(EXAMPLE_RELAY_URL.parse().expect("valid url"))
} else {
None
};
println!("announce endpoint {endpoint_id}:");
if let Some(relay_url) = &relay_url {
println!(" relay={relay_url}");
}
for addr in &args.addr {
println!(" addr={addr}");
}
if let Some(user_data) = &args.user_data {
println!(" user-data={user_data}");
}
println!();
println!("publish to {pkarr_relay_url} ...");
let pkarr = PkarrRelayClient::new(pkarr_relay_url);
let endpoint_info = EndpointInfo::new(endpoint_id)
.with_relay_url(relay_url.map(Into::into))
.with_ip_addrs(args.addr.into_iter().collect())
.with_user_data(args.user_data);
let signed_packet = endpoint_info.to_pkarr_signed_packet(&secret_key, 30)?;
tracing::debug!("signed packet: {signed_packet:?}");
pkarr.publish(&signed_packet).await?;
println!("signed packet published.");
println!("resolve with:");
match args.env {
Env::Staging => {
println!(" cargo run --example resolve -- --env staging endpoint {endpoint_id}");
println!(
" dig {} TXT",
fmt_domain(&endpoint_id, N0_DNS_ENDPOINT_ORIGIN_STAGING)
)
}
Env::Prod => {
println!(" cargo run --example resolve -- --env prod endpoint {endpoint_id}");
println!(
" dig {} TXT",
fmt_domain(&endpoint_id, N0_DNS_ENDPOINT_ORIGIN_PROD)
)
}
Env::Dev => {
println!(" cargo run --example resolve -- --env dev endpoint {endpoint_id}");
println!(
" dig @localhost -p 5300 {} TXT",
fmt_domain(&endpoint_id, DEV_DNS_ORIGIN_DOMAIN)
)
}
}
Ok(())
}
fn fmt_domain(endpoint_id: &EndpointId, origin: &str) -> String {
format!("{IROH_TXT_NAME}.{}.{origin}", endpoint_id.to_z32())
}