use {
crate::{
cli::CommandResult,
deploy::known_hosts,
ui::{fail_message, succeed_message, succeed_symbol},
},
anyhow::{anyhow, Result},
smbcloud_model::runner::Runner,
smbcloud_utils::config::Config,
spinners::{Spinner, Spinners},
std::{io::Write, process::Command},
tempfile::NamedTempFile,
};
pub fn rsync_deploy(
config: &Config,
runner: &Runner,
user_id: i32,
source: &str,
) -> Result<CommandResult> {
let home = dirs::home_dir().ok_or_else(|| anyhow!("Could not determine home directory"))?;
let identity_file = home.join(".ssh").join(format!("id_{}@smbcloud", user_id));
let identity_file_str = identity_file.to_string_lossy().into_owned();
let rsync_host = runner.rsync_host();
let mut known_hosts_file = NamedTempFile::new()
.map_err(|e| anyhow!("Failed to create temp known_hosts file: {}", e))?;
writeln!(known_hosts_file, "{}", known_hosts::for_host(&rsync_host))
.map_err(|e| anyhow!("Failed to write known_hosts: {}", e))?;
let ssh_command = format!(
"ssh -i {identity} \
-o StrictHostKeyChecking=yes \
-o UserKnownHostsFile={known_hosts} \
-o IdentitiesOnly=yes \
-o PasswordAuthentication=no \
-o BatchMode=yes",
identity = identity_file_str,
known_hosts = known_hosts_file.path().display(),
);
let remote_path = match &config.project.path {
Some(path) => path.clone(),
None => format!("apps/web/{}", config.project.name),
};
let source_with_slash = if source.ends_with('/') {
source.to_owned()
} else {
format!("{}/", source)
};
let remote_with_slash = if remote_path.ends_with('/') {
remote_path
} else {
format!("{}/", remote_path)
};
let destination = format!("git@{}:{}", rsync_host, remote_with_slash);
let spinner = Spinner::new(
Spinners::Hamburger,
succeed_message(&format!("Syncing {} -> {}", source_with_slash, destination)),
);
let output = Command::new("rsync")
.args([
"-a",
"--exclude=.git",
"--exclude=.smb",
"-e",
&ssh_command,
&source_with_slash,
&destination,
])
.output();
drop(known_hosts_file);
match output {
Ok(result) if result.status.success() => {
let stderr = String::from_utf8_lossy(&result.stderr);
if !stderr.is_empty() {
for line in stderr.lines() {
println!("{}", line);
}
}
Ok(CommandResult {
spinner,
symbol: succeed_symbol(),
msg: succeed_message("Deployment complete via rsync."),
})
}
Ok(result) => {
drop(spinner);
let stderr = String::from_utf8_lossy(&result.stderr);
if !stderr.is_empty() {
eprintln!("{}", stderr);
}
Err(anyhow!(fail_message(&format!(
"rsync exited with status {}",
result.status.code().unwrap_or(-1)
))))
}
Err(e) => {
drop(spinner);
Err(anyhow!(fail_message(&format!(
"Failed to launch rsync: {}. Is rsync installed?",
e
))))
}
}
}