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}