1use prettytable::{Cell, Row, Table};
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::io::Error;
5use tokio::io::{AsyncBufReadExt, BufReader};
6use tokio::net::TcpStream;
7use tokio::process::Command;
8use tokio::time::Duration;
9use tracing::{debug, info, warn};
10
11use super::rssh::SSHSession;
12use crate::config::CONFIG;
13use crate::storage::db::{
14 delete_index, get_connection, insert, query_all, query_index, update_authorized,
15};
16use crate::storage::secure::{decrypt, encrypt};
17use crate::Result;
18
19#[derive(Debug, Default, Serialize, Deserialize)]
20pub struct Remote {
21 pub index: usize,
24 pub user: String,
26 #[serde(deserialize_with = "depass", serialize_with = "enpass")]
28 pub password: String,
29 pub ip: String,
31 pub port: u16,
33 pub authorized: bool,
35 pub name: Option<String>,
37 pub note: Option<String>,
39}
40
41fn depass<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
42where
43 D: Deserializer<'de>,
44{
45 let password = String::deserialize(deserializer)?;
46 Ok(decrypt(&password))
47}
48
49fn enpass<S>(password: &String, serializer: S) -> std::result::Result<S::Ok, S::Error>
50where
51 S: Serializer,
52{
53 Serialize::serialize(&encrypt(password), serializer)
54}
55
56impl std::fmt::Display for Remote {
58 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
59 write!(f, "{}@{}:{}", self.user, self.ip, self.port,)
60 }
61}
62
63impl Remote {
64 pub fn add_record(&self) -> Result<usize> {
65 CONFIG.get_enc_key()?;
67 let n = {
68 let conn = get_connection().lock();
69 insert(&conn, &self)
70 }
71 .map_err(|e| Error::new(std::io::ErrorKind::Other, e))?;
72 info!(remote = self.to_string(), "success add record");
73 Ok(n)
74 }
75
76 pub fn delete_record(&self) -> Result<()> {
77 let conn = get_connection().lock();
79 delete_index(&conn, self.index).map_err(|e| Error::new(std::io::ErrorKind::Other, e))?;
80 info!(remote = self.to_string(), "success delete record");
81 Ok(())
82 }
83
84 pub async fn add_auth(&self) -> Result<()> {
85 let _ = CONFIG.get_enc_key()?;
88 let mut session = SSHSession::new(&self.user, &self.password, &self.ip, self.port).await?;
89 session.authenticate(CONFIG.read_public()?.as_str()).await?;
90 if !self.authorized {
92 let conn = get_connection().lock();
95 update_authorized(&conn, self.index, true)?;
96 }
97 info!(remote = self.to_string(), "success add authenticate");
98 Ok(())
99 }
100
101 pub async fn remove_auth(&self) -> Result<()> {
102 let mut session = SSHSession::new(&self.user, &self.password, &self.ip, self.port).await?;
103 session.revoke(CONFIG.read_public()?.as_str()).await?;
104 info!(remote = self.to_string(), "success remove authenticate");
105 Ok(())
106 }
107
108 pub async fn login(&self, reauth: bool) -> Result<()> {
109 if !self.authorized || reauth {
111 self.add_auth().await?;
112 }
113 let sshkey = CONFIG.get_private();
114 let _ = Command::new("ssh")
115 .arg(format!("{}@{}", self.user, self.ip))
116 .arg("-p")
117 .arg(self.port.to_string())
118 .arg("-i")
119 .arg(sshkey.to_str().unwrap())
120 .status()
121 .await?;
122 info!(remote = self.to_string(), "success login");
123 Ok(())
124 }
125
126 async fn scp(&self, args: &Vec<&str>) -> Result<()> {
127 if !self.authorized {
129 debug!(remote = self.to_string(), "no authorized, try authenticate");
130 self.add_auth().await?;
131 }
132 debug!(args=?args, "scp");
138
139 let mut child = Command::new("scp")
140 .args(args)
141 .stderr(std::process::Stdio::piped())
142 .spawn()?;
143
144 let stderr = child
145 .stderr
146 .take()
147 .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::BrokenPipe, "stderr is none"))?;
148
149 let reader = BufReader::new(stderr);
150 let mut lines = reader.lines();
151
152 while let Some(line) = lines.next_line().await? {
153 println!("{}", line);
154 }
155
156 let _ = child.wait().await?;
157
158 Ok(())
159 }
160
161 pub async fn upload(&self, from: &str, to: &str) -> Result<()> {
162 let port = self.port.to_string();
165 let remote = format!("{}@{}:{}", self.user, self.ip, to);
166 let cmd = vec![
167 "-r",
168 "-P",
169 &port,
170 "-i",
171 CONFIG.get_private().to_str().unwrap(),
172 from,
173 &remote,
174 ];
175 self.scp(&cmd).await?;
176 info!(from=?from, to=?to, "susccess upload");
177 Ok(())
178 }
179
180 pub async fn download(&self, from: &str, to: &str) -> Result<()> {
181 let port = self.port.to_string();
184 let remote = format!("{}@{}:{}", self.user, self.ip, from);
185 let cmd = vec![
186 "-r",
187 "-P",
188 &port,
189 "-i",
190 CONFIG.get_private().to_str().unwrap(),
191 &remote,
192 to,
193 ];
194 self.scp(&cmd).await?;
195 info!(from=?from, to=?to, "susccess download");
196 Ok(())
197 }
198
199 async fn is_alive(&self) -> bool {
200 check_port(&self.ip, self.port, None).await
201 }
202}
203
204async fn check_port(host: &str, port: u16, timeout_duration: Option<Duration>) -> bool {
205 let addr = format!("{}:{}", host, port);
206 match tokio::time::timeout(
207 timeout_duration.unwrap_or(Duration::from_secs(3)),
208 TcpStream::connect(&addr),
209 )
210 .await
211 {
212 Ok(Ok(_stream)) => {
213 debug!("✅ {addr} is OPEN");
214 true
215 }
216 Ok(Err(e)) => {
217 warn!("❌ {addr} is CLOSED: {e}");
218 false
219 }
220 Err(e) => {
221 warn!("⏱️ {addr} TIMEOUT {e}");
222 false
223 }
224 }
225}
226
227pub struct Remotes(pub Vec<Remote>);
229
230impl Remotes {
231 fn load() -> Result<Remotes> {
232 let conn = get_connection().lock();
233 let remotes = query_all(&conn).map_err(|e| Error::new(std::io::ErrorKind::Other, e))?;
234 Ok(Remotes(remotes))
235 }
236 pub fn get(idx: usize) -> Result<Option<Remote>> {
237 let remote = {
238 let conn = get_connection().lock();
239 query_index(&conn, idx)
240 }
241 .map_err(|e| Error::new(std::io::ErrorKind::Other, e))?;
242
243 if remote.is_some() {
244 info!(index = idx, "susccess get remote");
245 } else {
246 warn!(index = idx, "remote not found");
247 }
248 Ok(remote)
249 }
250
251 pub fn try_get(idx: usize) -> Result<Remote> {
252 if let Some(remote) = Remotes::get(idx)? {
253 Ok(remote)
254 } else {
255 Err(Box::new(Error::new(
256 std::io::ErrorKind::NotFound,
257 format!("index {} remote not found", idx),
258 )))
259 }
260 }
261
262 pub fn get_all() -> Result<Remotes> {
263 let remotes = Remotes::load()?;
264 info!("susccess get all remotes {}", remotes.0.len());
265 Ok(remotes)
266 }
267
268 pub fn add(
269 user: &str,
270 password: &str,
271 ip: &str,
272 port: u16,
273 name: &Option<impl AsRef<str>>,
274 note: &Option<impl AsRef<str>>,
275 ) -> Result<usize> {
276 let remote = Remote {
277 index: 0, user: user.to_string(),
279 password: password.to_string(),
280 ip: ip.to_string(),
281 port,
282 authorized: false,
283 name: name.as_ref().map(|n| n.as_ref().to_string()),
284 note: note.as_ref().map(|n| n.as_ref().to_string()),
285 };
286 remote.add_record()
290 }
291
292 pub async fn delete(indexs: &Vec<usize>) -> Result<usize> {
293 let remotes: Vec<Remote> = indexs
294 .iter()
295 .collect::<std::collections::HashSet<_>>()
296 .iter()
297 .filter_map(|&idx| Remotes::get(*idx).transpose())
298 .collect::<std::result::Result<Vec<_>, _>>()?;
299 debug!(total = remotes.len(), "delete");
300 for remote in remotes.iter() {
301 debug!(index = remote.index, "delete");
302 if remote.authorized && remote.is_alive().await {
304 remote.remove_auth().await?;
305 }
306 remote.delete_record()?;
308 }
309 Ok(remotes.len())
310 }
311
312 pub fn pprint(&self, all: bool) {
313 let mut table = Table::new();
314 let mut titles = vec!["index", "name", "user", "ip", "port"];
315 if all {
316 titles.push("password");
317 titles.push("authorized");
318 titles.push("note");
319 }
320 table.set_titles(Row::new(
321 titles
322 .iter()
323 .map(|v| Cell::new(v).style_spec("bcFg"))
324 .collect::<Vec<Cell>>(),
325 ));
326
327 for remote in self.0.iter() {
328 let mut row = vec![
329 remote.index.to_string(),
330 remote.name.clone().unwrap_or_else(|| "".to_string()),
331 remote.user.clone(),
332 remote.ip.clone(),
333 remote.port.to_string(),
334 ];
335 if all {
336 if let Ok(_) = CONFIG.get_enc_key() {
337 row.push(remote.password.clone());
338 } else {
339 row.push(format!(
340 "{}..{}",
341 &remote.password[..3],
342 &remote.password[remote.password.len() - 5..]
343 ));
344 }
345 row.push(remote.authorized.to_string());
347 row.push(remote.note.clone().unwrap_or_else(|| "".to_string()));
348 }
349 table.add_row(Row::new(
350 row.iter()
351 .map(|v| Cell::new(v).style_spec("lFc"))
352 .collect::<Vec<Cell>>(),
353 ));
354 }
355 debug!("the remote list total: {}", self.0.len());
356 table.printstd();
357 }
358}