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 endpoint id:");
41 println!(
42 " iroh-ssh {}@{}",
43 whoami::username().unwrap_or("UNKNOWN_USER".to_string()),
44 key.clone().public()
45 );
46 println!();
47 }
48
49 if let Some(key) = service_key {
50 println!();
51 println!("Your service iroh-ssh endpoint id:");
52 println!(
53 " iroh-ssh {}@{}",
54 whoami::username().unwrap_or("UNKNOWN_USER".to_string()),
55 key.clone().public()
56 );
57 println!();
58 }
59
60 Ok(())
61}
62
63pub mod service {
64 use crate::{ServiceParams, install_service, uninstall_service};
65
66 pub async fn install(ssh_port: u16) -> anyhow::Result<()> {
67 if install_service(ServiceParams { ssh_port }).await.is_err() {
68 anyhow::bail!("service install is only supported on linux and windows");
69 }
70 Ok(())
71 }
72
73 pub async fn uninstall() -> anyhow::Result<()> {
74 if uninstall_service().await.is_err() {
75 println!("service uninstall is only supported on linux or windows");
76 anyhow::bail!("service uninstall is only supported on linux or windows");
77 }
78 Ok(())
79 }
80}
81
82pub async fn server_mode(server_args: ServerArgs, service: bool) -> anyhow::Result<()> {
83 let mut iroh_ssh_builder = IrohSsh::builder()
84 .accept_incoming(true)
85 .accept_port(server_args.ssh_port);
86 if server_args.persist {
87 iroh_ssh_builder = iroh_ssh_builder.dot_ssh_integration(true, service);
88 }
89 let iroh_ssh = iroh_ssh_builder.build().await?;
90
91 println!("Connect to this this machine:");
92 println!(
93 "\n iroh-ssh {}@{}\n",
94 whoami::username().unwrap_or("UNKNOWN_USER".to_string()),
95 iroh_ssh.endpoint_id()
96 );
97 if server_args.persist {
98 let distro_home = my_home()?.ok_or_else(|| anyhow::anyhow!("home directory not found"))?;
99 let ssh_dir = distro_home.join(".ssh");
100 println!(" (using persistent keys in {})", ssh_dir.display());
101 } else {
102 println!(
103 " warning: (using ephemeral keys, run 'iroh-ssh server --persist' to create persistent keys)"
104 );
105 }
106 println!();
107 println!(
108 "client -> iroh-ssh -> direct connect -> iroh-ssh -> local ssh :{}",
109 server_args.ssh_port
110 );
111
112 println!("Waiting for incoming connections...");
113 println!("Press Ctrl+C to exit");
114 tokio::signal::ctrl_c().await?;
115 Ok(())
116}
117
118pub async fn proxy_mode(proxy_args: ProxyArgs) -> anyhow::Result<()> {
119 let iroh_ssh = IrohSsh::builder().accept_incoming(false).build().await?;
120 let endpoint_id = EndpointId::from_str(if proxy_args.endpoint_id.len() == 64 {
121 &proxy_args.endpoint_id
122 } else if proxy_args.endpoint_id.len() > 64 {
123 &proxy_args.endpoint_id[proxy_args.endpoint_id.len() - 64..]
124 } else {
125 return Err(anyhow::anyhow!("invalid endpoint id length"));
126 })?;
127 iroh_ssh.connect(endpoint_id).await
128}
129
130pub async fn client_mode(connect_args: ConnectArgs) -> anyhow::Result<()> {
131 let iroh_ssh = IrohSsh::builder().accept_incoming(false).build().await?;
132 let mut ssh_process = iroh_ssh
133 .start_ssh(
134 connect_args.target,
135 connect_args.ssh,
136 connect_args.remote_cmd,
137 )
138 .await?;
139
140 ssh_process.wait().await?;
141
142 Ok(())
143}