From ccec3f80dc8557ad30bef100a73a395defd88b15 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Mon, 5 Feb 2024 17:52:28 +0200 Subject: [PATCH] cli: add "debug" subcommand This writes out information that may be useful when debugging Radicle remotely on a user's machine. Things like versions of programs, and values of relevant environment variables. Signed-off-by: Lars Wirzenius --- radicle-cli/src/commands.rs | 2 + radicle-cli/src/commands/debug.rs | 149 ++++++++++++++++++++++++++++++ radicle-cli/src/main.rs | 7 ++ 3 files changed, 158 insertions(+) create mode 100644 radicle-cli/src/commands/debug.rs diff --git a/radicle-cli/src/commands.rs b/radicle-cli/src/commands.rs index 4f2abe9f..6808f7ff 100644 --- a/radicle-cli/src/commands.rs +++ b/radicle-cli/src/commands.rs @@ -12,6 +12,8 @@ pub mod rad_clone; pub mod rad_cob; #[path = "commands/config.rs"] pub mod rad_config; +#[path = "commands/debug.rs"] +pub mod rad_debug; #[path = "commands/diff.rs"] pub mod rad_diff; #[path = "commands/follow.rs"] diff --git a/radicle-cli/src/commands/debug.rs b/radicle-cli/src/commands/debug.rs new file mode 100644 index 00000000..55c71a5f --- /dev/null +++ b/radicle-cli/src/commands/debug.rs @@ -0,0 +1,149 @@ +#![allow(clippy::or_fun_call)] +use std::collections::HashMap; +use std::env; +use std::ffi::OsString; +use std::path::PathBuf; +use std::process::Command; + +use anyhow::anyhow; +use serde::Serialize; + +use radicle::Profile; + +use crate::terminal as term; +use crate::terminal::args::{Args, Help}; + +pub const NAME: &str = "rad"; +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const DESCRIPTION: &str = "Radicle command line interface"; +pub const GIT_HEAD: &str = env!("GIT_HEAD"); + +pub const HELP: Help = Help { + name: "debug", + description: "Write out information to help debug your Radicle node remotely", + version: env!("CARGO_PKG_VERSION"), + usage: r#" +Usage + + rad debug + + Run this if you are reporting a problem in Radicle. The output is + helpful for Radicle developers to debug your problem remotely. The + output is meant to not include any sensitive information, but + please check it, and then forward to the Radicle developers. + +"#, +}; + +#[derive(Debug)] +pub struct Options {} + +impl Args for Options { + fn from_args(_args: Vec) -> anyhow::Result<(Self, Vec)> { + Ok((Options {}, vec![])) + } +} + +pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> { + match ctx.profile() { + Ok(profile) => debug(&profile), + Err(e) => { + eprintln!("ERROR: {e}"); + Err(e) + } + } +} + +// Collect information about the local Radicle installation and write +// it out. +fn debug(profile: &Profile) -> anyhow::Result<()> { + let env = HashMap::from_iter(env::vars().filter_map(|(k, v)| { + if k == "RAD_PASSPHRASE" { + Some((k, "".into())) + } else if k.starts_with("RAD_") || k.starts_with("SSH_") { + Some((k, v)) + } else { + None + } + })); + + let debug = DebugInfo { + rad_version: VERSION, + radicle_node_version: stdout_of("radicle-node", &["--version"]) + .unwrap_or("".into()), + git_remote_rad_version: stdout_of("git-remote-rad", &["--version"]) + .unwrap_or("".into()), + git_version: stdout_of("git", &["--version"]).unwrap_or("".into()), + ssh_version: stderr_of("ssh", &["-V"]).unwrap_or("".into()), + git_head: GIT_HEAD, + log: LogFile::new(profile.node().join("node.log")), + old_log: LogFile::new(profile.node().join("node.log.old")), + operating_system: std::env::consts::OS, + arch: std::env::consts::ARCH, + env, + }; + + println!("{}", serde_json::to_string_pretty(&debug).unwrap()); + + Ok(()) +} + +#[derive(Debug, Serialize)] +#[allow(dead_code)] +#[serde(rename_all = "camelCase")] +struct DebugInfo { + rad_version: &'static str, + radicle_node_version: String, + git_remote_rad_version: String, + git_version: String, + ssh_version: String, + git_head: &'static str, + log: LogFile, + old_log: LogFile, + operating_system: &'static str, + arch: &'static str, + env: HashMap, +} + +#[derive(Debug, Serialize)] +#[allow(dead_code)] +#[serde(rename_all = "camelCase")] +struct LogFile { + filename: PathBuf, + exists: bool, + len: Option, +} + +impl LogFile { + fn new(filename: PathBuf) -> Self { + Self { + filename: filename.clone(), + exists: filename.exists(), + len: if let Ok(meta) = filename.metadata() { + Some(meta.len()) + } else { + None + }, + } + } +} + +fn output_of(bin: &str, args: &[&str]) -> anyhow::Result<(String, String)> { + let output = Command::new(bin).args(args).output()?; + if !output.status.success() { + return Err(anyhow!("command failed: {bin:?} {args:?}")); + } + let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + Ok((stdout, stderr)) +} + +fn stdout_of(bin: &str, args: &[&str]) -> anyhow::Result { + let (stdout, _) = output_of(bin, args)?; + Ok(stdout) +} + +fn stderr_of(bin: &str, args: &[&str]) -> anyhow::Result { + let (_, stderr) = output_of(bin, args)?; + Ok(stderr) +} diff --git a/radicle-cli/src/main.rs b/radicle-cli/src/main.rs index adbc2bab..0a30c230 100644 --- a/radicle-cli/src/main.rs +++ b/radicle-cli/src/main.rs @@ -152,6 +152,13 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option> args.to_vec(), ); } + "debug" => { + term::run_command_args::( + rad_debug::HELP, + rad_debug::run, + args.to_vec(), + ); + } "follow" => { term::run_command_args::( rad_follow::HELP,