Skip to main content

release_hub/
linux.rs

1//! Linux-specific installation helpers.
2
3use crate::{Error, InstallerKind, Result, Update};
4use fs_err as fs;
5use std::{
6    path::{Path, PathBuf},
7    process::Command,
8};
9
10/// Linux command description for package-manager-backed installs.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct LinuxInstallCommand {
13    /// Executable to launch.
14    pub program: String,
15    /// Arguments passed to the executable.
16    pub args: Vec<String>,
17}
18
19impl LinuxInstallCommand {
20    /// Builds the Linux install command for a staged artifact.
21    ///
22    /// `.deb` and `.rpm` artifacts are installed through `pkexec`, while
23    /// AppImages are staged through `install` before the final atomic swap.
24    pub fn for_kind(kind: InstallerKind, artifact: PathBuf) -> Result<Self> {
25        let path = artifact.display().to_string();
26        match kind {
27            InstallerKind::AppImage => Ok(Self {
28                program: "install".into(),
29                args: vec![
30                    "-m".into(),
31                    "755".into(),
32                    path.clone(),
33                    format!("{path}.new"),
34                ],
35            }),
36            InstallerKind::Deb => Ok(Self {
37                program: "pkexec".into(),
38                args: vec!["dpkg".into(), "-i".into(), path],
39            }),
40            InstallerKind::Rpm => Ok(Self {
41                program: "pkexec".into(),
42                args: vec!["rpm".into(), "-U".into(), path],
43            }),
44            _ => unreachable!("non-linux installer kind"),
45        }
46    }
47}
48
49impl Update {
50    pub(crate) fn install_linux(&self, bytes: &[u8]) -> Result<()> {
51        if self.installer_kind == InstallerKind::AppImage {
52            return install_appimage(bytes, &self.extract_path);
53        }
54
55        let staging_dir = tempfile::Builder::new()
56            .prefix("release-hub-linux-installer-")
57            .tempdir()?;
58        let artifact_name = self
59            .download_url
60            .path_segments()
61            .and_then(|mut segments| segments.rfind(|segment| !segment.is_empty()))
62            .unwrap_or("release-hub-installer.bin");
63        let artifact_path = staging_dir.path().join(artifact_name);
64
65        fs::write(&artifact_path, bytes)?;
66
67        let command = LinuxInstallCommand::for_kind(self.installer_kind.clone(), artifact_path)?;
68        let status = Command::new(&command.program)
69            .args(&command.args)
70            .status()?;
71        if status.success() {
72            Ok(())
73        } else {
74            Err(Error::InstallerExecutionFailed(status.code().unwrap_or(-1)))
75        }
76    }
77}
78
79fn install_appimage(bytes: &[u8], target_path: &Path) -> Result<()> {
80    if let Some(parent) = target_path.parent() {
81        fs::create_dir_all(parent)?;
82    }
83
84    let staging_path = appimage_staging_path(target_path);
85    fs::write(&staging_path, bytes)?;
86    #[cfg(unix)]
87    {
88        use std::{fs::Permissions, os::unix::fs::PermissionsExt};
89
90        fs::set_permissions(&staging_path, Permissions::from_mode(0o755))?;
91    }
92    fs::rename(&staging_path, target_path)?;
93    Ok(())
94}
95
96fn appimage_staging_path(target_path: &Path) -> PathBuf {
97    PathBuf::from(format!("{}.new", target_path.display()))
98}