diff --git a/Cargo.lock b/Cargo.lock index c5a71b6ebe..4cf43919ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3096,9 +3096,13 @@ name = "iroh-node-util" version = "0.28.0" dependencies = [ "anyhow", + "clap", + "colored", + "comfy-table", "derive_more", "dirs-next", "futures-lite 2.5.0", + "human-time", "iroh-net", "nested_enum_utils", "quic-rpc", @@ -3109,6 +3113,7 @@ dependencies = [ "serde_with", "strum 0.26.3", "tempfile", + "time", "tokio", "tracing", "tracing-appender", diff --git a/iroh-cli/Cargo.toml b/iroh-cli/Cargo.toml index d15efbbf0a..ce89de2c52 100644 --- a/iroh-cli/Cargo.toml +++ b/iroh-cli/Cargo.toml @@ -44,7 +44,7 @@ iroh-blobs = { version = "0.28.1", features = ["cli"] } iroh-docs = { version = "0.28.0", features = ["cli"] } iroh-gossip = { version = "0.28.1", features = ["cli"] } iroh-metrics = { version = "0.28.0" } -iroh-node-util = { path = "../iroh-node-util", features = ["config", "logging"] } +iroh-node-util = { path = "../iroh-node-util", features = ["config", "logging", "cli"] } parking_lot = "0.12.1" pkarr = { version = "2.2.0", default-features = false } portable-atomic = "1" diff --git a/iroh-cli/src/commands.rs b/iroh-cli/src/commands.rs index c016e08841..741baf3f82 100644 --- a/iroh-cli/src/commands.rs +++ b/iroh-cli/src/commands.rs @@ -14,12 +14,12 @@ use crate::config::NodeConfig; pub(crate) mod console; pub(crate) mod doctor; -pub(crate) mod net; pub(crate) mod rpc; pub(crate) mod start; pub(crate) use iroh_blobs::{cli as blobs, cli::tags}; pub(crate) use iroh_docs::{cli as docs, cli::authors}; pub(crate) use iroh_gossip::cli as gossip; +pub(crate) use iroh_node_util::cli::net; /// iroh is a tool for building distributed apps. /// diff --git a/iroh-cli/src/commands/rpc.rs b/iroh-cli/src/commands/rpc.rs index e7d5c9fd75..69c93783b1 100644 --- a/iroh-cli/src/commands/rpc.rs +++ b/iroh-cli/src/commands/rpc.rs @@ -4,6 +4,7 @@ use anyhow::Result; use clap::Subcommand; use iroh::client::Iroh; use iroh_docs::cli::ConsoleEnv; +use iroh_node_util::cli::node::NodeCommands; use super::{ authors::AuthorCommands, blobs::BlobCommands, docs::DocCommands, gossip::GossipCommands, @@ -74,19 +75,8 @@ pub enum RpcCommands { command: TagCommands, }, - /// Get statistics and metrics from the running node. - Stats, - /// Get status of the running node. - Status, - /// Shutdown the running node. - Shutdown { - /// Shutdown mode. - /// - /// Hard shutdown will immediately terminate the process, soft shutdown will wait - /// for all connections to close. - #[clap(long, default_value_t = false)] - force: bool, - }, + #[clap(flatten)] + Node(NodeCommands), } impl RpcCommands { @@ -94,36 +84,13 @@ impl RpcCommands { pub async fn run(self, iroh: &Iroh, env: &ConsoleEnv) -> Result<()> { let node_id = || async move { iroh.net().node_addr().await }; match self { - Self::Net { command } => command.run(iroh).await, + Self::Net { command } => command.run(&iroh.net()).await, Self::Blobs { command } => command.run(&iroh.blobs(), node_id().await?).await, Self::Docs { command } => command.run(&iroh.docs(), &iroh.blobs(), env).await, Self::Authors { command } => command.run(&iroh.authors(), env).await, Self::Tags { command } => command.run(&iroh.tags()).await, Self::Gossip { command } => command.run(&iroh.gossip()).await, - Self::Stats => { - let stats = iroh.node().stats().await?; - for (name, details) in stats.iter() { - println!( - "{:23} : {:>6} ({})", - name, details.value, details.description - ); - } - Ok(()) - } - Self::Shutdown { force } => { - iroh.node().shutdown(force).await?; - Ok(()) - } - Self::Status => { - let response = iroh.node().status().await?; - println!("Listening addresses: {:#?}", response.listen_addrs); - println!("Node ID: {}", response.addr.node_id); - println!("Version: {}", response.version); - if let Some(addr) = response.rpc_addr { - println!("RPC Addr: {}", addr); - } - Ok(()) - } + Self::Node(command) => command.run(&iroh.node()).await, } } } diff --git a/iroh-node-util/Cargo.toml b/iroh-node-util/Cargo.toml index 624148255a..7398b5d7d3 100644 --- a/iroh-node-util/Cargo.toml +++ b/iroh-node-util/Cargo.toml @@ -17,6 +17,7 @@ workspace = true [dependencies] anyhow = "1" +clap = { version = "4", features = ["derive"], optional = true } tokio = "1" iroh-net = { path = "../iroh-net" } tempfile = "3" @@ -29,16 +30,26 @@ serde-error = "0.1.3" futures-lite = "2.5.0" tracing = "0.1.40" -derive_more = { version = "1.0.0", features = ["display"], optional = true } +# config dirs-next = { version = "2.0.0", optional = true } + +# logging +derive_more = { version = "1.0.0", features = ["display"], optional = true } rustyline = { version = "12.0.0", optional = true } serde_with = { version = "3.7.0", optional = true } tracing-appender = { version = "0.2.3", optional = true } tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = true } +# cli +colored = { version = "2.0.4", optional = true } +comfy-table = { version = "7.0.1", optional = true } +time = { version = "0.3", features = ["formatting"], optional = true } +human-time = { version = "0.1.6", optional = true } + [features] logging = ["dep:derive_more", "dep:serde_with", "dep:rustyline", "dep:tracing-appender", "dep:tracing-subscriber"] config = ["dep:dirs-next"] +cli = ["dep:clap", "dep:colored", "dep:comfy-table", "dep:time", "dep:human-time"] [package.metadata.docs.rs] all-features = true diff --git a/iroh-node-util/src/cli.rs b/iroh-node-util/src/cli.rs new file mode 100644 index 0000000000..409d601699 --- /dev/null +++ b/iroh-node-util/src/cli.rs @@ -0,0 +1,3 @@ +//! Cli commands. +pub mod net; +pub mod node; diff --git a/iroh-cli/src/commands/net.rs b/iroh-node-util/src/cli/net.rs similarity index 93% rename from iroh-cli/src/commands/net.rs rename to iroh-node-util/src/cli/net.rs index a2b786583c..9e004f4208 100644 --- a/iroh-cli/src/commands/net.rs +++ b/iroh-node-util/src/cli/net.rs @@ -1,5 +1,4 @@ //! Define the net subcommands. - use std::{net::SocketAddr, time::Duration}; use anyhow::Result; @@ -8,17 +7,14 @@ use colored::Colorize; use comfy_table::{presets::NOTHING, Cell, Table}; use futures_lite::{Stream, StreamExt}; use human_time::ToHumanTimeString; -use iroh::{ - client::Iroh, - net::{ - endpoint::{DirectAddrInfo, RemoteInfo}, - NodeAddr, NodeId, RelayUrl, - }, +use iroh_net::{ + endpoint::{DirectAddrInfo, RemoteInfo}, + NodeAddr, NodeId, RelayUrl, }; /// Commands to manage the iroh network. #[derive(Subcommand, Debug, Clone)] -#[allow(clippy::large_enum_variant)] +#[allow(clippy::large_enum_variant, missing_docs)] pub enum NetCommands { /// Get information about the different remote nodes. RemoteList, @@ -38,10 +34,10 @@ pub enum NetCommands { impl NetCommands { /// Runs the net command given the iroh client. - pub async fn run(self, iroh: &Iroh) -> Result<()> { + pub async fn run(self, client: &crate::rpc::client::net::Client) -> Result<()> { match self { Self::RemoteList => { - let connections = iroh.net().remote_info_iter().await?; + let connections = client.remote_info_iter().await?; let timestamp = time::OffsetDateTime::now_utc() .format(&time::format_description::well_known::Rfc2822) .unwrap_or_else(|_| String::from("failed to get current time")); @@ -54,14 +50,14 @@ impl NetCommands { ); } Self::Remote { node_id } => { - let info = iroh.net().remote_info(node_id).await?; + let info = client.remote_info(node_id).await?; match info { Some(info) => println!("{}", fmt_info(info)), None => println!("Not Found"), } } Self::NodeAddr => { - let addr = iroh.net().node_addr().await?; + let addr = client.node_addr().await?; println!("Node ID: {}", addr.node_id); let relay = addr .info @@ -83,10 +79,10 @@ impl NetCommands { if let Some(relay) = relay { addr = addr.with_relay_url(relay); } - iroh.net().add_node_addr(addr).await?; + client.add_node_addr(addr).await?; } Self::HomeRelay => { - let relay = iroh.net().home_relay().await?; + let relay = client.home_relay().await?; let relay = relay .map(|s| s.to_string()) .unwrap_or_else(|| "Not Available".to_string()); diff --git a/iroh-node-util/src/cli/node.rs b/iroh-node-util/src/cli/node.rs new file mode 100644 index 0000000000..aabca5ce3b --- /dev/null +++ b/iroh-node-util/src/cli/node.rs @@ -0,0 +1,55 @@ +//! Node commands +use clap::Subcommand; + +use crate::rpc::client::node; + +/// Commands to manage the iroh RPC. +#[derive(Subcommand, Debug, Clone)] +#[allow(clippy::large_enum_variant)] +pub enum NodeCommands { + /// Get statistics and metrics from the running node. + Stats, + /// Get status of the running node. + Status, + /// Shutdown the running node. + Shutdown { + /// Shutdown mode. + /// + /// Hard shutdown will immediately terminate the process, soft shutdown will wait + /// for all connections to close. + #[clap(long, default_value_t = false)] + force: bool, + }, +} + +impl NodeCommands { + /// Run the RPC command given the iroh client and the console environment. + pub async fn run(self, node: &node::Client) -> anyhow::Result<()> { + match self { + Self::Stats => { + let stats = node.stats().await?; + for (name, details) in stats.iter() { + println!( + "{:23} : {:>6} ({})", + name, details.value, details.description + ); + } + Ok(()) + } + Self::Shutdown { force } => { + node.shutdown(force).await?; + Ok(()) + } + Self::Status => { + let response = node.status().await?; + println!("Listening addresses: {:#?}", response.listen_addrs); + println!("Node ID: {}", response.addr.node_id); + println!("Version: {}", response.version); + if let Some(addr) = response.rpc_addr { + println!("RPC Addr: {}", addr); + } + Ok(()) + } + } + } +} diff --git a/iroh-node-util/src/lib.rs b/iroh-node-util/src/lib.rs index d0f6200716..0f21b059f1 100644 --- a/iroh-node-util/src/lib.rs +++ b/iroh-node-util/src/lib.rs @@ -1,6 +1,9 @@ //! Utilities for building iroh nodes. #![deny(missing_docs, rustdoc::broken_intra_doc_links)] #![cfg_attr(iroh_docsrs, feature(doc_cfg))] +#[cfg_attr(iroh_docsrs, doc(cfg(feature = "cli")))] +#[cfg(feature = "cli")] +pub mod cli; #[cfg_attr(iroh_docsrs, doc(cfg(feature = "config")))] #[cfg(feature = "config")] pub mod config;