Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/fix-linux-updater-permission-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"updater": patch
---

Fixes the updater not preserving AppImage file permissions.
6 changes: 3 additions & 3 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
platform: [ubuntu-22.04, macos-latest, windows-latest]

steps:
- uses: actions/checkout@v4
Expand All @@ -38,10 +38,10 @@ jobs:
uses: dtolnay/rust-toolchain@stable

- name: install Linux dependencies
if: matrix.platform == 'ubuntu-latest'
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev libfuse2
sudo apt-get install -y webkit2gtk-4.0 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev libfuse2

- uses: Swatinem/rust-cache@v2

Expand Down
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
members = [
"plugins/*",
"plugins/*/tests/*",
"plugins/updater/tests/updater-migration/v2-app",
"plugins/*/examples/*/src-tauri",
"examples/*/src-tauri",
]
Expand Down
6 changes: 5 additions & 1 deletion plugins/updater/src/updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,8 @@ impl Update {

let tmp_app_image = &tmp_dir.path().join("current_app.AppImage");

let permissions = std::fs::metadata(&self.extract_path)?.permissions();

// create a backup of our current app image
std::fs::rename(&self.extract_path, tmp_app_image)?;

Expand Down Expand Up @@ -812,7 +814,9 @@ impl Update {
return Err(Error::BinaryNotFoundInArchive);
}

return match std::fs::write(&self.extract_path, bytes) {
return match std::fs::write(&self.extract_path, bytes)
.and_then(|_| std::fs::set_permissions(&self.extract_path, permissions))
{
Err(err) => {
// if something went wrong during the extraction, we should restore previous app
std::fs::rename(tmp_app_image, &self.extract_path)?;
Expand Down
2 changes: 1 addition & 1 deletion plugins/updater/tests/app-updater/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fn main() {
std::process::exit(0);
}
Ok(None) => {
std::process::exit(0);
std::process::exit(2);
}
Err(e) => {
println!("{e}");
Expand Down
202 changes: 135 additions & 67 deletions plugins/updater/tests/app-updater/tests/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,26 @@ use std::{
fs::File,
path::{Path, PathBuf},
process::Command,
sync::Arc,
};

use serde::Serialize;
use tauri::utils::config::{Updater, V1Compatible};

const UPDATER_PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5TlFOMFpXYzJFOUdjeHJEVXY4WE1TMUxGNDJVUjNrMmk1WlR3UVJVUWwva0FBQkFBQUFBQUFBQUFBQUlBQUFBQUpVK3ZkM3R3eWhyN3hiUXhQb2hvWFVzUW9FbEs3NlNWYjVkK1F2VGFRU1FEaGxuRUtlell5U0gxYS9DbVRrS0YyZVJGblhjeXJibmpZeGJjS0ZKSUYwYndYc2FCNXpHalM3MHcrODMwN3kwUG9SOWpFNVhCSUd6L0E4TGRUT096TEtLR1JwT1JEVFU9Cg==";
const UPDATED_EXIT_CODE: i32 = 0;
const UP_TO_DATE_EXIT_CODE: i32 = 2;

#[derive(Serialize)]
struct Config {
version: &'static str,
bundle: BundleConfig,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct BundleConfig {
create_updater_artifacts: Updater,
}

#[derive(Serialize)]
Expand Down Expand Up @@ -157,44 +168,83 @@ fn update_app() {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let root_dir = manifest_dir.join("../../../..");

let mut config = Config { version: "1.0.0" };

// bundle app update
build_app(&manifest_dir, &config, true, Default::default());

let updater_zip_ext = if cfg!(target_os = "macos") {
Some("tar.gz")
} else {
None
};

for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") {
let bundle_updater_ext = out_bundle_path.extension().unwrap().to_str().unwrap();
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
format!("{bundle_updater_ext}.{updater_zip_ext}")
for mut config in [
Config {
version: "1.0.0",
bundle: BundleConfig {
create_updater_artifacts: Updater::Bool(true),
},
},
Config {
version: "1.0.0",
bundle: BundleConfig {
create_updater_artifacts: Updater::String(V1Compatible::V1Compatible),
},
},
] {
let v1_compatible = matches!(
config.bundle.create_updater_artifacts,
Updater::String(V1Compatible::V1Compatible)
);

// bundle app update
build_app(&manifest_dir, &config, true, Default::default());

let updater_zip_ext = if v1_compatible {
if cfg!(windows) {
Some("zip")
} else {
Some("tar.gz")
}
} else if cfg!(target_os = "macos") {
Some("tar.gz")
} else {
format!("{bundle_updater_ext}")
None
};
let signature_extension = format!("{updater_extension}.sig");
let signature_path = out_bundle_path.with_extension(signature_extension);
let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| {
panic!("failed to read signature file {}", signature_path.display())
});
let out_updater_path = out_bundle_path.with_extension(updater_extension);
let updater_path = root_dir.join(format!(
"target/debug/{}",
out_updater_path.file_name().unwrap().to_str().unwrap()
));
std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle");

let target = target.clone();
std::thread::spawn(move || {

for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") {
let bundle_updater_ext = if v1_compatible {
out_bundle_path
.extension()
.unwrap()
.to_str()
.unwrap()
.replace("exe", "nsis")
} else {
out_bundle_path
.extension()
.unwrap()
.to_str()
.unwrap()
.to_string()
};
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
format!("{bundle_updater_ext}.{updater_zip_ext}")
} else {
format!("{bundle_updater_ext}")
};
let signature_extension = format!("{updater_extension}.sig");
let signature_path = out_bundle_path.with_extension(signature_extension);
let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| {
panic!("failed to read signature file {}", signature_path.display())
});
let out_updater_path = out_bundle_path.with_extension(updater_extension);
let updater_path = root_dir.join(format!(
"target/debug/{}",
out_updater_path.file_name().unwrap().to_str().unwrap()
));
std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle");

let target = target.clone();

// start the updater server
let server =
tiny_http::Server::http("localhost:3007").expect("failed to start updater server");
let server = Arc::new(
tiny_http::Server::http("localhost:3007").expect("failed to start updater server"),
);

loop {
if let Ok(request) = server.recv() {
let server_ = server.clone();
std::thread::spawn(move || {
for request in server_.incoming_requests() {
match request.url() {
"/" => {
let mut platforms = HashMap::new();
Expand Down Expand Up @@ -234,45 +284,63 @@ fn update_app() {
)
}),
));
// close server
return;
}
_ => (),
}
}
});

config.version = "0.1.0";

// bundle initial app version
build_app(&manifest_dir, &config, false, bundle_target);

let status_checks = if matches!(bundle_target, BundleTarget::Msi) {
// for msi we can't really check if the app was updated, because we can't change the install path
vec![UPDATED_EXIT_CODE]
} else {
vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE]
};

for expected_exit_code in status_checks {
let mut binary_cmd = if cfg!(windows) {
Command::new(root_dir.join("target/debug/app-updater.exe"))
} else if cfg!(target_os = "macos") {
Command::new(
bundle_paths(&root_dir, "0.1.0")
.first()
.unwrap()
.1
.join("Contents/MacOS/app-updater"),
)
} else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
let mut c = Command::new("xvfb-run");
c.arg("--auto-servernum")
.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1);
c
} else {
Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1)
};

binary_cmd.env("TARGET", bundle_target.name());

let status = binary_cmd.status().expect("failed to run app");
let code = status.code().unwrap_or(-1);

if code != expected_exit_code {
panic!(
"failed to run app, expected exit code {expected_exit_code}, got {code}"
);
}
#[cfg(windows)]
if code == UPDATED_EXIT_CODE {
// wait for the update to finish
std::thread::sleep(std::time::Duration::from_secs(5));
}
}
});

