1use anyhow::{Context, Result};
2use std::process::Command;
3
4fn check_rsync_version() -> Result<()> {
5 let output = Command::new("rsync")
6 .arg("--version")
7 .output()
8 .context("Failed to execute rsync --version")?;
9
10 if !output.status.success() {
11 anyhow::bail!("Failed to get rsync version");
12 }
13
14 let version_output = String::from_utf8_lossy(&output.stdout);
15
16 let version_line = version_output
18 .lines()
19 .next()
20 .context("No version information found")?;
21
22 let version_str = version_line
23 .split_whitespace()
24 .nth(2)
25 .context("Could not parse rsync version")?;
26
27 let major_version = version_str
28 .split('.')
29 .next()
30 .and_then(|v| v.parse::<u32>().ok())
31 .context("Could not parse major version number")?;
32
33 if major_version < 3 {
34 anyhow::bail!(
35 "rsync version {} is not supported. Please upgrade to version > 3.0",
36 version_str
37 );
38 }
39
40 Ok(())
41}
42
43pub fn get_remote_home(remote_host: &str) -> Result<String> {
44 let output = Command::new("ssh")
45 .arg(remote_host)
46 .arg("echo $HOME")
47 .output()
48 .context("Failed to get remote home directory")?;
49
50 if !output.status.success() {
51 anyhow::bail!(
52 "SSH command failed: {}",
53 String::from_utf8_lossy(&output.stderr)
54 );
55 }
56
57 let home = String::from_utf8(output.stdout)?.trim().to_string();
58
59 if home.is_empty() {
60 anyhow::bail!("Remote home directory is empty");
61 }
62
63 Ok(home)
64}
65
66pub fn sync_directory(
67 source: &str,
68 destination: &str,
69 filter: Option<&str>,
70 delete: bool,
71) -> Result<()> {
72 check_rsync_version()?;
74
75 let mut cmd = Command::new("rsync");
76 cmd.args(["-azP"]);
77
78 if delete {
79 cmd.args(["--delete"]);
80 }
81
82 if let Some(f) = filter {
83 for filter_rule in f.split(',') {
85 cmd.args(["--filter", filter_rule.trim()]);
86 }
87 }
88
89 cmd.args([source, destination]);
90
91 let status = cmd.status().context("Failed to execute rsync command")?;
92
93 if !status.success() {
94 anyhow::bail!("rsync failed with exit code: {:?}", status.code());
95 }
96
97 Ok(())
98}
99
100pub fn execute_ssh_command(host: &str, command: &str) -> Result<()> {
101 let status = Command::new("ssh")
102 .arg(host)
103 .arg(command)
104 .status()
105 .context("Failed to execute SSH command")?;
106
107 if !status.success() {
108 anyhow::bail!("SSH command failed with exit code: {:?}", status.code());
109 }
110
111 Ok(())
112}
113
114pub fn open_remote_shell(host: &str, directory: &str) -> Result<()> {
115 let status = Command::new("ssh")
116 .arg("-t") .arg(host)
118 .arg(format!("cd {} && exec $SHELL -l", directory))
119 .status()
120 .context("Failed to open remote shell")?;
121
122 if !status.success() {
123 anyhow::bail!("Remote shell exited with code: {:?}", status.code());
124 }
125
126 Ok(())
127}