Skip to content

Commit c5e9e0f

Browse files
committed
Add self-updating support
1 parent 0fdf6b2 commit c5e9e0f

File tree

4 files changed

+137
-10
lines changed

4 files changed

+137
-10
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "qud"
3-
version = "1.2.7"
3+
version = "1.2.8"
44
edition = "2021"
55
license = "GPL-3"
66
authors = ["barely-a-dev"]

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
`qud` is a command-line tool that **automatically detects and updates** various package managers across Linux, Windows,
44
macOS, and more.
55

6-
> **Latest Version:** **v1.2.7**
6+
> **Latest Version:** **v1.2.8**
77
88
## Table of Contents
99

@@ -45,10 +45,9 @@ macOS, and more.
4545
git clone https://github.com/barely-a-dev/qud.git
4646
cd qud
4747
cargo build --release
48+
cp ./target/release/qud /usr/bin/qud
4849
```
4950

50-
Add `target/release/qud` to your `PATH`.
51-
5251
### Prebuilt Packages
5352

5453
#### Arch Linux:
@@ -60,7 +59,7 @@ sudo pacman -U qud-<version>-x86_64.pkg.tar.zst
6059
#### Debian-based:
6160

6261
```bash
63-
sudo apt install ./qud_v1.2.7_amd64.deb
62+
sudo apt install ./qud_v1.2.8_amd64.deb
6463
```
6564

6665
## Usage
@@ -175,7 +174,7 @@ Submit issues or pull requests on [GitHub](https://github.com/barely-a-dev/qud).
175174

176175
## Version
177176

178-
**v1.2.7**
177+
**v1.2.8**
179178

180179
For assistance, visit [GitHub](https://github.com/barely-a-dev/qud). Happy updating!
181180

src/main.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![allow(clippy::doc_markdown)]
22

3+
mod self_up;
4+
35
use pico_args::Arguments;
46
use std::collections::{HashMap, HashSet};
57
use std::env;
@@ -13,7 +15,8 @@ use walkdir::WalkDir;
1315
// Windows: choco, scoop, winget
1416
// General: rustup, brew, port (MacPorts), pkg (FreeBSD), cargo, npm, pip, composer, gem, conda, poetry, nuget, asdf, vcpkg, conan, stack, opam, mix, sdkman
1517
// gvm, pnpm, yarn, maven, go
16-
const PM: [&str; 45] = [
18+
// and qud itself.
19+
const PM: [&str; 46] = [
1720
"pacman",
1821
"yay",
1922
"apt",
@@ -59,6 +62,7 @@ const PM: [&str; 45] = [
5962
"cave",
6063
"sbopkg",
6164
"scratch",
65+
"qud",
6266
];
6367

6468
/// Determines how to order package manager updates.
@@ -93,18 +97,31 @@ struct Config {
9397
}
9498

9599
impl Config {
100+
#[allow(clippy::too_many_lines)]
96101
fn parse_args() -> Config {
97102
let mut pargs = Arguments::from_env();
98103

99-
// Help and version.
104+
// Help, version, and self updating.
100105
if pargs.contains(["-h", "--help"]) {
101106
Self::print_help();
102107
std::process::exit(0);
103108
}
104109
if pargs.contains(["-V", "--version"]) {
105-
println!("qud v1.2.7");
110+
println!("qud v1.2.8");
106111
std::process::exit(0);
107112
}
113+
if pargs.contains(["-S", "--self-update"]) {
114+
if !self_up::perm::is_elevated() {
115+
eprintln!("Program must be run as root to update.");
116+
std::process::exit(2);
117+
}
118+
println!("Updating qud...");
119+
match self_up::self_update() {
120+
Ok(()) => println!("qud updated successfully, exiting."),
121+
Err(e) => eprintln!("qud failed to update: {e}, exiting."),
122+
}
123+
std::process::exit(-1);
124+
}
108125

109126
let dry_run = pargs.contains(["-d", "--dry"]);
110127
let excl_values: Vec<String> = pargs
@@ -195,7 +212,7 @@ impl Config {
195212

196213
fn print_help() {
197214
println!(
198-
r#"qud v1.2.7
215+
r#"qud v1.2.8
199216
200217
Usage:
201218
qud [options]
@@ -212,6 +229,7 @@ Options:
212229
--ord, -O [s] Specify the update order. If provided a value (pm1,pm2,...), that order is used for those found; if no value is provided, you'll be prompted to sort.
213230
--help, -h Show this help screen.
214231
--version, -V Show version information.
232+
--self-update, -S Update qud.
215233
"#
216234
);
217235
}
@@ -795,6 +813,9 @@ fn process_pm(pm_name: &str, auto: bool, current_dir: &Path, extra_args: &[Strin
795813
};
796814
upd("scratch", args, true, extra_args, dry_run);
797815
}
816+
"qud" => {
817+
upd("qud", &["--self-update"], true, extra_args, dry_run);
818+
}
798819
_ => eprintln!("\x1b[93mWarning:\x1b[0m Unknown package manager: {pm_name}"),
799820
}
800821
}

src/self_up.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use std::{
2+
error::Error,
3+
fs,
4+
path::PathBuf,
5+
process::Command,
6+
};
7+
8+
pub fn self_update() -> Result<(), Box<dyn Error>> {
9+
let repo_url = "https://github.com/barely-a-dev/qud.git";
10+
11+
let temp_dir = std::env::temp_dir().join("qud_temp");
12+
if temp_dir.exists() {
13+
fs::remove_dir_all(&temp_dir)?;
14+
}
15+
fs::create_dir(&temp_dir)?;
16+
let clone_path = temp_dir.join("qud");
17+
println!("Cloning repository into {clone_path:?}");
18+
19+
let status = Command::new("git")
20+
.args(["clone", repo_url, clone_path.to_str().unwrap()])
21+
.status()?;
22+
if !status.success() {
23+
return Err("Git clone failed".into());
24+
}
25+
26+
println!("Building project (release mode)...");
27+
let status = Command::new("cargo")
28+
.args(["build", "--release"])
29+
.current_dir(&clone_path)
30+
.status()?;
31+
if !status.success() {
32+
return Err("Cargo build failed".into());
33+
}
34+
35+
// Determine the binary name based on the OS.
36+
#[cfg(target_family = "windows")]
37+
let binary_name = "qud.exe";
38+
#[cfg(not(target_family = "windows"))]
39+
let binary_name = "qud";
40+
41+
let binary_path = clone_path.join("target").join("release").join(binary_name);
42+
if !binary_path.exists() {
43+
return Err(format!("Built binary not found at {binary_path:?}").into());
44+
}
45+
46+
#[cfg(not(target_family = "windows"))]
47+
{
48+
use std::os::unix::fs::PermissionsExt;
49+
let mut perms = fs::metadata(&binary_path)?.permissions();
50+
perms.set_mode(0o755);
51+
fs::set_permissions(&binary_path, perms)?;
52+
}
53+
54+
#[cfg(not(target_family = "windows"))]
55+
let install_path = PathBuf::from("/usr/bin/qud");
56+
57+
#[cfg(target_family = "windows")]
58+
let install_path = {
59+
let target_dir = PathBuf::from("C:\\Program Files\\qud");
60+
fs::create_dir_all(&target_dir)?;
61+
target_dir.join("qud.exe")
62+
};
63+
64+
println!(
65+
"Copying built binary from {binary_path:?} to {install_path:?}",
66+
);
67+
fs::copy(&binary_path, &install_path)?;
68+
69+
println!("Self-update successful!");
70+
71+
fs::remove_dir_all(&temp_dir)?;
72+
73+
Ok(())
74+
}
75+
76+
pub mod perm {
77+
#[cfg(not(target_family = "windows"))]
78+
mod platform {
79+
use std::os::raw::c_uint;
80+
81+
extern "C" {
82+
fn geteuid() -> c_uint;
83+
}
84+
85+
pub fn is_elevated() -> bool {
86+
unsafe { geteuid() == 0 }
87+
}
88+
}
89+
90+
#[cfg(target_family = "windows")]
91+
mod platform {
92+
// Link to shell32.dll where IsUserAnAdmin is defined.
93+
#[link(name = "shell32")]
94+
extern "system" {
95+
fn IsUserAnAdmin() -> i32;
96+
}
97+
98+
pub fn is_elevated() -> bool {
99+
// IsUserAnAdmin returns a nonzero value if the user is an administrator.
100+
unsafe { IsUserAnAdmin() != 0 }
101+
}
102+
}
103+
104+
pub fn is_elevated() -> bool {
105+
platform::is_elevated()
106+
}
107+
}

0 commit comments

Comments
 (0)