apt-ostree/src/commands/packages.rs
robojerk 3dec23f8f7 Fix YAML linting issues and update system requirements to Debian 13+
- Fix trailing spaces and blank lines in Forgejo workflows
- Update system requirements from Ubuntu Jammy/Bookworm to Debian 13+ (Trixie)
- Update test treefile to use Debian Trixie instead of Ubuntu Jammy
- Update documentation to reflect modern system requirements
- Fix yamllint errors for CI/CD functionality
- Ensure compatibility with modern OSTree and libapt versions
2025-08-18 11:39:58 -07:00

419 lines
14 KiB
Rust

//! Package management commands for apt-ostree
use crate::commands::Command;
use apt_ostree::lib::error::{AptOstreeError, AptOstreeResult};
use apt_ostree::lib::ostree::OstreeManager;
/// Install command - Overlay additional packages
pub struct InstallCommand;
impl InstallCommand {
pub fn new() -> Self {
Self
}
}
impl Command for InstallCommand {
fn execute(&self, args: &[String]) -> AptOstreeResult<()> {
if args.contains(&"--help".to_string()) || args.contains(&"-h".to_string()) {
self.show_help();
return Ok(());
}
if args.is_empty() {
return Err(AptOstreeError::InvalidArgument(
"No packages specified. Use --help for usage information.".to_string()
));
}
// Parse options
let mut opt_dry_run = false;
let mut opt_verbose = false;
let mut opt_no_deps = false;
let packages: Vec<String> = args.iter()
.filter(|arg| !arg.starts_with('-'))
.cloned()
.collect();
for arg in args {
match arg.as_str() {
"--dry-run" | "-n" => opt_dry_run = true,
"--verbose" | "-v" => opt_verbose = true,
"--no-deps" => opt_no_deps = true,
"--help" | "-h" => {
self.show_help();
return Ok(());
}
_ => {}
}
}
if packages.is_empty() {
return Err(AptOstreeError::InvalidArgument(
"No packages specified. Use --help for usage information.".to_string()
));
}
println!("📦 Install Packages");
println!("===================");
println!("Packages to install: {}", packages.join(", "));
if opt_dry_run {
println!("Mode: Dry run (no actual installation)");
}
if opt_verbose {
println!("Mode: Verbose output");
}
if opt_no_deps {
println!("Mode: No dependency installation");
}
println!();
// Use the real APT manager for installation
use apt_ostree::lib::apt::AptManager;
let apt_manager = AptManager::new();
// Check if APT is available
if !apt_manager.check_database_health()? {
return Err(AptOstreeError::System("APT database is not healthy".to_string()));
}
if opt_dry_run {
println!("Dry run mode - would install the following packages:");
for package in &packages {
if let Ok(Some(pkg_info)) = apt_manager.get_package_info(package) {
println!(" {} (version: {})", pkg_info.name, pkg_info.version);
println!(" Description: {}", pkg_info.description);
if !pkg_info.depends.is_empty() {
println!(" Dependencies: {}", pkg_info.depends.join(", "));
}
println!();
} else {
println!(" {} - Package not found", package);
}
}
println!("Dry run completed. No packages were actually installed.");
return Ok(());
}
// Install packages
for package in &packages {
println!("Installing package: {}", package);
// Since install_package is async, we'll use a simple approach for now
// TODO: Make the Command trait async or use a different approach
match apt_manager.install_package(package) {
Ok(_) => println!("Successfully installed: {}", package),
Err(e) => {
println!("Failed to install {}: {}", package, e);
return Err(e);
}
}
}
println!();
println!("✅ All packages installed successfully!");
println!("Note: On OSTree systems, packages are installed as overlays");
println!(" and will persist across system updates.");
Ok(())
}
fn name(&self) -> &'static str {
"install"
}
fn description(&self) -> &'static str {
"Overlay additional packages"
}
fn show_help(&self) {
println!("apt-ostree install - Overlay additional packages");
println!();
println!("Usage: apt-ostree install <PACKAGES>... [OPTIONS]");
println!();
println!("Arguments:");
println!(" PACKAGES Package names to install");
println!();
println!("Options:");
println!(" --dry-run, -n Show what would be installed without actually installing");
println!(" --verbose, -v Show detailed output during installation");
println!(" --no-deps Skip dependency installation (not recommended)");
println!(" --help, -h Show this help message");
println!();
println!("Examples:");
println!(" apt-ostree install nginx");
println!(" apt-ostree install nginx vim htop");
println!(" apt-ostree install --dry-run nginx");
println!(" apt-ostree install --verbose nginx");
println!();
println!("Note: On OSTree systems, packages are installed as overlays");
println!(" and will persist across system updates.");
}
}
/// Uninstall command - Remove overlayed additional packages
pub struct UninstallCommand;
impl UninstallCommand {
pub fn new() -> Self {
Self
}
}
impl Command for UninstallCommand {
fn execute(&self, args: &[String]) -> AptOstreeResult<()> {
if args.contains(&"--help".to_string()) || args.contains(&"-h".to_string()) {
self.show_help();
return Ok(());
}
if args.is_empty() {
return Err(AptOstreeError::InvalidArgument(
"No packages specified. Use --help for usage information.".to_string()
));
}
let packages: Vec<String> = args.iter()
.filter(|arg| !arg.starts_with('-'))
.cloned()
.collect();
if packages.is_empty() {
return Err(AptOstreeError::InvalidArgument(
"No packages specified. Use --help for usage information.".to_string()
));
}
println!("🗑️ Uninstall Packages");
println!("=====================");
println!("Packages to remove: {}", packages.join(", "));
println!();
// Check if we're on an OSTree system
let ostree_manager = OstreeManager::new();
let is_ostree_system = ostree_manager.is_available() && ostree_manager.is_ostree_booted();
if is_ostree_system {
println!("OSTree: System is booted from OSTree");
println!("Mode: Package overlay removal");
} else {
println!("OSTree: Traditional package management system");
println!("Mode: Standard package removal");
}
println!();
// Use the real APT manager for package removal
use apt_ostree::lib::apt::AptManager;
let apt_manager = AptManager::new();
// Check if APT is available
if !apt_manager.check_database_health()? {
return Err(AptOstreeError::System("APT database is not healthy".to_string()));
}
// Process each package
let mut success_count = 0;
let mut failure_count = 0;
for package in &packages {
println!("Removing package: {}", package);
// Check if package is installed
if !apt_manager.is_package_installed(package)? {
println!(" Warning: Package '{}' is not installed", package);
continue;
}
// Get package info before removal
if let Ok(Some(pkg_info)) = apt_manager.get_package_info(package) {
println!(" Package: {} (version: {})", pkg_info.name, pkg_info.version);
println!(" Description: {}", pkg_info.description);
// Check for reverse dependencies
// TODO: Implement reverse dependency checking
println!(" Checking dependencies...");
}
// Remove the package
match apt_manager.remove_package(package) {
Ok(_) => {
println!(" ✅ Successfully removed: {}", package);
success_count += 1;
}
Err(e) => {
println!(" ❌ Failed to remove {}: {}", package, e);
failure_count += 1;
}
}
println!();
}
// Summary
println!("Uninstall Summary:");
println!(" Successfully removed: {} packages", success_count);
if failure_count > 0 {
println!(" Failed to remove: {} packages", failure_count);
}
if is_ostree_system {
println!();
println!("Note: On OSTree systems, package overlays have been removed.");
println!(" The base system remains unchanged.");
}
if failure_count == 0 {
println!("✅ All packages removed successfully!");
} else {
println!("⚠️ Some packages could not be removed. Check the output above.");
}
Ok(())
}
fn name(&self) -> &'static str {
"uninstall"
}
fn description(&self) -> &'static str {
"Remove overlayed additional packages"
}
fn show_help(&self) {
println!("apt-ostree uninstall - Remove overlayed additional packages");
println!();
println!("Usage: apt-ostree uninstall <PACKAGES>... [OPTIONS]");
println!();
println!("Arguments:");
println!(" PACKAGES Package names to remove");
println!();
println!("Options:");
println!(" --help, -h Show this help message");
}
}
/// Search command - Search for packages
pub struct SearchCommand;
impl SearchCommand {
pub fn new() -> Self {
Self
}
}
impl Command for SearchCommand {
fn execute(&self, args: &[String]) -> AptOstreeResult<()> {
if args.contains(&"--help".to_string()) || args.contains(&"-h".to_string()) {
self.show_help();
return Ok(());
}
if args.is_empty() {
return Err(AptOstreeError::InvalidArgument(
"No search query specified. Use --help for usage information.".to_string()
));
}
// Parse options
let mut opt_exact = false;
let mut opt_regex = false;
let mut opt_verbose = false;
let mut search_query = String::new();
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--exact" | "-e" => opt_exact = true,
"--regex" | "-r" => opt_regex = true,
"--verbose" | "-v" => opt_verbose = true,
"--help" | "-h" => {
self.show_help();
return Ok(());
}
arg if !arg.starts_with('-') => {
search_query = arg.to_string();
}
_ => {}
}
i += 1;
}
if search_query.is_empty() {
return Err(AptOstreeError::InvalidArgument(
"No search query specified. Use --help for usage information.".to_string()
));
}
println!("🔍 Package Search");
println!("=================");
println!("Query: {}", search_query);
println!("Mode: {}", if opt_exact { "Exact Match" } else if opt_regex { "Regex" } else { "Standard Search" });
println!();
// Use the real APT manager for search
use apt_ostree::lib::apt::AptManager;
let apt_manager = AptManager::new();
let packages = if opt_exact {
apt_manager.search_packages_exact(&search_query)?
} else if opt_regex {
apt_manager.search_packages_regex(&search_query)?
} else {
apt_manager.search_packages(&search_query)?
};
if packages.is_empty() {
println!("No packages found matching '{}'", search_query);
return Ok(());
}
println!("Found {} packages:", packages.len());
println!();
for package in packages {
let status = if package.installed { "" } else { " " };
println!("{} {} - {}", status, package.name, package.description);
if opt_verbose {
println!(" Version: {}", package.version);
println!(" Section: {}", package.section);
println!(" Priority: {}", package.priority);
if !package.depends.is_empty() {
println!(" Dependencies: {}", package.depends.join(", "));
}
println!();
}
}
Ok(())
}
fn name(&self) -> &'static str {
"search"
}
fn description(&self) -> &'static str {
"Search for packages"
}
fn show_help(&self) {
println!("apt-ostree search - Search for packages");
println!();
println!("Usage: apt-ostree search <QUERY> [OPTIONS]");
println!();
println!("Arguments:");
println!(" QUERY Search query (package name or description)");
println!();
println!("Options:");
println!(" --exact, -e Exact package name match");
println!(" --regex, -r Regular expression search");
println!(" --verbose, -v Show detailed package information");
println!(" --help, -h Show this help message");
println!();
println!("Examples:");
println!(" apt-ostree search nginx");
println!(" apt-ostree search --exact nginx");
println!(" apt-ostree search --regex '^nginx.*'");
println!(" apt-ostree search --verbose nginx");
}
}