radicle_cli/commands/
debug.rs1mod args;
2
3use std::collections::BTreeMap;
4use std::env;
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;
14
15pub use args::Args;
16
17pub const NAME: &str = "rad";
18pub const VERSION: &str = env!("RADICLE_VERSION");
19pub const DESCRIPTION: &str = "Radicle command line interface";
20pub const GIT_HEAD: &str = env!("GIT_HEAD");
21
22pub fn run(_args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
23 match ctx.profile() {
24 Ok(profile) => debug(Some(&profile)),
25 Err(e) => {
26 eprintln!("ERROR: Could not load Radicle profile: {e}");
27 debug(None)
28 }
29 }
30}
31
32fn debug(profile: Option<&Profile>) -> anyhow::Result<()> {
35 let env = BTreeMap::from_iter(env::vars().filter_map(|(k, v)| {
36 if k == "RAD_PASSPHRASE" {
37 Some((k, "<REDACTED>".into()))
38 } else if k.starts_with("RAD_") || k.starts_with("SSH_") || k == "PATH" || k == "SHELL" {
39 Some((k, v))
40 } else {
41 None
42 }
43 }));
44
45 let debug = DebugInfo {
46 rad_exe: std::env::current_exe().ok(),
47 rad_version: VERSION,
48 radicle_node_version: stdout_of("radicle-node", &["--version"])
49 .unwrap_or("radicle-node <unknown>".into()),
50 git_remote_rad_version: stdout_of("git-remote-rad", &["--version"])
51 .unwrap_or("git-remote-rad <unknown>".into()),
52 git_version: stdout_of("git", &["--version"]).unwrap_or("<unknown>".into()),
53 ssh_version: stderr_of("ssh", &["-V"]).unwrap_or("<unknown>".into()),
54 git_head: GIT_HEAD,
55 log: profile.map(|p| LogFile::new(p.node().join("node.log"))),
56 old_log: profile.map(|p| LogFile::new(p.node().join("node.log.old"))),
57 operating_system: std::env::consts::OS,
58 arch: std::env::consts::ARCH,
59 env,
60 warnings: collect_warnings(profile),
61 };
62
63 println!("{}", serde_json::to_string_pretty(&debug).unwrap());
64
65 Ok(())
66}
67
68#[derive(Debug, Serialize)]
69#[allow(dead_code)]
70#[serde(rename_all = "camelCase")]
71struct DebugInfo {
72 rad_exe: Option<PathBuf>,
73 rad_version: &'static str,
74 radicle_node_version: String,
75 git_remote_rad_version: String,
76 git_version: String,
77 ssh_version: String,
78 git_head: &'static str,
79 log: Option<LogFile>,
80 old_log: Option<LogFile>,
81 operating_system: &'static str,
82 arch: &'static str,
83 env: BTreeMap<String, String>,
84
85 #[serde(skip_serializing_if = "Vec::is_empty")]
86 warnings: Vec<String>,
87}
88
89#[derive(Debug, Serialize)]
90#[allow(dead_code)]
91#[serde(rename_all = "camelCase")]
92struct LogFile {
93 filename: PathBuf,
94 exists: bool,
95 len: Option<u64>,
96}
97
98impl LogFile {
99 fn new(filename: PathBuf) -> Self {
100 Self {
101 filename: filename.clone(),
102 exists: filename.exists(),
103 len: if let Ok(meta) = filename.metadata() {
104 Some(meta.len())
105 } else {
106 None
107 },
108 }
109 }
110}
111
112fn output_of(bin: &str, args: &[&str]) -> anyhow::Result<(String, String)> {
113 let output = Command::new(bin).args(args).output()?;
114 if !output.status.success() {
115 return Err(anyhow!("command failed: {bin:?} {args:?}"));
116 }
117 let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
118 let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
119 Ok((stdout, stderr))
120}
121
122fn stdout_of(bin: &str, args: &[&str]) -> anyhow::Result<String> {
123 let (stdout, _) = output_of(bin, args)?;
124 Ok(stdout)
125}
126
127fn stderr_of(bin: &str, args: &[&str]) -> anyhow::Result<String> {
128 let (_, stderr) = output_of(bin, args)?;
129 Ok(stderr)
130}
131
132fn collect_warnings(profile: Option<&Profile>) -> Vec<String> {
133 match profile {
134 Some(profile) => crate::warning::config_warnings(&profile.config),
135 None => vec!["No Radicle profile found.".to_string()],
136 }
137}