From 20a0efd11611170bdd8d9a32fb666fc0da97f3cecbb3cb1e974d9d4eab3e47b5 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Mon, 17 Nov 2025 23:18:55 +0100 Subject: [PATCH] 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 --- crates/orchestrator/Cargo.toml | 2 +- crates/orchestrator/src/scheduler.rs | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/orchestrator/Cargo.toml b/crates/orchestrator/Cargo.toml index ebcd0c3..ee5d988 100644 --- a/crates/orchestrator/Cargo.toml +++ b/crates/orchestrator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orchestrator" -version = "0.1.10" +version = "0.1.11" edition = "2024" build = "build.rs" diff --git a/crates/orchestrator/src/scheduler.rs b/crates/orchestrator/src/scheduler.rs index fe9a7a1..b356e7f 100644 --- a/crates/orchestrator/src/scheduler.rs +++ b/crates/orchestrator/src/scheduler.rs @@ -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!(