Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(updater): Add .deb Package Support to Linux Updater #1991

Merged
merged 45 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
72a4670
Added deb update support
jLynx Oct 31, 2024
c890ec6
WIP
jLynx Oct 31, 2024
552c7b8
WIP
jLynx Oct 31, 2024
0be9dbb
WIP
jLynx Nov 1, 2024
a0ad60b
updated logging
jLynx Nov 1, 2024
fb269f0
add tarball support
jLynx Nov 1, 2024
b178029
reverted to deb only
jLynx Nov 1, 2024
a3f8462
add tarball support
jLynx Nov 1, 2024
7f7b6fd
Removed logs
jLynx Nov 1, 2024
c88064b
removed spaces
jLynx Nov 1, 2024
9b5d30a
fixed comments
jLynx Nov 1, 2024
408ccba
Clean up comments
jLynx Nov 1, 2024
4d4f2f8
Fixed console log
jLynx Nov 1, 2024
37ec629
Removed gz support
jLynx Nov 3, 2024
f1e3c10
Merge branch 'v2' into fix/deb_update
jLynx Nov 3, 2024
3651103
Merge branch 'v2' into fix/deb_update
jLynx Nov 4, 2024
69d6a55
Merge branch 'v2' into fix/deb_update
jLynx Nov 12, 2024
be60315
Updated detection method
jLynx Nov 12, 2024
a8866b9
Fixed formatting
jLynx Nov 12, 2024
337e23e
refactor
jLynx Nov 12, 2024
af8c8d9
Merge branch 'v2' into fix/deb_update
jLynx Nov 12, 2024
d8dcfef
Added changes file
jLynx Nov 12, 2024
f9f9297
Added update with kdialog support
jLynx Nov 12, 2024
466cdae
Check if file is deb
jLynx Nov 12, 2024
7d70f62
Updated as per comments
jLynx Nov 12, 2024
ffec0ed
Added appimage implementaion
jLynx Nov 13, 2024
f79dfbf
WIP test for deb
jLynx Nov 13, 2024
7346ade
got deb running
jLynx Nov 13, 2024
5a95db8
Now runs both appimage and deb tests
jLynx Nov 13, 2024
cbb0813
Adjusted order
jLynx Nov 13, 2024
36469f2
Merge branch 'v2' into fix/deb_update
jLynx Nov 13, 2024
a13d5b4
Got the build working
jLynx Nov 13, 2024
9c4f9d8
Merge branch 'fix/deb_update' of https://github.com/jLynx/plugins-wor…
jLynx Nov 13, 2024
8f07405
Got deb tests passing
jLynx Nov 14, 2024
5622972
Attempt at buidling with GH Actions
jLynx Nov 14, 2024
cf50659
WIP GH Actions
jLynx Nov 14, 2024
44d2e77
WIP
jLynx Nov 14, 2024
6b21e88
wip
jLynx Nov 14, 2024
628a355
Reverted tests
jLynx Nov 24, 2024
cd37fc1
Reverted tests
jLynx Nov 24, 2024
a6b9da7
Reverted tests
jLynx Nov 24, 2024
9c5eaef
Merge branch 'v2' into fix/deb_update
jLynx Nov 24, 2024
338aedb
Fixed formatting
jLynx Nov 24, 2024
88b3995
Fixed formatting
jLynx Nov 25, 2024
ce46dd5
Merge branch 'v2' into fix/deb_update
jLynx Nov 25, 2024
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/deb-update-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"updater": "minor"
---

Added support for `.deb` package updates on Linux systems.
14 changes: 11 additions & 3 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,13 @@ jobs:
with:
fetch-depth: 0

# - name: install stable
jLynx marked this conversation as resolved.
Show resolved Hide resolved
# uses: dtolnay/rust-toolchain@stable

- name: install stable
uses: dtolnay/rust-toolchain@stable
run: |
sudo curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sudo sh -s -- -y
sudo rustup default stable

- name: install Linux dependencies
if: matrix.platform == 'ubuntu-22.04'
Expand All @@ -45,8 +50,11 @@ jobs:

- uses: Swatinem/rust-cache@v2

# - name: install Tauri CLI
# run: sudo cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev

- name: install Tauri CLI
run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev
run: sudo $(which cargo) install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev

- name: run integration tests
run: cargo test --test '*' -- --ignored
run: sudo cargo test --test '*' -- --ignored
6 changes: 6 additions & 0 deletions plugins/updater/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ pub enum Error {
TempDirNotOnSameMountPoint,
#[error("binary for the current target not found in the archive")]
BinaryNotFoundInArchive,
#[error("failed to create temporary directory")]
TempDirNotFound,
#[error("Authentication failed or was cancelled")]
AuthenticationFailed,
#[error("Failed to install .deb package")]
DebInstallFailed,
#[error("invalid updater binary format")]
InvalidUpdaterFormat,
#[error(transparent)]
Expand Down
171 changes: 167 additions & 4 deletions plugins/updater/src/updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ impl Update {
}
}

