1use crate::{Error, InstallerKind, Result, Update};
4use fs_err as fs;
5use std::{
6 path::{Path, PathBuf},
7 process::Command,
8};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct LinuxInstallCommand {
13 pub program: String,
15 pub args: Vec<String>,
17}
18
19impl LinuxInstallCommand {
20 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}