freenet_test_network/
remote.rs1use crate::{Error, Result};
4use ssh2::Session;
5use std::io::Read;
6use std::net::TcpStream;
7use std::path::PathBuf;
8
9#[derive(Debug, Clone)]
11pub struct RemoteMachine {
12 pub host: String,
14
15 pub user: Option<String>,
17
18 pub port: Option<u16>,
20
21 pub identity_file: Option<PathBuf>,
23
24 pub freenet_binary: Option<PathBuf>,
27
28 pub work_dir: Option<PathBuf>,
31}
32
33impl RemoteMachine {
34 pub fn new(host: impl Into<String>) -> Self {
36 Self {
37 host: host.into(),
38 user: None,
39 port: None,
40 identity_file: None,
41 freenet_binary: None,
42 work_dir: None,
43 }
44 }
45
46 pub fn user(mut self, user: impl Into<String>) -> Self {
48 self.user = Some(user.into());
49 self
50 }
51
52 pub fn port(mut self, port: u16) -> Self {
54 self.port = Some(port);
55 self
56 }
57
58 pub fn identity_file(mut self, path: impl Into<PathBuf>) -> Self {
60 self.identity_file = Some(path.into());
61 self
62 }
63
64 pub fn freenet_binary(mut self, path: impl Into<PathBuf>) -> Self {
66 self.freenet_binary = Some(path.into());
67 self
68 }
69
70 pub fn work_dir(mut self, path: impl Into<PathBuf>) -> Self {
72 self.work_dir = Some(path.into());
73 self
74 }
75
76 pub fn ssh_port(&self) -> u16 {
78 self.port.unwrap_or(22)
79 }
80
81 pub fn ssh_user(&self) -> String {
83 self.user
84 .clone()
85 .unwrap_or_else(|| std::env::var("USER").unwrap_or_else(|_| "root".to_string()))
86 }
87
88 pub fn remote_work_dir(&self) -> PathBuf {
90 self.work_dir
91 .clone()
92 .unwrap_or_else(|| PathBuf::from("/tmp/freenet-test-network"))
93 }
94
95 pub fn connect(&self) -> Result<Session> {
97 let addr = format!("{}:{}", self.host, self.ssh_port());
98 let tcp = TcpStream::connect(&addr).map_err(|e| {
99 Error::PeerStartupFailed(format!("Failed to connect to {}: {}", addr, e))
100 })?;
101
102 let mut session = Session::new().map_err(|e| {
103 Error::PeerStartupFailed(format!("Failed to create SSH session: {}", e))
104 })?;
105
106 session.set_tcp_stream(tcp);
107 session
108 .handshake()
109 .map_err(|e| Error::PeerStartupFailed(format!("SSH handshake failed: {}", e)))?;
110
111 let username = self.ssh_user();
113 if let Some(identity) = &self.identity_file {
114 session
115 .userauth_pubkey_file(&username, None, identity, None)
116 .map_err(|e| {
117 Error::PeerStartupFailed(format!("SSH key authentication failed: {}", e))
118 })?;
119 } else {
120 session.userauth_agent(&username).map_err(|e| {
122 Error::PeerStartupFailed(format!("SSH agent authentication failed: {}", e))
123 })?;
124 }
125
126 if !session.authenticated() {
127 return Err(Error::PeerStartupFailed(
128 "SSH authentication failed".to_string(),
129 ));
130 }
131
132 Ok(session)
133 }
134
135 pub fn exec(&self, command: &str) -> Result<String> {
137 let session = self.connect()?;
138 let mut channel = session
139 .channel_session()
140 .map_err(|e| Error::PeerStartupFailed(format!("Failed to open SSH channel: {}", e)))?;
141
142 channel
143 .exec(command)
144 .map_err(|e| Error::PeerStartupFailed(format!("Failed to execute command: {}", e)))?;
145
146 let mut output = String::new();
147 channel.read_to_string(&mut output).map_err(|e| {
148 Error::PeerStartupFailed(format!("Failed to read command output: {}", e))
149 })?;
150
151 channel.wait_close().ok();
152 let exit_status = channel
153 .exit_status()
154 .map_err(|e| Error::PeerStartupFailed(format!("Failed to get exit status: {}", e)))?;
155
156 if exit_status != 0 {
157 return Err(Error::PeerStartupFailed(format!(
158 "Command failed with exit code {}: {}",
159 exit_status, output
160 )));
161 }
162
163 Ok(output.trim().to_string())
164 }
165
166 pub fn scp_upload(&self, local_path: &std::path::Path, remote_path: &str) -> Result<()> {
168 let session = self.connect()?;
169
170 let local_file = std::fs::File::open(local_path)
171 .map_err(|e| Error::PeerStartupFailed(format!("Failed to open local file: {}", e)))?;
172
173 let metadata = local_file
174 .metadata()
175 .map_err(|e| Error::PeerStartupFailed(format!("Failed to get file metadata: {}", e)))?;
176
177 let mut remote_file = session
178 .scp_send(
179 std::path::Path::new(remote_path),
180 0o755, metadata.len(),
182 None,
183 )
184 .map_err(|e| {
185 Error::PeerStartupFailed(format!("Failed to initiate SCP upload: {}", e))
186 })?;
187
188 std::io::copy(
189 &mut std::fs::File::open(local_path).unwrap(),
190 &mut remote_file,
191 )
192 .map_err(|e| Error::PeerStartupFailed(format!("Failed to upload file: {}", e)))?;
193
194 remote_file.send_eof().ok();
195 remote_file.wait_eof().ok();
196 remote_file.close().ok();
197 remote_file.wait_close().ok();
198
199 Ok(())
200 }
201
202 pub fn scp_download(&self, remote_path: &str, local_path: &std::path::Path) -> Result<()> {
204 let session = self.connect()?;
205
206 let (mut remote_file, _stat) = session
207 .scp_recv(std::path::Path::new(remote_path))
208 .map_err(|e| {
209 Error::PeerStartupFailed(format!("Failed to initiate SCP download: {}", e))
210 })?;
211
212 let mut local_file = std::fs::File::create(local_path)
213 .map_err(|e| Error::PeerStartupFailed(format!("Failed to create local file: {}", e)))?;
214
215 std::io::copy(&mut remote_file, &mut local_file)
216 .map_err(|e| Error::PeerStartupFailed(format!("Failed to download file: {}", e)))?;
217
218 remote_file.send_eof().ok();
219 remote_file.wait_eof().ok();
220 remote_file.close().ok();
221 remote_file.wait_close().ok();
222
223 Ok(())
224 }
225
226 pub fn discover_public_address(&self) -> Result<String> {
228 if let Ok(addr) = self.exec("ip route get 8.8.8.8 | awk '{print $7; exit}'") {
232 if !addr.is_empty() && addr != "127.0.0.1" {
233 return Ok(addr);
234 }
235 }
236
237 if let Ok(output) = self.exec("hostname -I") {
239 for addr in output.split_whitespace() {
240 if addr.starts_with("192.168.")
241 || addr.starts_with("10.")
242 || addr.starts_with("172.")
243 {
244 return Ok(addr.to_string());
245 }
246 }
247 }
248
249 Ok(self.host.clone())
251 }
252}
253
254#[derive(Debug, Clone)]
256pub enum PeerLocation {
257 Local,
259
260 Remote(RemoteMachine),
262}
263
264impl Default for PeerLocation {
265 fn default() -> Self {
266 PeerLocation::Local
267 }
268}