Atomically upload runner via SFTP to ensure safe file replacement; bump version to 0.1.11

- Refactor runner upload logic to use temporary files and atomic renaming for safer updates.
- Improve file permission handling during temporary file creation.
- Increment orchestrator version to 0.1.11.

Signed-off-by: Till Wegmueller <toasterson@gmail.com>
This commit is contained in:
Till Wegmueller 2025-11-17 23:18:55 +01:00
parent b36e5c70a8
commit 20a0efd116
No known key found for this signature in database
2 changed files with 17 additions and 6 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "orchestrator"
version = "0.1.10"
version = "0.1.11"
edition = "2024"
build = "build.rs"

View file

@ -599,22 +599,33 @@ async fn run_job_via_ssh_owned(
if !sess.authenticated() {
return Err(miette::miette!("ssh not authenticated"));
}
// Upload runner via SFTP
// Upload runner via SFTP atomically: write to temp, close, then rename over final path
let sftp = sess.sftp().map_err(OrchestratorError::SftpInit).into_diagnostic()?;
let mut local = StdFile::open(&local_runner)
.map_err(OrchestratorError::OpenLocalRunner)
.into_diagnostic()?;
let mut buf = Vec::new();
local.read_to_end(&mut buf).map_err(OrchestratorError::ReadRunner).into_diagnostic()?;
let mut remote = sftp.create(&remote_path)
// Temp remote path
let tmp_remote = {
let mut p = remote_path.clone();
let tmp_name = format!("{}.tmp", p.file_name().and_then(|s| s.to_str()).unwrap_or("solstice-runner"));
p.set_file_name(tmp_name);
p
};
let mut remote_tmp = sftp.create(&tmp_remote)
.map_err(OrchestratorError::SftpCreate)
.into_diagnostic()?;
use std::io::Write as _;
remote.write_all(&buf)
remote_tmp.write_all(&buf)
.map_err(OrchestratorError::SftpWrite)
.into_diagnostic()?;
let remote_file_stat = ssh2::FileStat { size: None, uid: None, gid: None, perm: Some(0o755), atime: None, mtime: None };
let _ = sftp.setstat(&remote_path, remote_file_stat);
let tmp_file_stat = ssh2::FileStat { size: None, uid: None, gid: None, perm: Some(0o755), atime: None, mtime: None };
let _ = sftp.setstat(&tmp_remote, tmp_file_stat);
// Ensure remote file handle is closed before attempting exec/rename
drop(remote_tmp);
// Rename temp to final atomically; overwrite if exists
let _ = sftp.rename(&tmp_remote, &remote_path, Some(ssh2::RenameFlags::OVERWRITE));
// Build command
let cmd = format!(