From 302f375f197bec0c1b419239217bb8e9e93fb83f Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Sun, 15 Feb 2026 17:17:38 +0100 Subject: [PATCH] Add SSH download function for SFTP file retrieval Add download() to the ssh module, mirroring the existing upload() function. Reads a remote file via SFTP and writes it to a local path, creating parent directories as needed. Used by forge-builder to retrieve build artifacts from builder VMs. Co-Authored-By: Claude Opus 4.6 --- crates/vm-manager/src/ssh.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/crates/vm-manager/src/ssh.rs b/crates/vm-manager/src/ssh.rs index 1d51603..47a258e 100644 --- a/crates/vm-manager/src/ssh.rs +++ b/crates/vm-manager/src/ssh.rs @@ -204,6 +204,36 @@ pub fn upload(sess: &Session, local: &Path, remote: &Path) -> Result<()> { Ok(()) } +/// Download a remote file to a local path via SFTP. +pub fn download(sess: &Session, remote: &Path, local: &Path) -> Result<()> { + let sftp = sess.sftp().map_err(|e| VmError::SshFailed { + detail: format!("SFTP init: {e}"), + })?; + + let mut remote_file = sftp.open(remote).map_err(|e| VmError::SshFailed { + detail: format!("SFTP open {}: {e}", remote.display()), + })?; + + let mut buf = Vec::new(); + remote_file + .read_to_end(&mut buf) + .map_err(|e| VmError::SshFailed { + detail: format!("SFTP read {}: {e}", remote.display()), + })?; + + if let Some(parent) = local.parent() { + std::fs::create_dir_all(parent).map_err(|e| VmError::SshFailed { + detail: format!("create local dir {}: {e}", parent.display()), + })?; + } + + std::fs::write(local, &buf).map_err(|e| VmError::SshFailed { + detail: format!("write local file {}: {e}", local.display()), + })?; + + Ok(()) +} + /// Connect with exponential backoff retry. /// /// Retries the connection until `timeout` elapses, with exponential backoff capped at 5 seconds.