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] [package]
name = "orchestrator" name = "orchestrator"
version = "0.1.10" version = "0.1.11"
edition = "2024" edition = "2024"
build = "build.rs" build = "build.rs"

View file

@ -599,22 +599,33 @@ async fn run_job_via_ssh_owned(
if !sess.authenticated() { if !sess.authenticated() {
return Err(miette::miette!("ssh not 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 sftp = sess.sftp().map_err(OrchestratorError::SftpInit).into_diagnostic()?;
let mut local = StdFile::open(&local_runner) let mut local = StdFile::open(&local_runner)
.map_err(OrchestratorError::OpenLocalRunner) .map_err(OrchestratorError::OpenLocalRunner)
.into_diagnostic()?; .into_diagnostic()?;
let mut buf = Vec::new(); let mut buf = Vec::new();
local.read_to_end(&mut buf).map_err(OrchestratorError::ReadRunner).into_diagnostic()?; 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) .map_err(OrchestratorError::SftpCreate)
.into_diagnostic()?; .into_diagnostic()?;
use std::io::Write as _; use std::io::Write as _;
remote.write_all(&buf) remote_tmp.write_all(&buf)
.map_err(OrchestratorError::SftpWrite) .map_err(OrchestratorError::SftpWrite)
.into_diagnostic()?; .into_diagnostic()?;
let remote_file_stat = ssh2::FileStat { size: None, uid: None, gid: None, perm: Some(0o755), atime: None, mtime: None }; let tmp_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 _ = 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 // Build command
let cmd = format!( let cmd = format!(