use std::{fs, env, vec, error::Error, process::Command, path::Path, collections::HashMap, io::{self, Write}}; use serde::Deserialize; use rust_embed::RustEmbed; use tempfile::NamedTempFile; #[derive(Deserialize)] struct Flatpak { map: HashMap, } #[derive(RustEmbed)] #[folder = "data/"] struct Asset; fn main() -> io::Result<()> { loop { clear_terminal(); let binding = detect_distro(); let distro: &str = binding.as_str(); let file_content = match distro { "arch" => Asset::get("prompts/arch.txt").ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "File not found"))?, "Debian" => Asset::get("prompts/debian.txt").ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "File not found"))?, "fedora" => Asset::get("prompts/fedora.txt").ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "File not found"))?, _ => return Err(io::Error::new(io::ErrorKind::NotFound, "Unsupported distro")), }; let buffer = std::str::from_utf8(&file_content.data) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let first_choice: Vec = buffer.lines().map(|line| line.trim().to_string()).collect(); for (index, element) in first_choice.iter().enumerate() { eprint!("{}. {}\n", index + 1, element); } eprint!("\nEnter 0 to exit\n"); let input: i32 = handle_int_input(); if input == 0 { break; } match distro { "arch" => arch_choices(input)?, "Debian" => debian_choices(input)?, "fedora" => fedora_choices(input)?, _ => return Err(io::Error::new(io::ErrorKind::NotFound, "Unsupported distro")), }; } Ok(()) } fn arch_choices(input: i32) -> io::Result<()> { let _ = match input { 1 => install_programs(), 2 => install_flatpaks(), 3 => update_system(), 4 => aur(), 5 => drivers(), _ => return Err(io::Error::new(io::ErrorKind::NotFound, "Invalid input")), }; Ok(()) } fn debian_choices(input: i32) -> io::Result<()> { let _ = match input { 1 => install_programs(), 2 => install_flatpaks(), 3 => update_system(), 4 => third_party(), 5 => drivers(), _ => return Err(io::Error::new(io::ErrorKind::NotFound, "Invalid input")), }; Ok(()) } fn fedora_choices(input: i32) -> io::Result<()> { let _ = match input { 1 => install_programs(), 2 => install_flatpaks(), 3 => update_system(), 4 => third_party(), 5 => Ok(rpm()), 6 => drivers(), _ => return Err(io::Error::new(io::ErrorKind::NotFound, "Invalid input")), }; Ok(()) } fn install_programs() -> Result<(), Box> { clear_terminal(); let file = Asset::get("prompts/programs.txt").ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "File not found"))?; let buffer = std::str::from_utf8(&file.data) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let choice: Vec = buffer.lines().map(|line| line.trim().to_string()).collect(); for(index, element) in choice.iter().enumerate() { eprint!("{}. {}\n", index+1, element) } eprint!("\nEnter 0 to go back\n"); let input = handle_vec_input(); let mut chosen: Vec = vec![]; for &n in input.iter() { if input.contains(&0) { return Ok(()); } else { chosen.push(choice[(n-1) as usize].clone()); } } if which("apt").is_some() { Command::new("sudo").arg("apt").arg("update"); } for n in chosen.iter() { if which("yay").is_some() { Command::new("yay").arg("-S").arg(n).status()?; } else if which("pacman").is_some() { Command::new("sudo").arg("pacman").arg("-S").arg(n).status()?; } else if which("dnf").is_some() { Command::new("sudo").arg("dnf").arg("install").arg(n).status()?; } else if which("apt").is_some() { Command::new("sudo").arg("apt").arg("install").arg(n).status()?; } }; Ok(()) } fn flatpak_file() -> Result, Box> { let file_content = Asset::get("prompts/flatpaks.json") .ok_or("Embedded file not found")?; let contents = std::str::from_utf8(file_content.data.as_ref())?; let data: Flatpak = serde_json::from_str(contents)?; Ok(data.map) } fn install_flatpaks() -> Result<(), Box> { clear_terminal(); let binding = detect_distro(); let distro = binding.as_str(); if which("flatpak").is_none() { eprint!("\nInstall Flatpak first? Y/N (1/0)\n"); let input = handle_int_input(); if input == 0 { return Ok(()); } else { match distro { "arch" => { Command::new("sudo").arg("pacman").arg("-S").arg("flatpak").status()?; } "Debian" => { Command::new("sudo").arg("apt").arg("update").status()?; Command::new("sudo").arg("apt").arg("install").arg("flatpak").status()?; } "fedora" => { Command::new("sudo").arg("dnf").arg("install").arg("flatpak").status()?; } _ => eprint!("Unsupported distro") }; } } match flatpak_file() { Ok(hashmap) => { for (i, (_key, value)) in hashmap.iter().enumerate() { eprint!("{}. {}\n",i+1, value); } eprint!("\nEnter 0 to go back\n"); let input = handle_vec_input(); for &n in input.iter() { if input.contains(&0) { return Ok(()); } else if let Some((key, _)) = hashmap.iter().nth((n-1) as usize) { let status = Command::new("flatpak").arg("install").arg("-y").arg(key).status(); match status { Ok(s) if s.success() => eprint!("Successfully installed {}", key), Ok(_) => eprintln!("Failed to install Flatpak: {}", key), Err(e) => eprintln!("Error executing command: {}", e), } } else { eprint!("Invalid selection"); } } } Err(e) => { eprint!("Error reading file: {}", e); } } Ok(()) } fn update_system() -> Result<(), Box> { clear_terminal(); let binding = detect_distro(); let distro = binding.as_str(); match distro { "arch" => { if which("yay").is_some() { Command::new("yay").status()?; } else { Command::new("sudo").arg("pacman").arg("-Syu").status()?; } } "Debian" => { Command::new("sudo").arg("apt").arg("update").status()?; Command::new("sudo").arg("apt").arg("upgrade").status()?; } "fedora" => { Command::new("sudo").arg("dnf").arg("up").status()?; } _ => eprint!("Unsupported distro") }; if which("flatpak").is_some() { Command::new("flatpak").arg("update").status()?; } Ok(()) } fn aur() -> Result<(), Box> { clear_terminal(); let file = Asset::get("prompts/aur.txt").ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "File not found"))?; let buffer = std::str::from_utf8(&file.data) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let choice: Vec = buffer.lines().map(|line| line.trim().to_string()).collect(); if which("yay").is_none() { eprint!("yay not found. Install yay Y/N? (1/0)"); let input = handle_int_input(); if input == 1 { Command::new("sudo").arg("pacman").arg("-S").arg("git").arg("base-devel").status()?; Command::new("git").arg("clone").arg("https://aur.archlinux.org/yay.git").status()?; std::env::set_current_dir("yay")?; Command::new("makepkg").arg("-si").status()?; } else { return Ok(()); } } for (index, element) in choice.iter().enumerate() { eprint!("{}. {}\n", index+1, element) } eprint!("\nEnter 0 to go back\n"); let input = handle_vec_input(); let mut chosen: Vec = vec![]; for &n in input.iter() { if input.contains(&0) { return Ok(()); } else { chosen.push(choice[(n-1) as usize].clone()); } } for i in chosen.iter() { Command::new("yay").arg("-S").arg(i).status()?; } Ok(()) } fn third_party() -> Result<(), Box> { clear_terminal(); let file = Asset::get("prompts/third-party.txt") .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "File not found"))?; let buffer = std::str::from_utf8(&file.data) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let choices: Vec = buffer.lines().map(|line| line.trim().to_string()).collect(); for (index, element) in choices.iter().enumerate() { eprintln!("{}. {}", index + 1, element); } eprintln!("\nEnter 0 to go back"); let mut chosen: Vec = vec![]; let input = handle_vec_input(); for &n in input.iter() { if input.contains(&0) { let _ = main(); } else { chosen.push(choices[(n-1) as usize].clone()); } } for j in chosen.iter() { let script_name = format!("scripts/{}.sh", j); if let Some(script) = Asset::get(&script_name) { let mut temp_file = NamedTempFile::new()?; temp_file.write_all(&script.data)?; let temp_file_path = temp_file.path(); let status = Command::new("bash") .arg(temp_file_path) .status()?; if !status.success() { eprintln!("Script failed: {}", script_name); } } else { eprintln!("Script not found: {}", script_name); } } Ok(()) } fn drivers() -> Result<(), Box> { clear_terminal(); let distro = detect_distro(); let choices = vec!["Intel", "AMD", "Nvidia"]; for (index, element) in choices.iter().enumerate() { eprintln!("{}. {}", index + 1, element); } eprintln!("\nEnter 0 to go back"); let input: i32 = handle_int_input(); if input == 0 { return Ok(()); } match input { 1 => handle_intel_driver(&distro)?, 2 => handle_amd_driver(&distro)?, 3 => handle_nvidia_driver(&distro)?, _ => eprintln!("Invalid choice"), } Ok(()) } fn handle_intel_driver(distro: &str) -> Result<(), Box> { clear_terminal(); match distro { "arch" => { let intel_models = vec!["intel-media-driver", "libva-intel-driver"]; for (index, element) in intel_models.iter().enumerate() { eprintln!("{}. {}", index + 1, element); } eprintln!("\nEnter 0 to go back"); let input_2 = handle_int_input(); if input_2 == 0 { return drivers(); } match input_2 { 1 => Command::new("sudo").arg("pacman").arg("-S").arg("intel-media-driver").status()?, 2 => Command::new("sudo").arg("pacman").arg("-S").arg("libva-intel-driver").status()?, _ => { eprintln!("Invalid choice"); return Ok(()); }, } }, "Debian" => { Command::new("sudo").arg("apt").arg("update").status()?; Command::new("sudo").arg("apt").arg("install").arg("intel-media-va-driver").status() }?, "fedora" => { Command::new("sudo").arg("dnf").arg("install").arg("intel-media-driver").status()? }, _ => { eprintln!("Invalid choice"); return Ok(()); }, }; let _ = drivers(); Ok(()) } fn handle_amd_driver(distro: &str) -> Result<(), Box> { clear_terminal(); match distro { "arch" => Command::new("sudo").arg("pacman").arg("-S").arg("mesa").status()?, "Debian" => { Command::new("sudo").arg("apt").arg("update").status()?; Command::new("sudo").arg("apt").arg("install").arg("mesa-va-drivers").status() }?, "fedora" => Command::new("sudo").arg("dnf").arg("install").arg("mesa-va-drivers").status()?, _ => { eprintln!("Unsupported distro"); return Ok(()) }, }; let _ = drivers(); Ok(()) } fn handle_nvidia_driver(distro: &str) -> Result<(), Box> { clear_terminal(); match distro { "arch" => Command::new("sudo").arg("pacman").arg("-S").arg("nvidia").status()?, "Debian" => { Command::new("sudo").arg("apt").arg("update").status()?; Command::new("sudo").arg("apt").arg("install").arg("nvidia-driver").status() }?, "fedora" => Command::new("sudo").arg("dnf").arg("install").arg("akmod-nvidia").status()?, _ => { eprintln!("Unsupported distro"); return Ok(()) }, }; let _ = drivers(); Ok(()) } fn rpm() { clear_terminal(); let version = Command::new("rpm") .arg("-E") .arg("%fedora") .output() .expect("Failed to lookup fedora version"); if version.status.success() { let stdout = String::from_utf8_lossy(&version.stdout); let version_str = stdout.trim(); println!("Fedora version: {}", version_str); let url1 = format!("https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-{}.noarch.rpm", version_str); let url2 = format!("https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-{}.noarch.rpm", version_str); let status1 = Command::new("sudo").arg("dnf").arg("install").arg("-y").arg(&url1).status(); let status2 = Command::new("sudo").arg("dnf").arg("install").arg("-y").arg(&url2).status(); if status1.is_ok() && status1.unwrap().success() { println!("Successfully installed RPM Fusion free repository."); } else { eprintln!("Failed to install RPM Fusion free repository."); } if status2.is_ok() && status2.unwrap().success() { println!("Successfully installed RPM Fusion nonfree repository."); } else { eprintln!("Failed to install RPM Fusion nonfree repository."); } } else { let stderr = String::from_utf8_lossy(&version.stderr); eprintln!("Failed to get Fedora version with status: {}", version.status); eprintln!("Error output:\n{}", stderr); } } fn handle_vec_input() -> Vec { let mut input = String::new(); io::stdin() .read_line(&mut input) .expect("Error in reading."); input .trim() .split_whitespace() .filter_map(|x| x.parse().ok()) .collect() } fn handle_int_input() -> i32 { loop { let mut input = String::new(); io::stdin() .read_line(&mut input) .expect("Failed to read input"); match input.trim().parse::() { Ok(num) => return num, Err(_) => println!("Please enter a valid integer."), } } } fn detect_distro() -> String { let detected_distro = fs::read_to_string("/etc/os-release") .expect("Could not find os-release file on system"); if detected_distro.contains("arch") { "arch".to_string() } else if detected_distro.contains("Debian") || detected_distro.contains("ubuntu") { "Debian".to_string() } else if detected_distro.contains("fedora") { "fedora".to_string() } else { eprintln!("Distro Not Supported"); "unsupported".to_string() } } fn which(program: &str) -> Option { if let Ok(paths) = env::var("PATH") { for path in paths.split(':') { let full_path = Path::new(path).join(program); if full_path.exists() && full_path.is_file() { return Some(full_path.to_string_lossy().to_string()); } } } None } fn clear_terminal() { Command::new("clear") .status() .expect("Failed to clear the terminal"); }