From e0acae253fb40959f19b6dceea49842c62e8a855 Mon Sep 17 00:00:00 2001 From: Nihal Kumar Date: Thu, 19 Mar 2026 07:01:20 +0530 Subject: [PATCH] auto_update: Improve Linux rsync hinting (#50637) Previously, automatic checks could fail quietly and return to idle. This change ensures missing dependency errors are surfaced in the update UI with install guidance. What Changed: - Added a dedicated `MissingDependencyError` for dependency-related update failures. - Updated auto-update polling behavior so: - automatic checks still stay quiet for transient/general errors, - but missing dependency errors are surfaced as `AutoUpdateStatus::Errored`. - Improved Linux dependency hinting: - distro checks with `/etc/os-release`, - returns distro-appropriate install hints where possible and falls back to a generic package-manager message when distro parsing is unavailable/unknown. image Closes #47552 - [x] Done a self-review taking into account security and performance aspects Release Notes: - Improved Linux auto-update failure issue caused by missing `rsync` by surfacing actionable install guidance in the update UI. --- crates/auto_update/src/auto_update.rs | 75 ++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index b5b01807a8a0cd75ea46881b379414002bc26c04..a057a30c6d37e9e2bc8f98a60055c407b95c2a4b 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -30,9 +30,64 @@ use util::command::new_command; use workspace::Workspace; const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; + +#[derive(Debug)] +struct MissingDependencyError(String); + +impl std::fmt::Display for MissingDependencyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for MissingDependencyError {} const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60); const REMOTE_SERVER_CACHE_LIMIT: usize = 5; +#[cfg(target_os = "linux")] +fn linux_rsync_install_hint() -> &'static str { + let os_release = match std::fs::read_to_string("/etc/os-release") { + Ok(os_release) => os_release, + Err(_) => return "Please install rsync using your package manager", + }; + + let mut distribution_ids = Vec::new(); + for line in os_release.lines() { + let trimmed = line.trim(); + if let Some(value) = trimmed.strip_prefix("ID=") { + distribution_ids.push(value.trim_matches('"').to_ascii_lowercase()); + } else if let Some(value) = trimmed.strip_prefix("ID_LIKE=") { + for id in value.trim_matches('"').split_whitespace() { + distribution_ids.push(id.to_ascii_lowercase()); + } + } + } + + let package_manager_hint = if distribution_ids + .iter() + .any(|distribution_id| distribution_id == "arch") + { + Some("Install it with: sudo pacman -S rsync") + } else if distribution_ids + .iter() + .any(|distribution_id| distribution_id == "debian" || distribution_id == "ubuntu") + { + Some("Install it with: sudo apt install rsync") + } else if distribution_ids.iter().any(|distribution_id| { + distribution_id == "fedora" + || distribution_id == "rhel" + || distribution_id == "centos" + || distribution_id == "rocky" + || distribution_id == "almalinux" + }) { + Some("Install it with: sudo dnf install rsync") + } else { + None + }; + + package_manager_hint.unwrap_or("Please install rsync using your package manager") +} + actions!( auto_update, [ @@ -397,7 +452,15 @@ impl AutoUpdater { this.update(cx, |this, cx| { this.pending_poll = None; if let Err(error) = result { + let is_missing_dependency = + error.downcast_ref::().is_some(); this.status = match check_type { + UpdateCheckType::Automatic if is_missing_dependency => { + log::warn!("auto-update: {}", error); + AutoUpdateStatus::Errored { + error: Arc::new(error), + } + } // Be quiet if the check was automated (e.g. when offline) UpdateCheckType::Automatic => { log::info!("auto-update check failed: error:{:?}", error); @@ -715,11 +778,21 @@ impl AutoUpdater { } fn check_dependencies() -> Result<()> { - #[cfg(not(target_os = "windows"))] + #[cfg(target_os = "linux")] + if which::which("rsync").is_err() { + let install_hint = linux_rsync_install_hint(); + return Err(MissingDependencyError(format!( + "rsync is required for auto-updates but is not installed. {install_hint}" + )) + .into()); + } + + #[cfg(target_os = "macos")] anyhow::ensure!( which::which("rsync").is_ok(), "Could not auto-update because the required rsync utility was not found." ); + Ok(()) }