holochain_cli_run_local_services/
lib.rs

1use clap::Parser;
2use std::io::{Error, Result};
3use std::sync::Arc;
4use tokio::io::AsyncWriteExt;
5
6/// Helper for running local Holochain bootstrap and WebRTC signal servers.
7#[derive(Debug, Parser)]
8#[command(version, about)]
9pub struct HcRunLocalServices {
10    /// If set, write the bound address list to a new file, separated by
11    /// newlines. If the file exists, an error will be returned.
12    #[arg(long)]
13    bootstrap_address_path: Option<std::path::PathBuf>,
14
15    /// A single interface on which to run the bootstrap server.
16    #[arg(long, default_value = "127.0.0.1")]
17    bootstrap_interface: String,
18
19    /// The port to use for the bootstrap server. You probably want
20    /// to leave this as 0 (zero) to be assigned an available port.
21    #[arg(short, long, default_value = "0")]
22    bootstrap_port: u16,
23
24    /// Disable running a bootstrap server.
25    #[arg(long)]
26    disable_bootstrap: bool,
27
28    /// If set, write the bound address list to a new file, separated by
29    /// newlines. If the file exists, an error will be returned.
30    #[arg(long)]
31    signal_address_path: Option<std::path::PathBuf>,
32
33    /// A comma-separated list of interfaces on which to run the signal server.
34    #[arg(long, default_value = "127.0.0.1, [::1]")]
35    signal_interfaces: String,
36
37    /// The port to use for the signal server. You probably want
38    /// to leave this as 0 (zero) to be assigned an available port.
39    #[arg(short, long, default_value = "0")]
40    signal_port: u16,
41
42    /// Disable running a signal server.
43    #[arg(long)]
44    disable_signal: bool,
45}
46
47struct AOut(Option<tokio::fs::File>);
48
49impl AOut {
50    pub async fn new(p: &Option<std::path::PathBuf>) -> Result<Self> {
51        Ok(Self(if let Some(path) = p {
52            Some(
53                tokio::fs::OpenOptions::new()
54                    .write(true)
55                    .create_new(true)
56                    .open(path)
57                    .await?,
58            )
59        } else {
60            None
61        }))
62    }
63
64    pub async fn write(&mut self, s: String) -> Result<()> {
65        if let Some(f) = &mut self.0 {
66            f.write_all(s.as_bytes()).await?;
67        }
68        Ok(())
69    }
70
71    pub async fn close(mut self) -> Result<()> {
72        if let Some(f) = &mut self.0 {
73            f.flush().await?;
74            f.shutdown().await?;
75        }
76        Ok(())
77    }
78}
79
80impl HcRunLocalServices {
81    #[allow(clippy::too_many_arguments)]
82    pub fn new(
83        bootstrap_address_path: Option<std::path::PathBuf>,
84        bootstrap_interface: String,
85        bootstrap_port: u16,
86        disable_bootstrap: bool,
87        signal_address_path: Option<std::path::PathBuf>,
88        signal_interfaces: String,
89        signal_port: u16,
90        disable_signal: bool,
91    ) -> Self {
92        Self {
93            bootstrap_address_path,
94            bootstrap_interface,
95            bootstrap_port,
96            disable_bootstrap,
97            signal_address_path,
98            signal_interfaces,
99            signal_port,
100            disable_signal,
101        }
102    }
103
104    pub async fn run(self) {
105        if let Err(err) = self.run_err().await {
106            eprintln!("run-local-services error");
107            eprintln!("{err:#?}");
108        }
109    }
110
111    pub async fn run_err(self) -> Result<()> {
112        let mut task_list = Vec::new();
113
114        if !self.disable_bootstrap {
115            let bs_ip: std::net::IpAddr = self.bootstrap_interface.parse().map_err(Error::other)?;
116            let bs_addr = std::net::SocketAddr::from((bs_ip, self.bootstrap_port));
117            let (bs_driver, bs_addr, shutdown) = kitsune_p2p_bootstrap::run(bs_addr, vec![])
118                .await
119                .map_err(Error::other)?;
120            std::mem::forget(shutdown);
121            task_list.push(bs_driver);
122
123            let mut a_out = AOut::new(&self.bootstrap_address_path).await?;
124
125            for addr in tx_addr(bs_addr)? {
126                a_out.write(format!("http://{addr}\n")).await?;
127                println!("# HC BOOTSTRAP - ADDR: http://{addr}");
128            }
129
130            a_out.close().await?;
131
132            println!("# HC BOOTSTRAP - RUNNING");
133        }
134
135        if !self.disable_signal {
136            let bind = self
137                .signal_interfaces
138                .split(',')
139                .map(|i| format!("{}:{}", i.trim(), self.signal_port))
140                .collect();
141            println!("BIND: {bind:?}");
142            let config = sbd_server::Config {
143                bind,
144                ..Default::default()
145            };
146            tracing::info!(?config);
147
148            let sig_hnd = sbd_server::SbdServer::new(Arc::new(config)).await?;
149
150            let addr_list = sig_hnd.bind_addrs().to_vec();
151
152            // there is no real task here... just fake it
153            task_list.push(Box::pin(async move {
154                let _sig_hnd = sig_hnd;
155                std::future::pending().await
156            }));
157
158            let mut a_out = AOut::new(&self.signal_address_path).await?;
159
160            for addr in addr_list {
161                a_out.write(format!("ws://{addr}\n")).await?;
162                println!("# HC SIGNAL - ADDR: ws://{addr}");
163            }
164
165            a_out.close().await?;
166
167            println!("# HC SIGNAL - RUNNING");
168        }
169
170        if task_list.is_empty() {
171            println!("All Services Disabled - Aborting");
172            return Ok(());
173        }
174
175        futures::future::join_all(task_list).await;
176
177        Ok(())
178    }
179}
180
181fn tx_addr(addr: std::net::SocketAddr) -> Result<Vec<std::net::SocketAddr>> {
182    if addr.ip().is_unspecified() {
183        let port = addr.port();
184        let mut list = Vec::new();
185        let include_v6 = addr.ip().is_ipv6();
186
187        for iface in if_addrs::get_if_addrs()? {
188            if iface.ip().is_ipv6() && !include_v6 {
189                continue;
190            }
191            list.push((iface.ip(), port).into());
192        }
193
194        Ok(list)
195    } else {
196        Ok(vec![addr])
197    }
198}