iroh_ssh/
api.rs

1use std::{path::PathBuf, str::FromStr as _};
2
3use anyhow::bail;
4use homedir::my_home;
5use iroh::{NodeId, SecretKey};
6
7use crate::{dot_ssh,  IrohSsh};
8
9pub async fn info_mode() -> anyhow::Result<()> {
10    let key = dot_ssh(&SecretKey::generate(rand::rngs::OsRng), false);
11    if key.is_err() {
12        println!("No keys found, run 'iroh-ssh server --persist' or '-p' to create it");
13        println!();
14        println!("(if an iroh-ssh instance is currently running, it is using ephemeral keys)");
15        bail!("No keys found")
16    }
17
18    let key = key.unwrap();
19    let node_id = key.public();
20    println!("Your iroh-ssh nodeid: {}", node_id.to_string());
21
22    println!("iroh-ssh version {}", env!("CARGO_PKG_VERSION"));
23    println!("https://github.com/rustonbsd/iroh-ssh");
24    println!("");
25    println!("run 'iroh-ssh server --persist' to start the server with persistent keys");
26    println!("run 'iroh-ssh server' to start the server with ephemeral keys");
27    println!(
28        "run 'iroh-ssh service install' to copy the binary, install the service and start the server (always uses persistent keys)"
29    );
30    println!("");
31    println!("Your iroh-ssh nodeid:");
32    println!("  iroh-ssh my-user@{}\n\n", key.public().to_string());
33    Ok(())
34}
35
36pub mod service {
37    use crate::{install_service, uninstall_service, ServiceParams};
38
39    pub async fn install(ssh_port: u16) -> anyhow::Result<()> {
40        if install_service(ServiceParams { ssh_port }).await.is_err() {
41            println!("service install is only supported on linux and windows");
42            anyhow::bail!("service install is only supported on linux and windows");
43        }
44        Ok(())
45    }
46
47    pub async fn uninstall() -> anyhow::Result<()> {
48        if uninstall_service().await.is_err() {
49            println!("service uninstall is only supported on linux or windows");
50            anyhow::bail!("service uninstall is only supported on linux or windows");
51        }
52        Ok(())
53    }
54}
55
56pub async fn server_mode(ssh_port: u16, persist: bool) -> anyhow::Result<()> {
57    let mut iroh_ssh_builder = IrohSsh::new().accept_incoming(true).accept_port(ssh_port);
58    if persist {
59        iroh_ssh_builder = iroh_ssh_builder.dot_ssh_integration(true);
60    }
61    let iroh_ssh = iroh_ssh_builder.build().await?;
62
63    println!("Connect to this this machine:");
64    println!("\n  iroh-ssh my-user@{}\n", iroh_ssh.node_id());
65    if persist {
66        
67        let distro_home = my_home()?.ok_or_else(|| anyhow::anyhow!("home directory not found"))?;
68        let ssh_dir = distro_home.join(".ssh");
69        println!("  (using persistent keys in {})", ssh_dir.display());
70    } else {
71        println!("  warning: (using ephemeral keys, run 'iroh-ssh server --persist' to create persistent keys)");
72    }
73    println!("");
74    println!(
75        "client -> iroh-ssh -> direct connect -> iroh-ssh -> local ssh :{}",
76        ssh_port
77    );
78
79    println!("Waiting for incoming connections...");
80    println!("Press Ctrl+C to exit");
81    tokio::signal::ctrl_c().await?;
82    Ok(())
83}
84
85pub async fn client_mode(target: String, identity_file: Option<PathBuf>) -> anyhow::Result<()> {
86    let (ssh_user, iroh_node_id) = parse_iroh_target(&target)?;
87    let iroh_ssh = IrohSsh::new().accept_incoming(false).build().await?;
88    let mut ssh_process = iroh_ssh.connect(&ssh_user, iroh_node_id, identity_file).await?;
89
90    ssh_process.wait().await?;
91
92    Ok(())
93}
94
95fn parse_iroh_target(target: &str) -> anyhow::Result<(String, NodeId)> {
96    let (user, node_id_str) = target
97        .split_once('@')
98        .ok_or_else(|| anyhow::anyhow!("Invalid format, use user@node_id"))?;
99    let node_id = NodeId::from_str(node_id_str)?;
100    Ok((user.to_string(), node_id))
101}