|
|
@@ -174,6 +174,45 @@ pub async fn run_ssh_follow(
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
+/// Runs a command via SSH with no inactivity timeout — suitable for long,
|
|
|
+/// streaming commands like `docker save | ssh docker load`.
|
|
|
+pub async fn run_ssh_long_running(
|
|
|
+ config: &Config,
|
|
|
+ node: &Node,
|
|
|
+ command: String,
|
|
|
+ dry_run: bool,
|
|
|
+) -> Result<String> {
|
|
|
+ if dry_run {
|
|
|
+ println!(
|
|
|
+ " ssh {}@{}: {} (long-running)",
|
|
|
+ config.ssh_user, node.ssh_ip, command
|
|
|
+ );
|
|
|
+ return Ok(String::new());
|
|
|
+ }
|
|
|
+ let session = ssh_session_follow(config, node).await?;
|
|
|
+ let mut channel = session.channel_open_session().await?;
|
|
|
+ channel.exec(true, command.as_bytes()).await?;
|
|
|
+ let mut output = String::new();
|
|
|
+ let mut exit_status: Option<u32> = None;
|
|
|
+ while let Some(msg) = channel.wait().await {
|
|
|
+ match msg {
|
|
|
+ ChannelMsg::Data { data } => output.push_str(&String::from_utf8_lossy(&data)),
|
|
|
+ ChannelMsg::ExitStatus { exit_status: s } => exit_status = Some(s),
|
|
|
+ _ => {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ match exit_status {
|
|
|
+ Some(0) | None => Ok(output),
|
|
|
+ Some(code) => Err(anyhow::anyhow!(
|
|
|
+ "ssh {}@{}: command exited with status {}\n command: {}",
|
|
|
+ config.ssh_user,
|
|
|
+ node.ssh_ip,
|
|
|
+ code,
|
|
|
+ command
|
|
|
+ )),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// Fetches the SSH host key fingerprint from a node.
|
|
|
pub async fn fetch_host_key(config: &Config, node: &Node) -> Result<String> {
|
|
|
use std::sync::Mutex;
|