|
1 |
| -use std::io::{self, Write}; |
2 |
| -use std::process::{Command, Stdio}; |
3 |
| -use std::thread; |
4 |
| -use std::time::Duration; |
5 |
| -use colored::*; |
6 |
| -use indicatif::{ProgressBar, ProgressStyle, MultiProgress}; |
7 |
| -use crossterm::{ |
8 |
| - event::{self, Event, KeyCode}, |
9 |
| - execute, |
10 |
| - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, |
11 |
| -}; |
12 |
| -use std::fs; |
| 1 | +use std::process::{Command, ExitStatus}; |
| 2 | +use std::env; |
| 3 | +use std::os::unix::fs::PermissionsExt; |
13 | 4 |
|
14 |
| -fn main() -> io::Result<()> { |
15 |
| - // Enter alternate screen and enable raw mode for key input |
16 |
| - execute!(io::stdout(), EnterAlternateScreen)?; |
17 |
| - enable_raw_mode()?; |
18 |
| - // Print stylized header with gradient-like effect |
19 |
| - println!("{}", "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓".bright_green().bold()); |
20 |
| - println!("{}", "┃ Hacker-Update ┃".bright_cyan().bold().on_bright_black()); |
21 |
| - println!("{}", "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛".bright_green().bold()); |
22 |
| - println!("{}", " Initializing system updates...".bright_blue().italic()); |
23 |
| - println!(); |
24 |
| - // Define update commands with associated colors for visual distinction |
25 |
| - let update_commands = vec![ |
26 |
| - ("DNF System Update", vec![ |
27 |
| - ("sudo /usr/lib/HackerOS/dnf update -y", Color::BrightMagenta), |
28 |
| - ("sudo /usr/lib/HackerOS/dnf upgrade -y", Color::BrightMagenta), |
29 |
| - ("sudo /usr/lib/HackerOS/dnf autoremove -y", Color::BrightMagenta), |
30 |
| - ]), |
31 |
| - ("Flatpak Update", vec![("flatpak update -y", Color::BrightYellow)]), |
32 |
| - ("Snap Update", vec![("sudo snap refresh", Color::BrightBlue)]), |
33 |
| - ("Firmware Update", vec![ |
34 |
| - ("sudo fwupdmgr refresh", Color::BrightGreen), |
35 |
| - ("sudo fwupdmgr update", Color::BrightGreen), |
36 |
| - ]), |
37 |
| - ]; |
38 |
| - // Store logs |
39 |
| - let mut logs: Vec<String> = Vec::new(); |
40 |
| - // Initialize MultiProgress for concurrent progress bars |
41 |
| - let multi_pb = MultiProgress::new(); |
42 |
| - // Run each update command with enhanced progress bar |
43 |
| - for (update_name, commands) in update_commands { |
44 |
| - println!("{}", format!(" Starting {} ", update_name).white().bold().on_color(update_name_color(update_name))); |
45 |
| - let pb = multi_pb.add(ProgressBar::new(100)); |
46 |
| - pb.set_style( |
47 |
| - ProgressStyle::default_bar() |
48 |
| - .template("{spinner:.cyan} [{elapsed_precise}] {bar:50.green/blue} {msg} {percent:>3}%") |
49 |
| - .unwrap() |
50 |
| - .progress_chars("█▉▊▋▌▍▎▏ ") |
51 |
| - .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"), |
52 |
| - ); |
53 |
| - for (_i, (cmd, color)) in commands.iter().enumerate() { |
54 |
| - pb.set_message(format!("{}", cmd.color(*color).bold()).to_string()); |
55 |
| - let output = Command::new("sh") |
56 |
| - .arg("-c") |
57 |
| - .arg(cmd) |
58 |
| - .stdout(Stdio::piped()) |
59 |
| - .stderr(Stdio::piped()) |
60 |
| - .output(); |
61 |
| - match output { |
62 |
| - Ok(output) => { |
63 |
| - let stdout = String::from_utf8_lossy(&output.stdout).to_string(); |
64 |
| - let stderr = String::from_utf8_lossy(&output.stderr).to_string(); |
65 |
| - logs.push(format!("{} Output:\n{}", cmd, stdout)); |
66 |
| - if !stderr.is_empty() { |
67 |
| - logs.push(format!("{} Errors:\n{}", cmd, stderr)); |
68 |
| - } |
69 |
| - if !output.status.success() { |
70 |
| - println!("{}", format!(" Error running {} ", cmd).white().bold().on_bright_red()); |
71 |
| - } else { |
72 |
| - println!("{}", format!(" Completed {} ", cmd).white().bold().on_bright_green()); |
73 |
| - } |
74 |
| - } |
75 |
| - Err(e) => { |
76 |
| - logs.push(format!("Failed to execute {}: {}", cmd, e)); |
77 |
| - println!("{}", format!(" Failed to execute {}: {} ", cmd, e).white().bold().on_bright_red()); |
78 |
| - } |
79 |
| - } |
80 |
| - pb.inc(100 / commands.len() as u64); |
81 |
| - thread::sleep(Duration::from_millis(200)); |
82 |
| - } |
83 |
| - pb.finish_with_message(format!(" {} completed ", update_name).white().bold().to_string()); |
84 |
| - println!(); |
| 5 | +const DNF_PATH: &str = "/usr/lib/HackerOS/dnf"; |
| 6 | + |
| 7 | +fn print_help() { |
| 8 | + println!("hacker: A simple package management tool"); |
| 9 | + println!("Usage: hacker <command> [arguments]"); |
| 10 | + println!("\nAvailable commands:"); |
| 11 | + println!(" autoremove Remove unneeded packages"); |
| 12 | + println!(" install <packages> Install one or more packages"); |
| 13 | + println!(" remove <packages> Remove one or more packages"); |
| 14 | + println!(" list List installed packages"); |
| 15 | + println!(" search <term> Search for packages"); |
| 16 | + println!(" clean Clean package cache"); |
| 17 | + println!(" info <package> Show package information"); |
| 18 | + println!(" repolist List enabled repositories"); |
| 19 | + println!(" copr-enable <repo> Enable a COPR repository"); |
| 20 | + println!(" copr-disable <repo> Disable a COPR repository"); |
| 21 | + println!(" ? Show this help message"); |
| 22 | + println!("\nNote: Use 'hacker-update' for system updates and upgrades."); |
| 23 | +} |
| 24 | + |
| 25 | +fn execute_dnf(args: Vec<&str>, use_sudo: bool) -> Result<ExitStatus, String> { |
| 26 | + let mut command = if use_sudo { |
| 27 | + let mut cmd = Command::new("sudo"); |
| 28 | + cmd.arg(DNF_PATH); |
| 29 | + cmd |
| 30 | + } else { |
| 31 | + Command::new(DNF_PATH) |
| 32 | + }; |
| 33 | + |
| 34 | + let output = command |
| 35 | + .args(&args) |
| 36 | + .status() |
| 37 | + .map_err(|e| format!("Failed to execute dnf: {}", e))?; |
| 38 | + Ok(output) |
| 39 | +} |
| 40 | + |
| 41 | +fn can_run_without_sudo() -> bool { |
| 42 | + // Check if user has write permissions to /usr/lib/HackerOS/dnf |
| 43 | + if let Ok(metadata) = std::fs::metadata(DNF_PATH) { |
| 44 | + let permissions = metadata.permissions(); |
| 45 | + let mode = permissions.mode(); |
| 46 | + // Check if executable and writable by user or group |
| 47 | + (mode & 0o111) != 0 && (mode & 0o600) != 0 |
| 48 | + } else { |
| 49 | + false |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +fn main() { |
| 54 | + let args: Vec<String> = env::args().collect(); |
| 55 | + if args.len() < 2 { |
| 56 | + println!("Error: No command provided"); |
| 57 | + print_help(); |
| 58 | + std::process::exit(1); |
85 | 59 | }
|
86 |
| - // Run the custom script |
87 |
| - let script_path = "/usr/share/HackerOS/Scripts/Bin/Update-usrshare.sh"; |
88 |
| - if fs::metadata(script_path).is_ok() { |
89 |
| - println!("{}", " Starting custom update script ".white().bold().on_bright_purple()); |
90 |
| - let pb = multi_pb.add(ProgressBar::new_spinner()); |
91 |
| - pb.set_style( |
92 |
| - ProgressStyle::default_spinner() |
93 |
| - .template("{spinner:.purple} {msg} [{elapsed_precise}]") |
94 |
| - .unwrap() |
95 |
| - .tick_chars("⣾⣷⣯⣟⡿⢿⣻⣽"), |
96 |
| - ); |
97 |
| - pb.set_message("Executing Update-usrshare.sh".to_string()); |
98 |
| - let output = Command::new("sh") |
99 |
| - .arg(script_path) |
100 |
| - .stdout(Stdio::piped()) |
101 |
| - .stderr(Stdio::piped()) |
102 |
| - .output(); |
103 |
| - match output { |
104 |
| - Ok(output) => { |
105 |
| - let stdout = String::from_utf8_lossy(&output.stdout).to_string(); |
106 |
| - let stderr = String::from_utf8_lossy(&output.stderr).to_string(); |
107 |
| - logs.push(format!("Update-usrshare.sh Output:\n{}", stdout)); |
108 |
| - if !stderr.is_empty() { |
109 |
| - logs.push(format!("Update-usrshare.sh Errors:\n{}", stderr)); |
110 |
| - } |
111 |
| - if !output.status.success() { |
112 |
| - println!("{}", " Error running custom script ".white().bold().on_bright_red()); |
113 |
| - } else { |
114 |
| - println!("{}", " Completed custom script ".white().bold().on_bright_green()); |
115 |
| - } |
116 |
| - } |
117 |
| - Err(e) => { |
118 |
| - logs.push(format!("Failed to execute script: {}", e)); |
119 |
| - println!("{}", format!(" Failed to execute script: {} ", e).white().bold().on_bright_red()); |
| 60 | + |
| 61 | + let command = &args[1]; |
| 62 | + let use_sudo = !can_run_without_sudo(); |
| 63 | + |
| 64 | + match command.as_str() { |
| 65 | + "autoremove" => { |
| 66 | + match execute_dnf(vec!["autoremove", "-y"], use_sudo) { |
| 67 | + Ok(status) if status.success() => println!("Autoremove completed successfully"), |
| 68 | + Ok(_) => println!("Autoremove failed"), |
| 69 | + Err(e) => println!("Error: {}", e), |
120 | 70 | }
|
121 | 71 | }
|
122 |
| - pb.finish_with_message(" Script execution completed ".to_string()); |
123 |
| - } else { |
124 |
| - println!("{}", " Custom script not found ".white().bold().on_bright_red()); |
125 |
| - logs.push("Custom script not found!".to_string()); |
126 |
| - } |
127 |
| - // Display stylized menu with gradient borders |
128 |
| - loop { |
129 |
| - println!(); |
130 |
| - println!("{}", "╔════════════════════════════════════════════════╗".bright_cyan().bold()); |
131 |
| - println!("{}", "║ Update Completed! ║".white().bold().on_bright_black()); |
132 |
| - println!("{}", "╠════════════════════════════════════════════════╣".bright_cyan().bold()); |
133 |
| - println!("{}", "║ (E)xit (S)hutdown (R)eboot ║".bright_yellow().bold()); |
134 |
| - println!("{}", "║ (L)og Out (T)ry again (S)ow logs ║".bright_yellow().bold()); |
135 |
| - println!("{}", "╚════════════════════════════════════════════════╝".bright_cyan().bold()); |
136 |
| - println!("{}", " Select an option: ".white().italic()); |
137 |
| - io::stdout().flush()?; |
138 |
| - if let Event::Key(key_event) = event::read()? { |
139 |
| - match key_event.code { |
140 |
| - KeyCode::Char('e') | KeyCode::Char('E') => { |
141 |
| - println!("{}", " Exiting ".white().bold().on_bright_blue()); |
142 |
| - break; |
143 |
| - } |
144 |
| - KeyCode::Char('s') | KeyCode::Char('S') => { |
145 |
| - println!("{}", " Shutting down ".white().bold().on_bright_blue()); |
146 |
| - let _ = Command::new("sudo").arg("poweroff").output(); |
147 |
| - break; |
148 |
| - } |
149 |
| - KeyCode::Char('r') | KeyCode::Char('R') => { |
150 |
| - println!("{}", " Rebooting ".white().bold().on_bright_blue()); |
151 |
| - let _ = Command::new("sudo").arg("reboot").output(); |
152 |
| - break; |
153 |
| - } |
154 |
| - KeyCode::Char('l') | KeyCode::Char('L') => { |
155 |
| - println!("{}", " Logging out ".white().bold().on_bright_blue()); |
156 |
| - let _ = Command::new("pkill").arg("-u").arg(&whoami::username()).output(); |
157 |
| - break; |
158 |
| - } |
159 |
| - KeyCode::Char('t') | KeyCode::Char('T') => { |
160 |
| - println!("{}", " Restarting updates ".white().bold().on_bright_blue()); |
161 |
| - let _ = execute!(io::stdout(), LeaveAlternateScreen)?; |
162 |
| - disable_raw_mode()?; |
163 |
| - main()?; |
164 |
| - return Ok(()); |
165 |
| - } |
166 |
| - KeyCode::Char('h') | KeyCode::Char('H') => { |
167 |
| - println!("{}", " Update Logs ".white().bold().on_bright_cyan()); |
168 |
| - println!("{}", "╔════════════════════════════════════════════════╗".bright_cyan().bold()); |
169 |
| - for log in &logs { |
170 |
| - println!("{}", format!("║ {} ", log).white().on_bright_black()); |
171 |
| - } |
172 |
| - println!("{}", "╚════════════════════════════════════════════════╝".bright_cyan().bold()); |
173 |
| - } |
174 |
| - _ => { |
175 |
| - println!("{}", " Invalid option, try again. ".white().bold().on_bright_red()); |
176 |
| - } |
| 72 | + "install" => { |
| 73 | + if args.len() < 3 { |
| 74 | + println!("Error: At least one package name required for install"); |
| 75 | + std::process::exit(1); |
| 76 | + } |
| 77 | + let packages = &args[2..]; |
| 78 | + let mut dnf_args = vec!["install", "-y"]; |
| 79 | + dnf_args.extend(packages.iter().map(|s| s.as_str())); |
| 80 | + match execute_dnf(dnf_args, use_sudo) { |
| 81 | + Ok(status) if status.success() => println!("Package(s) {} installed successfully", packages.join(" ")), |
| 82 | + Ok(_) => println!("Failed to install package(s) {}", packages.join(" ")), |
| 83 | + Err(e) => println!("Error: {}", e), |
177 | 84 | }
|
178 | 85 | }
|
179 |
| - } |
180 |
| - // Cleanup |
181 |
| - execute!(io::stdout(), LeaveAlternateScreen)?; |
182 |
| - disable_raw_mode()?; |
183 |
| - Ok(()) |
184 |
| -} |
185 |
| - |
186 |
| -// Helper function to assign background colors based on update type |
187 |
| -fn update_name_color(update_name: &str) -> Color { |
188 |
| - match update_name { |
189 |
| - "DNF System Update" => Color::BrightMagenta, |
190 |
| - "Flatpak Update" => Color::BrightYellow, |
191 |
| - "Snap Update" => Color::BrightBlue, |
192 |
| - "Firmware Update" => Color::BrightGreen, |
193 |
| - _ => Color::BrightBlack, |
| 86 | + "remove" => { |
| 87 | + if args.len() < 3 { |
| 88 | + println!("Error: At least one package name required for remove"); |
| 89 | + std::process::exit(1); |
| 90 | + } |
| 91 | + let packages = &args[2..]; |
| 92 | + let mut dnf_args = vec!["remove", "-y"]; |
| 93 | + dnf_args.extend(packages.iter().map(|s| s.as_str())); |
| 94 | + match execute_dnf(dnf_args, use_sudo) { |
| 95 | + Ok(status) if status.success() => println!("Package(s) {} removed successfully", packages.join(" ")), |
| 96 | + Ok(_) => println!("Failed to remove package(s) {}", packages.join(" ")), |
| 97 | + Err(e) => println!("Error: {}", e), |
| 98 | + } |
| 99 | + } |
| 100 | + "list" => { |
| 101 | + match execute_dnf(vec!["list", "installed"], use_sudo) { |
| 102 | + Ok(status) if status.success() => println!("Listed installed packages"), |
| 103 | + Ok(_) => println!("Failed to list packages"), |
| 104 | + Err(e) => println!("Error: {}", e), |
| 105 | + } |
| 106 | + } |
| 107 | + "search" => { |
| 108 | + if args.len() < 3 { |
| 109 | + println!("Error: Search term required"); |
| 110 | + std::process::exit(1); |
| 111 | + } |
| 112 | + let term = &args[2]; |
| 113 | + match execute_dnf(vec!["search", term], use_sudo) { |
| 114 | + Ok(status) if status.success() => println!("Search completed"), |
| 115 | + Ok(_) => println!("Search failed"), |
| 116 | + Err(e) => println!("Error: {}", e), |
| 117 | + } |
| 118 | + } |
| 119 | + "clean" => { |
| 120 | + match execute_dnf(vec!["clean", "all"], use_sudo) { |
| 121 | + Ok(status) if status.success() => println!("Package cache cleaned successfully"), |
| 122 | + Ok(_) => println!("Failed to clean package cache"), |
| 123 | + Err(e) => println!("Error: {}", e), |
| 124 | + } |
| 125 | + } |
| 126 | + "info" => { |
| 127 | + if args.len() < 3 { |
| 128 | + println!("Error: Package name required for info"); |
| 129 | + std::process::exit(1); |
| 130 | + } |
| 131 | + let package = &args[2]; |
| 132 | + match execute_dnf(vec!["info", package], use_sudo) { |
| 133 | + Ok(status) if status.success() => println!("Package information displayed"), |
| 134 | + Ok(_) => println!("Failed to display package information"), |
| 135 | + Err(e) => println!("Error: {}", e), |
| 136 | + } |
| 137 | + } |
| 138 | + "repolist" => { |
| 139 | + match execute_dnf(vec!["repolist"], use_sudo) { |
| 140 | + Ok(status) if status.success() => println!("Repository list displayed"), |
| 141 | + Ok(_) => println!("Failed to display repository list"), |
| 142 | + Err(e) => println!("Error: {}", e), |
| 143 | + } |
| 144 | + } |
| 145 | + "copr-enable" => { |
| 146 | + if args.len() < 3 { |
| 147 | + println!("Error: COPR repository name required"); |
| 148 | + std::process::exit(1); |
| 149 | + } |
| 150 | + let repo = &args[2]; |
| 151 | + match execute_dnf(vec!["copr", "enable", repo], use_sudo) { |
| 152 | + Ok(status) if status.success() => println!("COPR repository {} enabled", repo), |
| 153 | + Ok(_) => println!("Failed to enable COPR repository {}", repo), |
| 154 | + Err(e) => println!("Error: {}", e), |
| 155 | + } |
| 156 | + } |
| 157 | + "copr-disable" => { |
| 158 | + if args.len() < 3 { |
| 159 | + println!("Error: COPR repository name required"); |
| 160 | + std::process::exit(1); |
| 161 | + } |
| 162 | + let repo = &args[2]; |
| 163 | + match execute_dnf(vec!["copr", "disable", repo], use_sudo) { |
| 164 | + Ok(status) if status.success() => println!("COPR repository {} disabled", repo), |
| 165 | + Ok(_) => println!("Failed to disable COPR repository {}", repo), |
| 166 | + Err(e) => println!("Error: {}", e), |
| 167 | + } |
| 168 | + } |
| 169 | + "update" | "upgrade" => { |
| 170 | + println!("Error: Use 'hacker-update' for system updates and upgrades."); |
| 171 | + std::process::exit(1); |
| 172 | + } |
| 173 | + "?" => { |
| 174 | + print_help(); |
| 175 | + } |
| 176 | + _ => { |
| 177 | + println!("Error: Unknown command '{}'", command); |
| 178 | + print_help(); |
| 179 | + std::process::exit(1); |
| 180 | + } |
194 | 181 | }
|
195 | 182 | }
|
0 commit comments