/// Linux (AppImage)
/// Linux (AppImage and Deb)
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
Expand All @@ -760,12 +760,19 @@ impl Update {
/// ### Expected structure:
/// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler
/// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage
/// ├── [AppName]_[version]_amd64.deb # Debian package
/// └── ...
///
/// We should have an AppImage already installed to be able to copy and install
/// the extract_path is the current AppImage path
/// tmp_dir is where our new AppImage is found
fn install_inner(&self, bytes: &[u8]) -> Result<()> {
if self.is_deb_package() {
amrbashir marked this conversation as resolved.
Show resolved Hide resolved
self.install_deb(bytes)
} else {
// Handle AppImage or other formats
self.install_appimage(bytes)
}
}

fn install_appimage(&self, bytes: &[u8]) -> Result<()> {
use std::os::unix::fs::{MetadataExt, PermissionsExt};
let extract_path_metadata = self.extract_path.metadata()?;

Expand Down Expand Up @@ -835,6 +842,162 @@ impl Update {

Err(Error::TempDirNotOnSameMountPoint)
}

fn is_deb_package(&self) -> bool {
// First check if we're in a typical Debian installation path
let in_system_path = self
.extract_path
.to_str()
.map(|p| p.starts_with("/usr"))
.unwrap_or(false);

if !in_system_path {
return false;
}

// Then verify it's actually a Debian-based system by checking for dpkg
let dpkg_exists = std::path::Path::new("/var/lib/dpkg").exists();
let apt_exists = std::path::Path::new("/etc/apt").exists();

// Additional check for the package in dpkg database
let package_in_dpkg = if let Ok(output) = std::process::Command::new("dpkg")
.args(["-S", &self.extract_path.to_string_lossy()])
.output()
{
output.status.success()
} else {
false
};

// Consider it a deb package only if:
// 1. We're in a system path AND
// 2. We have Debian package management tools AND
// 3. The binary is tracked by dpkg
dpkg_exists && apt_exists && package_in_dpkg
}

fn install_deb(&self, bytes: &[u8]) -> Result<()> {
// First verify the bytes are actually a .deb package
if !infer::archive::is_deb(bytes) {
return Err(Error::InvalidUpdaterFormat);
}

// Try different temp directories
let tmp_dir_locations = vec![
Box::new(|| Some(std::env::temp_dir())) as Box<dyn FnOnce() -> Option<PathBuf>>,
Box::new(dirs::cache_dir),
Box::new(|| Some(self.extract_path.parent().unwrap().to_path_buf())),
];

// Try writing to multiple temp locations until one succeeds
for tmp_dir_location in tmp_dir_locations {
if let Some(path) = tmp_dir_location() {
if let Ok(tmp_dir) = tempfile::Builder::new()
.prefix("tauri_deb_update")
.tempdir_in(path)
{
let deb_path = tmp_dir.path().join("package.deb");

// Try writing the .deb file
if std::fs::write(&deb_path, bytes).is_ok() {
// If write succeeds, proceed with installation
return self.try_install_with_privileges(&deb_path);
}
// If write fails, continue to next temp location
}
}
}

// If we get here, all temp locations failed
Err(Error::TempDirNotFound)
}

fn try_install_with_privileges(&self, deb_path: &Path) -> Result<()> {
// 1. First try using pkexec (graphical sudo prompt)
if let Ok(status) = std::process::Command::new("pkexec")
.arg("dpkg")
.arg("-i")
.arg(deb_path)
.status()
{
if status.success() {
return Ok(());
}
}

// 2. Try zenity or kdialog for a graphical sudo experience
if let Ok(password) = self.get_password_graphically() {
if self.install_with_sudo(deb_path, &password)? {
return Ok(());
}
}

// 3. Final fallback: terminal sudo
let status = std::process::Command::new("sudo")
.arg("dpkg")
.arg("-i")
.arg(deb_path)
.status()?;
amrbashir marked this conversation as resolved.
Show resolved Hide resolved

if status.success() {
Ok(())
} else {
Err(Error::DebInstallFailed)
}
}

fn get_password_graphically(&self) -> Result<String> {
// Try zenity first
let zenity_result = std::process::Command::new("zenity")
.args([
"--password",
"--title=Authentication Required",
"--text=Enter your password to install the update:",
])
.output();

if let Ok(output) = zenity_result {
if output.status.success() {
return Ok(String::from_utf8_lossy(&output.stdout).trim().to_string());
}
}

// Fall back to kdialog if zenity fails or isn't available
let kdialog_result = std::process::Command::new("kdialog")
.args(["--password", "Enter your password to install the update:"])
.output();

if let Ok(output) = kdialog_result {
if output.status.success() {
return Ok(String::from_utf8_lossy(&output.stdout).trim().to_string());
}
}

Err(Error::AuthenticationFailed)
}

fn install_with_sudo(&self, deb_path: &Path, password: &str) -> Result<bool> {
use std::io::Write;
use std::process::{Command, Stdio};

let mut child = Command::new("sudo")
.arg("-S") // read password from stdin
.arg("dpkg")
.arg("-i")
.arg(deb_path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;

if let Some(mut stdin) = child.stdin.take() {
// Write password to stdin
writeln!(stdin, "{}", password)?;
}

let status = child.wait()?;
Ok(status.success())
}
}

/// MacOS
Expand Down
Loading
Loading