Skip to main content

iroh_ssh/
api.rs

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}