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 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}