1use std::str::FromStr as _;
2
3use anyhow::bail;
4use homedir::my_home;
5use iroh::{EndpointId, SecretKey};
6
7use crate::{
8 IrohSsh,
9 cli::{ConnectArgs, ProxyArgs, ServerArgs},
10 dot_ssh,
11};
12
13pub async fn info_mode() -> anyhow::Result<()> {
14 let server_key = dot_ssh(&SecretKey::generate(&mut rand::rng()), false, false).ok();
15 let service_key = dot_ssh(&SecretKey::generate(&mut rand::rng()), false, true).ok();
16
17 if server_key.is_none() && service_key.is_none() {
18 println!(
19 "No keys found, run for server or service:\n 'iroh-ssh server --persist' or '-p' to create it"
20 );
21 println!();
22 println!("(if an iroh-ssh instance is currently running, it is using ephemeral keys)");
23 bail!("No keys found")
24 }
25
26 println!("iroh-ssh version {}", env!("CARGO_PKG_VERSION"));
27 println!("https://github.com/rustonbsd/iroh-ssh");
28 println!();
29
30 if server_key.is_none() && service_key.is_none() {
31 println!("run 'iroh-ssh server --persist' to start the server with persistent keys");
32 println!("run 'iroh-ssh server' to start the server with ephemeral keys");
33 println!(
34 "run 'iroh-ssh service install' to copy the binary, install the service and start the server (always uses persistent keys)"
35 );
36 }
37
38 if let Some(key) = server_key {
39 println!();
40 println!("Your server iroh-ssh nodeid:");
41 println!(" iroh-ssh {}@{}", whoami::username(), key.clone().public());
42 println!();
43 }
44
45 if let Some(key) = service_key {
46 println!();
47 println!("Your service iroh-ssh nodeid:");
48 println!(" iroh-ssh {}@{}", whoami::username(), key.clone().public());
49 println!();
50 }
51
52 Ok(())
53}
54
55pub mod service {
56 use crate::{ServiceParams, install_service, uninstall_service};
57
58 pub async fn install(ssh_port: u16) -> anyhow::Result<()> {
59 if install_service(ServiceParams { ssh_port }).await.is_err() {
60 anyhow::bail!("service install is only supported on linux and windows");
61 }
62 Ok(())
63 }
64
65 pub async fn uninstall() -> anyhow::Result<()> {
66 if uninstall_service().await.is_err() {
67 println!("service uninstall is only supported on linux or windows");
68 anyhow::bail!("service uninstall is only supported on linux or windows");
69 }
70 Ok(())
71 }
72}
73
74pub async fn server_mode(server_args: ServerArgs, service: bool) -> anyhow::Result<()> {
75 let mut iroh_ssh_builder = IrohSsh::builder()
76 .accept_incoming(true)
77 .accept_port(server_args.ssh_port);
78 if server_args.persist {
79 iroh_ssh_builder = iroh_ssh_builder.dot_ssh_integration(true, service);
80 }
81 let iroh_ssh = iroh_ssh_builder.build().await?;
82
83 println!("Connect to this this machine:");
84 println!(
85 "\n iroh-ssh {}@{}\n",
86 whoami::username(),
87 iroh_ssh.node_id()
88 );
89 if server_args.persist {
90 let distro_home = my_home()?.ok_or_else(|| anyhow::anyhow!("home directory not found"))?;
91 let ssh_dir = distro_home.join(".ssh");
92 println!(" (using persistent keys in {})", ssh_dir.display());
93 } else {
94 println!(
95 " warning: (using ephemeral keys, run 'iroh-ssh server --persist' to create persistent keys)"
96 );
97 }
98 println!();
99 println!(
100 "client -> iroh-ssh -> direct connect -> iroh-ssh -> local ssh :{}",
101 server_args.ssh_port
102 );
103
104 println!("Waiting for incoming connections...");
105 println!("Press Ctrl+C to exit");
106 tokio::signal::ctrl_c().await?;
107 Ok(())
108}
109
110pub async fn proxy_mode(proxy_args: ProxyArgs) -> anyhow::Result<()> {
111 let iroh_ssh = IrohSsh::builder().accept_incoming(false).build().await?;
112 let endpoint_id = EndpointId::from_str(if proxy_args.node_id.len() == 64 {
113 &proxy_args.node_id
114 } else if proxy_args.node_id.len() > 64 {
115 &proxy_args.node_id[proxy_args.node_id.len() - 64..]
116 } else {
117 return Err(anyhow::anyhow!("invalid node id length"));
118 })?;
119 iroh_ssh.connect(endpoint_id).await
120}
121
122pub async fn client_mode(connect_args: ConnectArgs) -> anyhow::Result<()> {
123 let iroh_ssh = IrohSsh::builder().accept_incoming(false).build().await?;
124 let mut ssh_process = iroh_ssh
125 .start_ssh(
126 connect_args.target,
127 connect_args.ssh,
128 connect_args.remote_cmd,
129 )
130 .await?;
131
132 ssh_process.wait().await?;
133
134 Ok(())
135}