config.version = "0.1.0";

// bundle initial app version
build_app(&manifest_dir, &config, false, bundle_target);

let mut binary_cmd = if cfg!(windows) {
Command::new(root_dir.join("target/debug/app-updater.exe"))
} else if cfg!(target_os = "macos") {
Command::new(
bundle_paths(&root_dir, "0.1.0")
.first()
.unwrap()
.1
.join("Contents/MacOS/app-updater"),
)
} else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
let mut c = Command::new("xvfb-run");
c.arg("--auto-servernum")
.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1);
c
} else {
Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1)
};

binary_cmd.env("TARGET", bundle_target.name());

let status = binary_cmd.status().expect("failed to run app");

if !status.success() {
panic!("failed to run app");
// graceful shutdown
server.unblock();
}
}
}
15 changes: 15 additions & 0 deletions plugins/updater/tests/updater-migration/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "updater-migration-test"
version = "0.1.0"
edition = { workspace = true }

[build-dependencies]
tauri-build = { workspace = true }

[dependencies]
tauri = { workspace = true, features = ["wry", "compression"] }
serde = { workspace = true }
serde_json = { workspace = true }
tauri-plugin-updater = { path = "../.." }
tiny_http = "0.12"
time = { version = "0.3", features = ["formatting"] }
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading