Skip to content

Commit 7701392

Browse files
fix(updater): linux not retaining AppImage permissions, enhance tests (#1636)
* fix(updater): linux not retaining AppImage permissions, enhance tests - adds a test for migration from v1 to v2 - extends the existing integration test to actually verify if the app was updated * remove test framework * fix macos test * fix windows * wait on windows * fix wix * typo * fmt * install webkit2gtk-4.0 on ci * fix npm command on windows * fix test on windows * ubuntu-22.04 --------- Co-authored-by: Tillmann <[email protected]>
1 parent e4e19e0 commit 7701392

File tree

27 files changed

+5132
-72
lines changed

27 files changed

+5132
-72
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"updater": patch
3+
---
4+
5+
Fixes the updater not preserving AppImage file permissions.

.github/workflows/integration-tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
strategy:
2828
fail-fast: false
2929
matrix:
30-
platform: [ubuntu-latest, macos-latest, windows-latest]
30+
platform: [ubuntu-22.04, macos-latest, windows-latest]
3131

3232
steps:
3333
- uses: actions/checkout@v4
@@ -38,10 +38,10 @@ jobs:
3838
uses: dtolnay/rust-toolchain@stable
3939

4040
- name: install Linux dependencies
41-
if: matrix.platform == 'ubuntu-latest'
41+
if: matrix.platform == 'ubuntu-22.04'
4242
run: |
4343
sudo apt-get update
44-
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev libfuse2
44+
sudo apt-get install -y webkit2gtk-4.0 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev libfuse2
4545
4646
- uses: Swatinem/rust-cache@v2
4747

Cargo.lock

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
members = [
33
"plugins/*",
44
"plugins/*/tests/*",
5+
"plugins/updater/tests/updater-migration/v2-app",
56
"plugins/*/examples/*/src-tauri",
67
"examples/*/src-tauri",
78
]

plugins/updater/src/updater.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,8 @@ impl Update {
784784

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

787+
let permissions = std::fs::metadata(&self.extract_path)?.permissions();
788+
787789
// create a backup of our current app image
788790
std::fs::rename(&self.extract_path, tmp_app_image)?;
789791

@@ -812,7 +814,9 @@ impl Update {
812814
return Err(Error::BinaryNotFoundInArchive);
813815
}
814816

815-
return match std::fs::write(&self.extract_path, bytes) {
817+
return match std::fs::write(&self.extract_path, bytes)
818+
.and_then(|_| std::fs::set_permissions(&self.extract_path, permissions))
819+
{
816820
Err(err) => {
817821
// if something went wrong during the extraction, we should restore previous app
818822
std::fs::rename(tmp_app_image, &self.extract_path)?;

plugins/updater/tests/app-updater/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ fn main() {
4242
std::process::exit(0);
4343
}
4444
Ok(None) => {
45-
std::process::exit(0);
45+
std::process::exit(2);
4646
}
4747
Err(e) => {
4848
println!("{e}");

plugins/updater/tests/app-updater/tests/update.rs

Lines changed: 135 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,26 @@ use std::{
99
fs::File,
1010
path::{Path, PathBuf},
1111
process::Command,
12+
sync::Arc,
1213
};
1314

1415
use serde::Serialize;
16+
use tauri::utils::config::{Updater, V1Compatible};
1517

1618
const UPDATER_PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5TlFOMFpXYzJFOUdjeHJEVXY4WE1TMUxGNDJVUjNrMmk1WlR3UVJVUWwva0FBQkFBQUFBQUFBQUFBQUlBQUFBQUpVK3ZkM3R3eWhyN3hiUXhQb2hvWFVzUW9FbEs3NlNWYjVkK1F2VGFRU1FEaGxuRUtlell5U0gxYS9DbVRrS0YyZVJGblhjeXJibmpZeGJjS0ZKSUYwYndYc2FCNXpHalM3MHcrODMwN3kwUG9SOWpFNVhCSUd6L0E4TGRUT096TEtLR1JwT1JEVFU9Cg==";
19+
const UPDATED_EXIT_CODE: i32 = 0;
20+
const UP_TO_DATE_EXIT_CODE: i32 = 2;
1721

1822
#[derive(Serialize)]
1923
struct Config {
2024
version: &'static str,
25+
bundle: BundleConfig,
26+
}
27+
28+
#[derive(Serialize)]
29+
#[serde(rename_all = "camelCase")]
30+
struct BundleConfig {
31+
create_updater_artifacts: Updater,
2132
}
2233

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

160-
let mut config = Config { version: "1.0.0" };
161-
162-
// bundle app update
163-
build_app(&manifest_dir, &config, true, Default::default());
164-
165-
let updater_zip_ext = if cfg!(target_os = "macos") {
166-
Some("tar.gz")
167-
} else {
168-
None
169-
};
170-
171-
for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") {
172-
let bundle_updater_ext = out_bundle_path.extension().unwrap().to_str().unwrap();
173-
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
174-
format!("{bundle_updater_ext}.{updater_zip_ext}")
171+
for mut config in [
172+
Config {
173+
version: "1.0.0",
174+
bundle: BundleConfig {
175+
create_updater_artifacts: Updater::Bool(true),
176+
},
177+
},
178+
Config {
179+
version: "1.0.0",
180+
bundle: BundleConfig {
181+
create_updater_artifacts: Updater::String(V1Compatible::V1Compatible),
182+
},
183+
},
184+
] {
185+
let v1_compatible = matches!(
186+
config.bundle.create_updater_artifacts,
187+
Updater::String(V1Compatible::V1Compatible)
188+
);
189+
190+
// bundle app update
191+
build_app(&manifest_dir, &config, true, Default::default());
192+
193+
let updater_zip_ext = if v1_compatible {
194+
if cfg!(windows) {
195+
Some("zip")
196+
} else {
197+
Some("tar.gz")
198+
}
199+
} else if cfg!(target_os = "macos") {
200+
Some("tar.gz")
175201
} else {
176-
format!("{bundle_updater_ext}")
202+
None
177203
};
178-
let signature_extension = format!("{updater_extension}.sig");
179-
let signature_path = out_bundle_path.with_extension(signature_extension);
180-
let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| {
181-
panic!("failed to read signature file {}", signature_path.display())
182-
});
183-
let out_updater_path = out_bundle_path.with_extension(updater_extension);
184-
let updater_path = root_dir.join(format!(
185-
"target/debug/{}",
186-
out_updater_path.file_name().unwrap().to_str().unwrap()
187-
));
188-
std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle");
189-
190-
let target = target.clone();
191-
std::thread::spawn(move || {
204+
205+
for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") {
206+
let bundle_updater_ext = if v1_compatible {
207+
out_bundle_path
208+
.extension()
209+
.unwrap()
210+
.to_str()
211+
.unwrap()
212+
.replace("exe", "nsis")
213+
} else {
214+
out_bundle_path
215+
.extension()
216+
.unwrap()
217+
.to_str()
218+
.unwrap()
219+
.to_string()
220+
};
221+
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
222+
format!("{bundle_updater_ext}.{updater_zip_ext}")
223+
} else {
224+
format!("{bundle_updater_ext}")
225+
};
226+
let signature_extension = format!("{updater_extension}.sig");
227+
let signature_path = out_bundle_path.with_extension(signature_extension);
228+
let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| {
229+
panic!("failed to read signature file {}", signature_path.display())
230+
});
231+
let out_updater_path = out_bundle_path.with_extension(updater_extension);
232+
let updater_path = root_dir.join(format!(
233+
"target/debug/{}",
234+
out_updater_path.file_name().unwrap().to_str().unwrap()
235+
));
236+
std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle");
237+
238+
let target = target.clone();
239+
192240
// start the updater server
193-
let server =
194-
tiny_http::Server::http("localhost:3007").expect("failed to start updater server");
241+
let server = Arc::new(
242+
tiny_http::Server::http("localhost:3007").expect("failed to start updater server"),
243+
);
195244

196-
loop {
197-
if let Ok(request) = server.recv() {
245+
let server_ = server.clone();
246+
std::thread::spawn(move || {
247+
for request in server_.incoming_requests() {
198248
match request.url() {
199249
"/" => {
200250
let mut platforms = HashMap::new();
@@ -234,45 +284,63 @@ fn update_app() {
234284
)
235285
}),
236286
));
237-
// close server
238-
return;
239287
}
240288
_ => (),
241289
}
242290
}
291+
});
292+
293+
config.version = "0.1.0";
294+
295+
// bundle initial app version
296+
build_app(&manifest_dir, &config, false, bundle_target);
297+
298+
let status_checks = if matches!(bundle_target, BundleTarget::Msi) {
299+
// for msi we can't really check if the app was updated, because we can't change the install path
300+
vec![UPDATED_EXIT_CODE]
301+
} else {
302+
vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE]
303+
};
304+
305+
for expected_exit_code in status_checks {
306+
let mut binary_cmd = if cfg!(windows) {
307+
Command::new(root_dir.join("target/debug/app-updater.exe"))
308+
} else if cfg!(target_os = "macos") {
309+
Command::new(
310+
bundle_paths(&root_dir, "0.1.0")
311+
.first()
312+
.unwrap()
313+
.1
314+
.join("Contents/MacOS/app-updater"),
315+
)
316+
} else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
317+
let mut c = Command::new("xvfb-run");
318+
c.arg("--auto-servernum")
319+
.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1);
320+
c
321+
} else {
322+
Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1)
323+
};
324+
325+
binary_cmd.env("TARGET", bundle_target.name());
326+
327+
let status = binary_cmd.status().expect("failed to run app");
328+
let code = status.code().unwrap_or(-1);
329+
330+
if code != expected_exit_code {
331+
panic!(
332+
"failed to run app, expected exit code {expected_exit_code}, got {code}"
333+
);
334+
}
335+
#[cfg(windows)]
336+
if code == UPDATED_EXIT_CODE {
337+
// wait for the update to finish
338+
std::thread::sleep(std::time::Duration::from_secs(5));
339+
}
243340
}
244-
});
245-
246-
config.version = "0.1.0";
247-
248-
// bundle initial app version
249-
build_app(&manifest_dir, &config, false, bundle_target);
250-
251-
let mut binary_cmd = if cfg!(windows) {
252-
Command::new(root_dir.join("target/debug/app-updater.exe"))
253-
} else if cfg!(target_os = "macos") {
254-
Command::new(
255-
bundle_paths(&root_dir, "0.1.0")
256-
.first()
257-
.unwrap()
258-
.1
259-
.join("Contents/MacOS/app-updater"),
260-
)
261-
} else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
262-
let mut c = Command::new("xvfb-run");
263-
c.arg("--auto-servernum")
264-
.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1);
265-
c
266-
} else {
267-
Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1)
268-
};
269-
270-
binary_cmd.env("TARGET", bundle_target.name());
271-
272-
let status = binary_cmd.status().expect("failed to run app");
273341

274-
if !status.success() {
275-
panic!("failed to run app");
342+
// graceful shutdown
343+
server.unblock();
276344
}
277345
}
278346
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "updater-migration-test"
3+
version = "0.1.0"
4+
edition = { workspace = true }
5+
6+
[build-dependencies]
7+
tauri-build = { workspace = true }
8+
9+
[dependencies]
10+
tauri = { workspace = true, features = ["wry", "compression"] }
11+
serde = { workspace = true }
12+
serde_json = { workspace = true }
13+
tauri-plugin-updater = { path = "../.." }
14+
tiny_http = "0.12"
15+
time = { version = "0.3", features = ["formatting"] }
10.8 KB
Loading
22.6 KB
Loading

0 commit comments

Comments
 (0)