auto_update: Improve Linux rsync hinting (#50637)
Nihal Kumar
created 3 weeks ago
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.
<img width="610" height="145" alt="image"
src="https://github.com/user-attachments/assets/8bef3970-38ba-4412-9ece-7b6bb6bf903b"
/>
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.
Change summary
crates/auto_update/src/auto_update.rs | 75 ++++++++++++++++++++++++++++
1 file changed, 74 insertions(+), 1 deletion(-)
Detailed changes
@@ -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::<MissingDependencyError>().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(())
}