1 Commits

Author SHA1 Message Date
Uber Veng
65e189c913 Fixed auto update 2026-05-22 00:24:19 +07:00
5 changed files with 25 additions and 19 deletions

2
Cargo.lock generated
View File

@@ -1123,7 +1123,7 @@ dependencies = [
[[package]] [[package]]
name = "ostiary" name = "ostiary"
version = "1.0.3" version = "1.0.4"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"crossterm", "crossterm",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ostiary" name = "ostiary"
version = "1.0.3" version = "1.0.4"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@@ -19,7 +19,8 @@ pub enum Event {
StructuredFinished(i32), StructuredFinished(i32),
UpdateAvailable(updater::UpdateInfo), UpdateAvailable(updater::UpdateInfo),
UpdateProgress(u64), UpdateProgress(u64),
UpdateDone, /// Carries the exe path captured before the binary was replaced.
UpdateDone(std::path::PathBuf),
UpdateError(String), UpdateError(String),
} }
@@ -40,7 +41,8 @@ pub struct App {
pub event_tx: UnboundedSender<Event>, pub event_tx: UnboundedSender<Event>,
/// When set, main loop suspends ratatui, runs the command, then restores. /// When set, main loop suspends ratatui, runs the command, then restores.
pub pending_exec: Option<PendingExec>, pub pending_exec: Option<PendingExec>,
pub pending_restart: bool, /// Exe path to exec after update; captured before the binary was replaced.
pub pending_restart: Option<std::path::PathBuf>,
} }
pub enum Popup { pub enum Popup {
@@ -104,7 +106,7 @@ impl App {
popup: None, popup: None,
event_tx, event_tx,
pending_exec: None, pending_exec: None,
pending_restart: false, pending_restart: None,
} }
} }
@@ -241,11 +243,11 @@ impl App {
} }
Ok(false) Ok(false)
} }
Event::UpdateDone => { Event::UpdateDone(exe_path) => {
if let Some(Popup::Updating { status, .. }) = &mut self.popup { if let Some(Popup::Updating { status, .. }) = &mut self.popup {
*status = UpdatingStatus::Done; *status = UpdatingStatus::Done;
} }
self.pending_restart = true; self.pending_restart = Some(exe_path);
Ok(false) Ok(false)
} }
Event::UpdateError(msg) => { Event::UpdateError(msg) => {
@@ -507,8 +509,8 @@ impl App {
) )
.await .await
{ {
Ok(()) => { Ok(exe_path) => {
let _ = tx2.send(Event::UpdateDone); let _ = tx2.send(Event::UpdateDone(exe_path));
} }
Err(e) => { Err(e) => {
let _ = let _ =

View File

@@ -74,7 +74,7 @@ async fn run_app(
} }
// Check for pending restart (after update applied) — before pending_exec. // Check for pending restart (after update applied) — before pending_exec.
if app.pending_restart { if let Some(exe_path) = app.pending_restart.take() {
disable_raw_mode()?; disable_raw_mode()?;
execute!( execute!(
terminal.backend_mut(), terminal.backend_mut(),
@@ -83,7 +83,7 @@ async fn run_app(
)?; )?;
terminal.show_cursor()?; terminal.show_cursor()?;
#[cfg(unix)] #[cfg(unix)]
updater::exec_updated(); updater::exec_updated(&exe_path);
// fallback for non-unix or if exec failed // fallback for non-unix or if exec failed
break; break;
} }

View File

@@ -94,11 +94,12 @@ pub async fn check(api_base: &str, timeout_sec: u64) -> Result<Option<UpdateInfo
/// Downloads binary to temp file with streaming progress, atomically replaces /// Downloads binary to temp file with streaming progress, atomically replaces
/// current exe, sets chmod 755. /// current exe, sets chmod 755.
/// progress_tx receives bytes downloaded so far. /// Returns the path of the replaced executable (captured before rename so it
/// remains valid even after the old inode is marked "(deleted)" by the kernel).
pub async fn download_and_apply( pub async fn download_and_apply(
info: &UpdateInfo, info: &UpdateInfo,
progress_tx: UnboundedSender<u64>, progress_tx: UnboundedSender<u64>,
) -> Result<()> { ) -> Result<std::path::PathBuf> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let response = client let response = client
@@ -144,25 +145,28 @@ pub async fn download_and_apply(
std::fs::set_permissions(&tmp_path, perms).context("failed to set permissions")?; std::fs::set_permissions(&tmp_path, perms).context("failed to set permissions")?;
} }
// Atomically replace current exe // Atomically replace current exe.
// current_exe is captured BEFORE this rename — after rename, /proc/self/exe
// on Linux returns the path with " (deleted)" appended, but the PathBuf we
// hold still refers to the correct filesystem path of the new binary.
std::fs::rename(&tmp_path, &current_exe).context("failed to replace current exe")?; std::fs::rename(&tmp_path, &current_exe).context("failed to replace current exe")?;
Ok(()) Ok(current_exe)
} }
/// Replaces current process via execv (Unix). Never returns on success. /// Replaces current process via execv (Unix). Never returns on success.
/// `exe_path` must be the path captured *before* the binary was replaced —
/// do NOT call std::env::current_exe() here, it returns "(deleted)" on Linux.
#[cfg(unix)] #[cfg(unix)]
pub fn exec_updated() -> ! { pub fn exec_updated(exe_path: &std::path::Path) -> ! {
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
let exe = std::env::current_exe().expect("failed to get current exe path");
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
let err = std::process::Command::new(&exe) let err = std::process::Command::new(exe_path)
.args(&args[1..]) .args(&args[1..])
.exec(); .exec();
// exec only returns if it failed
eprintln!("Failed to exec updated binary: {}", err); eprintln!("Failed to exec updated binary: {}", err);
std::process::exit(1); std::process::exit(1);
} }