radicle_cli/commands/
self.rs1use std::ffi::OsString;
2
3use radicle::crypto::ssh;
4use radicle::node::Handle as _;
5use radicle::{Node, Profile};
6
7use crate::terminal as term;
8use crate::terminal::args::{Args, Error, Help};
9use crate::terminal::Element as _;
10
11pub const HELP: Help = Help {
12 name: "self",
13 description: "Show information about your identity and device",
14 version: env!("RADICLE_VERSION"),
15 usage: r#"
16Usage
17
18 rad self [<option>...]
19
20Options
21
22 --did Show your DID
23 --alias Show your Node alias
24 --home Show your Radicle home
25 --config Show the location of your configuration file
26 --ssh-key Show your public key in OpenSSH format
27 --ssh-fingerprint Show your public key fingerprint in OpenSSH format
28 --help Show help
29"#,
30};
31
32#[derive(Debug)]
33enum Show {
34 Alias,
35 NodeId,
36 Did,
37 Home,
38 Config,
39 SshKey,
40 SshFingerprint,
41 All,
42}
43
44#[derive(Debug)]
45pub struct Options {
46 show: Show,
47}
48
49impl Args for Options {
50 fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
51 use lexopt::prelude::*;
52
53 let mut parser = lexopt::Parser::from_args(args);
54 let mut show: Option<Show> = None;
55
56 while let Some(arg) = parser.next()? {
57 match arg {
58 Long("alias") if show.is_none() => {
59 show = Some(Show::Alias);
60 }
61 Long("nid") if show.is_none() => {
62 show = Some(Show::NodeId);
63 }
64 Long("did") if show.is_none() => {
65 show = Some(Show::Did);
66 }
67 Long("home") if show.is_none() => {
68 show = Some(Show::Home);
69 }
70 Long("config") if show.is_none() => {
71 show = Some(Show::Config);
72 }
73 Long("ssh-key") if show.is_none() => {
74 show = Some(Show::SshKey);
75 }
76 Long("ssh-fingerprint") if show.is_none() => {
77 show = Some(Show::SshFingerprint);
78 }
79 Long("help") | Short('h') => {
80 return Err(Error::Help.into());
81 }
82 _ => anyhow::bail!(arg.unexpected()),
83 }
84 }
85
86 Ok((
87 Options {
88 show: show.unwrap_or(Show::All),
89 },
90 vec![],
91 ))
92 }
93}
94
95pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
96 let profile = ctx.profile()?;
97
98 match options.show {
99 Show::Alias => {
100 term::print(profile.config.alias());
101 }
102 Show::NodeId => {
103 crate::warning::deprecated("rad self --nid", "rad node status --only nid");
104 term::print(
105 Node::new(profile.socket())
106 .nid()
107 .ok()
108 .unwrap_or_else(|| *profile.id()),
109 );
110 }
111 Show::Did => {
112 term::print(profile.did());
113 }
114 Show::Home => {
115 term::print(profile.home().path().display());
116 }
117 Show::Config => {
118 term::print(profile.home.config().display());
119 }
120 Show::SshKey => {
121 term::print(ssh::fmt::key(profile.id()));
122 }
123 Show::SshFingerprint => {
124 term::print(ssh::fmt::fingerprint(profile.id()));
125 }
126 Show::All => all(&profile)?,
127 }
128
129 Ok(())
130}
131
132fn all(profile: &Profile) -> anyhow::Result<()> {
133 let mut table = term::Table::<2, term::Label>::default();
134
135 table.push([
136 term::format::style("Alias").into(),
137 term::format::primary(profile.config.alias()).into(),
138 ]);
139
140 let did = profile.did();
141 table.push([
142 term::format::style("DID").into(),
143 term::format::tertiary(did).into(),
144 ]);
145
146 let socket = profile.socket();
147 let node = if Node::new(&socket).is_running() {
148 term::format::positive(format!("running ({})", socket.display()))
149 } else {
150 term::format::negative("not running".to_string())
151 };
152 table.push([term::format::style("Node").into(), node.to_string().into()]);
153
154 let ssh_agent = match ssh::agent::Agent::connect() {
155 Ok(c) => term::format::positive(format!(
156 "running ({})",
157 c.path()
158 .map(|p| p.display().to_string())
159 .unwrap_or(String::from("?"))
160 )),
161 Err(e) if e.is_not_running() => term::format::yellow(String::from("not running")),
162 Err(e) => term::format::negative(format!("error: {e}")),
163 };
164 table.push([
165 term::format::style("SSH").into(),
166 ssh_agent.to_string().into(),
167 ]);
168
169 let id = profile.id();
170 let ssh_short = ssh::fmt::fingerprint(id);
171 table.push([
172 term::format::style("├╴Key (hash)").into(),
173 term::format::tertiary(ssh_short).into(),
174 ]);
175
176 let ssh_long = ssh::fmt::key(id);
177 table.push([
178 term::format::style("└╴Key (full)").into(),
179 term::format::tertiary(ssh_long).into(),
180 ]);
181
182 let home = profile.home();
183 table.push([
184 term::format::style("Home").into(),
185 term::format::tertiary(home.path().display()).into(),
186 ]);
187
188 let config_path = profile.home.config();
189 table.push([
190 term::format::style("├╴Config").into(),
191 term::format::tertiary(config_path.display()).into(),
192 ]);
193
194 let storage_path = profile.home.storage();
195 table.push([
196 term::format::style("├╴Storage").into(),
197 term::format::tertiary(storage_path.display()).into(),
198 ]);
199
200 let keys_path = profile.home.keys();
201 table.push([
202 term::format::style("├╴Keys").into(),
203 term::format::tertiary(keys_path.display()).into(),
204 ]);
205
206 table.push([
207 term::format::style("└╴Node").into(),
208 term::format::tertiary(profile.home.node().display()).into(),
209 ]);
210
211 table.print();
212
213 Ok(())
214}