radicle_cli/commands/
debug.rs1#![allow(clippy::or_fun_call)]
2use std::collections::HashMap;
3use std::env;
4use std::ffi::OsString;
5use std::path::PathBuf;
6use std::process::Command;
7
8use anyhow::anyhow;
9use serde::Serialize;
10
11use radicle::Profile;
12
13use crate::terminal as term;
14use crate::terminal::args::{Args, Help};
15
16pub const NAME: &str = "rad";
17pub const VERSION: &str = env!("RADICLE_VERSION");
18pub const DESCRIPTION: &str = "Radicle command line interface";
19pub const GIT_HEAD: &str = env!("GIT_HEAD");
20
21pub const HELP: Help = Help {
22 name: "debug",
23 description: "Write out information to help debug your Radicle node remotely",
24 version: env!("RADICLE_VERSION"),
25 usage: r#"
26Usage
27
28 rad debug
29
30 Run this if you are reporting a problem in Radicle. The output is
31 helpful for Radicle developers to debug your problem remotely. The
32 output is meant to not include any sensitive information, but
33 please check it, and then forward to the Radicle developers.
34
35"#,
36};
37
38#[derive(Debug)]
39pub struct Options {}
40
41impl Args for Options {
42 fn from_args(_args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
43 Ok((Options {}, vec![]))
44 }
45}
46
47pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
48 match ctx.profile() {
49 Ok(profile) => debug(Some(&profile)),
50 Err(e) => {
51 eprintln!("ERROR: Could not load Radicle profile: {e}");
52 debug(None)
53 }
54 }
55}
56
57fn debug(profile: Option<&Profile>) -> anyhow::Result<()> {
60 let env = HashMap::from_iter(env::vars().filter_map(|(k, v)| {
61 if k == "RAD_PASSPHRASE" {
62 Some((k, "<REDACTED>".into()))
63 } else if k.starts_with("RAD_") || k.starts_with("SSH_") || k == "PATH" || k == "SHELL" {
64 Some((k, v))
65 } else {
66 None
67 }
68 }));
69
70 let debug = DebugInfo {
71 rad_exe: if let Ok(filename) = std::env::current_exe() {
72 Some(filename)
73 } else {
74 None
75 },
76 rad_version: VERSION,
77 radicle_node_version: stdout_of("radicle-node", &["--version"])
78 .unwrap_or("<unknown>".into()),
79 git_remote_rad_version: stdout_of("git-remote-rad", &["--version"])
80 .unwrap_or("<unknown>".into()),
81 git_version: stdout_of("git", &["--version"]).unwrap_or("<unknown>".into()),
82 ssh_version: stderr_of("ssh", &["-V"]).unwrap_or("<unknown>".into()),
83 git_head: GIT_HEAD,
84 log: profile.map(|p| LogFile::new(p.node().join("node.log"))),
85 old_log: profile.map(|p| LogFile::new(p.node().join("node.log.old"))),
86 operating_system: std::env::consts::OS,
87 arch: std::env::consts::ARCH,
88 env,
89 };
90
91 println!("{}", serde_json::to_string_pretty(&debug).unwrap());
92
93 Ok(())
94}
95
96#[derive(Debug, Serialize)]
97#[allow(dead_code)]
98#[serde(rename_all = "camelCase")]
99struct DebugInfo {
100 rad_exe: Option<PathBuf>,
101 rad_version: &'static str,
102 radicle_node_version: String,
103 git_remote_rad_version: String,
104 git_version: String,
105 ssh_version: String,
106 git_head: &'static str,
107 log: Option<LogFile>,
108 old_log: Option<LogFile>,
109 operating_system: &'static str,
110 arch: &'static str,
111 env: HashMap<String, String>,
112}
113
114#[derive(Debug, Serialize)]
115#[allow(dead_code)]
116#[serde(rename_all = "camelCase")]
117struct LogFile {
118 filename: PathBuf,
119 exists: bool,
120 len: Option<u64>,
121}
122
123impl LogFile {
124 fn new(filename: PathBuf) -> Self {
125 Self {
126 filename: filename.clone(),
127 exists: filename.exists(),
128 len: if let Ok(meta) = filename.metadata() {
129 Some(meta.len())
130 } else {
131 None
132 },
133 }
134 }
135}
136
137fn output_of(bin: &str, args: &[&str]) -> anyhow::Result<(String, String)> {
138 let output = Command::new(bin).args(args).output()?;
139 if !output.status.success() {
140 return Err(anyhow!("command failed: {bin:?} {args:?}"));
141 }
142 let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
143 let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
144 Ok((stdout, stderr))
145}
146
147fn stdout_of(bin: &str, args: &[&str]) -> anyhow::Result<String> {
148 let (stdout, _) = output_of(bin, args)?;
149 Ok(stdout)
150}
151
152fn stderr_of(bin: &str, args: &[&str]) -> anyhow::Result<String> {
153 let (_, stderr) = output_of(bin, args)?;
154 Ok(stderr)
155}