Files
feddy/src/main.rs

304 lines
9.1 KiB
Rust

use std::thread::available_parallelism;
use clap::{CommandFactory, Parser};
use feddy::*;
use ini::Ini;
fn main() {
configure_logger();
let cli = Cli::parse();
let mut cmd = Cli::command();
if let Some(generator) = cli.generator {
log::info!("Generating completion file for {generator:?}...");
print_completions(generator, &mut cmd);
return;
}
if let Some(command) = cli.command {
match command {
Commands::ConfigureDnf => {
if let Err(e) = configure_dnf() {
log::error!("Error configuring dnf: {}", e);
}
}
Commands::AddRepo(repo_command) => {
if let Err(e) = add_repo(repo_command) {
log::error!("Error adding repository: {}", e);
}
}
Commands::InstallRpmFusion => {
if let Err(e) = install_rpm_fusion() {
log::error!("Error installing RPM Fusion: {}", e);
}
}
Commands::ConfigureGroups(configure_groups_command) => {
if let Err(e) = configure_groups(configure_groups_command) {
log::error!("Error configuring groups: {}", e);
}
}
Commands::Package(package_command) => {
if let Err(e) = manage_package(package_command) {
log::error!("Error executing package command: {}", e);
}
}
}
} else {
cmd.print_help().unwrap();
}
success!("Bye :)");
}
fn configure_groups(configure_groups_command: ConfigureGroupsCommand) -> Result<()> {
let groups = include_str!("../data/user_groups.txt").to_string();
if configure_groups_command.print {
log::info!("Printing groups...");
println!("{}", groups);
return Ok(());
}
let groups = groups
.lines()
.map(|line| line.trim())
.filter(|line| !line.is_empty())
.filter(|group| {
let group_exists = std::process::Command::new("getent")
.arg("group")
.arg(group)
.output()
.map(|output| output.status.success())
.unwrap_or(false);
if !group_exists {
log::warn!("Group {} does not exist.", group);
}
group_exists
})
.collect::<Vec<_>>();
if groups.is_empty() {
log::warn!("No valid groups found to add user to.");
return Ok(());
}
let groups = groups.join(",");
let user = configure_groups_command.user.unwrap_or_else(|| {
log::info!("No user specified, using current user");
whoami::username()
});
log::info!("Adding user {} to groups: {}", user, groups);
let mut cmd = std::process::Command::new("usermod");
cmd.arg("-aG");
cmd.arg(groups);
cmd.arg(&user);
log::debug!("Executing command: {:?}", cmd);
cmd.status()?;
Ok(())
}
fn add_repo(repo_command: AddRepoCommand) -> Result<()> {
match repo_command.command {
AddRepoSubCommand::Vscode => {
add_repo_common(
"vscode",
"Visual Studio Code",
"https://packages.microsoft.com/keys/microsoft.asc",
"https://packages.microsoft.com/yumrepos/vscode",
"/etc/yum.repos.d/vscode.repo",
)?;
}
AddRepoSubCommand::Mullvad => {
add_repo_common(
"mullvad-stable",
"Mullvad VPN",
"https://repository.mullvad.net/rpm/mullvad-keyring.asc",
"https://repository.mullvad.net/rpm/stable/$basearch",
"/etc/yum.repos.d/mullvad.repo",
)?;
}
AddRepoSubCommand::Vivaldi => {
add_repo_common(
"vivaldi",
"Vivaldi",
"https://repo.vivaldi.com/archive/linux_signing_key.pub",
"https://repo.vivaldi.com/archive/rpm/x86_64",
"/etc/yum.repos.d/vivaldi.repo",
)?;
}
}
Ok(())
}
fn add_repo_common(
name: &str,
display_name: &str,
gpg_key_url: &str,
baseurl: &str,
repo_file_path: &str,
) -> Result<()> {
log::info!("Importing {} GPG key...", display_name);
std::process::Command::new("rpm")
.arg("--import")
.arg(gpg_key_url)
.status()?;
let mut conf = Ini::new();
conf.with_section(Some(name))
.set("name", display_name)
.set("enabled", "1")
.set("gpgcheck", "1")
.set("autorefresh", "1")
.set("baseurl", baseurl)
.set("gpgkey", gpg_key_url);
log::info!("Adding {} repository...", display_name);
conf.write_to_file(repo_file_path)?;
Ok(())
}
fn install_rpm_fusion() -> Result<()> {
let fedora_version_vec = std::process::Command::new("rpm")
.arg("-E")
.arg("%fedora")
.output()?
.stdout;
let fedora_version = String::from_utf8_lossy(&fedora_version_vec);
let fedora_version = fedora_version.trim();
log::info!("Installing RPM Fusion for Fedora {}", fedora_version);
log::info!("Enabling the openh264 library...");
std::process::Command::new("dnf")
.arg("config-manager")
.arg("setopt")
.arg("fedora-cisco-openh264.enabled=1")
.status()?;
log::info!("Installing RPM Fusion free and non-free repositories...");
std::process::Command::new("dnf")
.arg("install")
.arg(format!(
"https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-{}.noarch.rpm",
fedora_version
))
.arg(format!(
"https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-{}.noarch.rpm",
fedora_version
))
.status()?;
log::info!("Installing RPM Fusion additional packages...");
std::process::Command::new("dnf")
.arg("install")
.arg("rpmfusion-free-appstream-data")
.arg("rpmfusion-nonfree-appstream-data")
.arg("rpmfusion-free-release-tainted")
.arg("rpmfusion-nonfree-release-tainted")
.status()?;
Ok(())
}
fn manage_package(package_command: PackageCommand) -> Result<()> {
let list = match &package_command.command {
PackageSubCommand::CommonList => include_str!("../data/common_list.txt").to_string(),
PackageSubCommand::AmdList => include_str!("../data/amd_list.txt").to_string(),
PackageSubCommand::IntelList => include_str!("../data/intel_list.txt").to_string(),
PackageSubCommand::GnomeExtraList => {
include_str!("../data/gnome_extra_list.txt").to_string()
}
PackageSubCommand::FirmwareList => include_str!("../data/firmware_list.txt").to_string(),
PackageSubCommand::CustomList(custom_list_command) => {
log::info!("Using custom list from {}", custom_list_command.file);
read_file!(&custom_list_command.file)?
}
};
if package_command.print {
log::info!("Printing package list...");
println!("{}", list);
return Ok(());
}
manage_list(list, package_command.remove)?;
Ok(())
}
fn manage_list(list: String, remove: bool) -> Result<()> {
process_packages(&list, remove, '+')?;
process_packages(&list, remove, '-')?;
Ok(())
}
fn process_packages(list: &String, remove: bool, prefix: char) -> Result<()> {
let packages = list
.lines()
.map(|line| line.trim())
.filter(|line| !line.is_empty() && line.starts_with(prefix))
.map(|line| line.trim_start_matches(prefix).trim())
.collect::<Vec<_>>();
if packages.is_empty() {
return Ok(());
}
let mut dnf_cmd = std::process::Command::new("dnf");
if remove {
if prefix == '+' {
log::info!("Removing wanted list's packages...");
dnf_cmd.arg("remove");
} else {
log::info!("Re-installing unwanted list's packages...");
dnf_cmd.arg("install").arg("--allowerasing");
}
} else {
if prefix == '+' {
log::info!("Installing wanted list's packages...");
dnf_cmd.arg("install").arg("--allowerasing");
} else {
log::info!("Removing unwanted list's packages...");
dnf_cmd.arg("remove");
}
}
packages.iter().for_each(|package| {
dnf_cmd.arg(package);
});
dnf_cmd.status()?;
Ok(())
}
fn configure_dnf() -> Result<()> {
log::info!("Tweaking dnf configuration...");
let mut conf = Ini::load_from_file("/etc/dnf/dnf.conf")?;
let max_parallel_downloads =
std::cmp::min(20, std::cmp::max(available_parallelism()?.get() / 2, 3));
log::info!(
"Setting max_parallel_downloads to {}",
max_parallel_downloads
);
log::info!("Setting defaultyes to True");
conf.with_section(Some("main"))
.set("defaultyes", "True")
.set("max_parallel_downloads", max_parallel_downloads.to_string());
// Write the changes back to the file
log::info!("Writing changes to /etc/dnf/dnf.conf");
conf.write_to_file("/etc/dnf/dnf.conf")?;
Ok(())
}