- Add --version and --help flags for rpm-ostree compatibility - Fix postinst script to handle 'triggered' argument properly - Maintain subcommand interface while adding flag support - Improve error messages and help text
495 lines
17 KiB
Rust
495 lines
17 KiB
Rust
use std::env;
|
||
use tracing::{info, error};
|
||
|
||
mod apt_compat;
|
||
mod error;
|
||
mod ostree_integration;
|
||
|
||
use apt_compat::AptManager;
|
||
use ostree_integration::OstreeManager;
|
||
use error::{AptOstreeError, AptOstreeResult};
|
||
|
||
#[tokio::main]
|
||
async fn main() -> AptOstreeResult<()> {
|
||
// Initialize logging
|
||
tracing_subscriber::fmt::init();
|
||
|
||
info!("apt-ostree starting...");
|
||
|
||
let args: Vec<String> = env::args().collect();
|
||
if args.len() < 2 {
|
||
show_help(&args[0]);
|
||
return Ok(());
|
||
}
|
||
|
||
let command = &args[1];
|
||
|
||
// Handle rpm-ostree compatible flags first
|
||
match command.as_str() {
|
||
"--version" | "-V" => {
|
||
show_version();
|
||
return Ok(());
|
||
}
|
||
"--help" | "-h" => {
|
||
show_help(&args[0]);
|
||
return Ok(());
|
||
}
|
||
_ => {}
|
||
}
|
||
|
||
// Handle subcommands (rpm-ostree style)
|
||
match command.as_str() {
|
||
"search" => {
|
||
if args.len() < 3 {
|
||
error!("Search command requires a query");
|
||
return Err(AptOstreeError::InvalidArgument("Search query required".to_string()));
|
||
}
|
||
let query = &args[2];
|
||
search_packages(query).await?;
|
||
}
|
||
"list" => {
|
||
list_packages().await?;
|
||
}
|
||
"installed" => {
|
||
list_installed_packages().await?;
|
||
}
|
||
"info" => {
|
||
if args.len() < 3 {
|
||
error!("Info command requires a package name");
|
||
return Err(AptOstreeError::InvalidArgument("Package name required".to_string()));
|
||
}
|
||
let package_name = &args[2];
|
||
show_package_info(package_name).await?;
|
||
}
|
||
"install" => {
|
||
if args.len() < 3 {
|
||
error!("Install command requires a package name");
|
||
return Err(AptOstreeError::InvalidArgument("Package name required".to_string()));
|
||
}
|
||
let package_name = &args[2];
|
||
install_package(package_name).await?;
|
||
}
|
||
"remove" => {
|
||
if args.len() < 3 {
|
||
error!("Remove command requires a package name");
|
||
return Err(AptOstreeError::InvalidArgument("Package name required".to_string()));
|
||
}
|
||
let package_name = &args[2];
|
||
remove_package(package_name).await?;
|
||
}
|
||
"upgrade" => {
|
||
upgrade_system().await?;
|
||
}
|
||
"status" => {
|
||
show_system_status().await?;
|
||
}
|
||
"rollback" => {
|
||
rollback_system().await?;
|
||
}
|
||
"help" => {
|
||
show_help(&args[0]);
|
||
}
|
||
"version" => {
|
||
show_version();
|
||
}
|
||
_ => {
|
||
error!("Unknown command: {}", command);
|
||
println!("Try '{} --help' for more information.", args[0]);
|
||
return Err(AptOstreeError::InvalidArgument(format!("Unknown command: {}", command)));
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// Show version information (rpm-ostree compatible)
|
||
fn show_version() {
|
||
println!("apt-ostree {}", env!("CARGO_PKG_VERSION"));
|
||
println!("Debian/Ubuntu equivalent of rpm-ostree");
|
||
println!("Copyright (C) 2025 Robojerk");
|
||
println!("License GPL-3.0-or-later: <https://www.gnu.org/licenses/gpl-3.0.html>");
|
||
}
|
||
|
||
/// Show help information (rpm-ostree compatible)
|
||
fn show_help(program_name: &str) {
|
||
println!("Usage: {} <command> [options]", program_name);
|
||
println!("");
|
||
println!("Commands:");
|
||
println!(" search <query> - Search for packages");
|
||
println!(" list - List all packages");
|
||
println!(" installed - List installed packages");
|
||
println!(" info <package> - Show package information");
|
||
println!(" install <package> - Install package (atomic)");
|
||
println!(" remove <package> - Remove package (atomic)");
|
||
println!(" upgrade - Upgrade system (atomic)");
|
||
println!(" status - Show system status");
|
||
println!(" rollback - Rollback to previous deployment");
|
||
println!(" help - Show this help");
|
||
println!(" version - Show version information");
|
||
println!("");
|
||
println!("Options:");
|
||
println!(" --version, -V - Show version information");
|
||
println!(" --help, -h - Show this help");
|
||
println!("");
|
||
println!("Examples:");
|
||
println!(" {} --version", program_name);
|
||
println!(" {} --help", program_name);
|
||
println!(" {} status", program_name);
|
||
println!(" {} info apt", program_name);
|
||
println!(" {} install vim", program_name);
|
||
}
|
||
|
||
async fn search_packages(query: &str) -> AptOstreeResult<()> {
|
||
info!("Searching for packages matching: {}", query);
|
||
|
||
let mut apt_manager = AptManager::new()?;
|
||
let packages = apt_manager.search_packages(query).await?;
|
||
|
||
if packages.is_empty() {
|
||
println!("No packages found matching '{}'", query);
|
||
} else {
|
||
println!("Found {} packages matching '{}':", packages.len(), query);
|
||
for package in packages {
|
||
println!(" {}", package);
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn list_packages() -> AptOstreeResult<()> {
|
||
info!("Listing all packages");
|
||
|
||
let mut apt_manager = AptManager::new()?;
|
||
let packages = apt_manager.list_packages();
|
||
|
||
println!("Total packages: {}", packages.len());
|
||
for package in packages.iter().take(20) { // Show first 20
|
||
println!(" {} ({})", package.name(), package.arch());
|
||
}
|
||
if packages.len() > 20 {
|
||
println!(" ... and {} more", packages.len() - 20);
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn list_installed_packages() -> AptOstreeResult<()> {
|
||
info!("Listing installed packages");
|
||
|
||
let mut apt_manager = AptManager::new()?;
|
||
let packages = apt_manager.list_installed_packages();
|
||
|
||
println!("Installed packages: {}", packages.len());
|
||
for package in packages.iter().take(20) { // Show first 20
|
||
println!(" {} ({})", package.name(), package.arch());
|
||
}
|
||
if packages.len() > 20 {
|
||
println!(" ... and {} more", packages.len() - 20);
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn show_package_info(package_name: &str) -> AptOstreeResult<()> {
|
||
info!("Getting package info for: {}", package_name);
|
||
|
||
let mut apt_manager = AptManager::new()?;
|
||
let package_info = apt_manager.get_package_info(package_name).await?;
|
||
|
||
println!("Package: {}", package_info.name);
|
||
println!("Version: {}", package_info.version);
|
||
println!("Architecture: {}", package_info.architecture);
|
||
println!("Description: {}", package_info.description);
|
||
|
||
// Display enhanced package information
|
||
if package_info.section != "unknown" {
|
||
println!("Section: {}", package_info.section);
|
||
}
|
||
if package_info.priority != "unknown" {
|
||
println!("Priority: {}", package_info.priority);
|
||
}
|
||
if package_info.maintainer != "unknown" {
|
||
println!("Maintainer: {}", package_info.maintainer);
|
||
}
|
||
if package_info.homepage != "unknown" {
|
||
println!("Homepage: {}", package_info.homepage);
|
||
}
|
||
if package_info.size > 0 {
|
||
println!("Size: {} bytes", package_info.size);
|
||
}
|
||
if package_info.installed_size > 0 {
|
||
println!("Installed-Size: {} bytes", package_info.installed_size);
|
||
}
|
||
if package_info.source != "unknown" {
|
||
println!("Source: {}", package_info.source);
|
||
}
|
||
if package_info.multi_arch != "unknown" {
|
||
println!("Multi-Arch: {}", package_info.multi_arch);
|
||
}
|
||
|
||
if !package_info.depends.is_empty() {
|
||
println!("Depends: {}", package_info.depends.join(", "));
|
||
}
|
||
|
||
if !package_info.conflicts.is_empty() {
|
||
println!("Conflicts: {}", package_info.conflicts.join(", "));
|
||
}
|
||
|
||
if !package_info.provides.is_empty() {
|
||
println!("Provides: {}", package_info.provides.join(", "));
|
||
}
|
||
|
||
if !package_info.breaks.is_empty() {
|
||
println!("Breaks: {}", package_info.breaks.join(", "));
|
||
}
|
||
|
||
if !package_info.replaces.is_empty() {
|
||
println!("Replaces: {}", package_info.replaces.join(", "));
|
||
}
|
||
|
||
if !package_info.recommends.is_empty() {
|
||
println!("Recommends: {}", package_info.recommends.join(", "));
|
||
}
|
||
|
||
if !package_info.suggests.is_empty() {
|
||
println!("Suggests: {}", package_info.suggests.join(", "));
|
||
}
|
||
|
||
if !package_info.enhances.is_empty() {
|
||
println!("Enhances: {}", package_info.enhances.join(", "));
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn install_package(package_name: &str) -> AptOstreeResult<()> {
|
||
info!("Installing package: {}", package_name);
|
||
|
||
println!("=== apt-ostree install {} ===", package_name);
|
||
|
||
// Initialize OSTree manager
|
||
let ostree_manager = match OstreeManager::new() {
|
||
Ok(manager) => manager,
|
||
Err(e) => {
|
||
println!("⚠️ Warning: Not running in OSTree system: {}", e);
|
||
println!("This is a simulation of atomic package installation.");
|
||
println!("");
|
||
println!("In a real OSTree system, this would:");
|
||
println!("1. Create a staging deployment from current system");
|
||
println!("2. Install the package in the staging environment");
|
||
println!("3. Create a new OSTree commit");
|
||
println!("4. Deploy the new commit (requires reboot to activate)");
|
||
println!("");
|
||
println!("Package '{}' would be installed atomically.", package_name);
|
||
return Ok(());
|
||
}
|
||
};
|
||
|
||
// Perform actual atomic installation
|
||
println!("🚀 Creating staging deployment...");
|
||
let staging_ref = ostree_manager.create_staging_deployment()?;
|
||
println!("✅ Staging deployment created: {}", staging_ref);
|
||
|
||
println!("📦 Installing package in staging environment...");
|
||
ostree_manager.install_packages_in_staging(&[package_name.to_string()])?;
|
||
println!("✅ Package installed in staging environment");
|
||
|
||
println!("💾 Committing staging deployment...");
|
||
let commit_message = format!("Install package: {}", package_name);
|
||
let new_commit = ostree_manager.commit_staging_deployment(&commit_message)?;
|
||
println!("✅ New commit created: {}", new_commit);
|
||
|
||
println!("<EFBFBD><EFBFBD> Deploying new commit...");
|
||
ostree_manager.deploy_new_commit(&new_commit)?;
|
||
println!("✅ New deployment ready");
|
||
|
||
println!("");
|
||
println!("🎉 Package '{}' installed atomically!", package_name);
|
||
println!("🔄 Reboot required to activate changes");
|
||
println!("");
|
||
println!("To activate: sudo reboot");
|
||
println!("To rollback: apt-ostree rollback");
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn remove_package(package_name: &str) -> AptOstreeResult<()> {
|
||
info!("Removing package: {}", package_name);
|
||
|
||
println!("=== apt-ostree remove {} ===", package_name);
|
||
|
||
// Initialize OSTree manager
|
||
let ostree_manager = match OstreeManager::new() {
|
||
Ok(manager) => manager,
|
||
Err(e) => {
|
||
println!("⚠️ Warning: Not running in OSTree system: {}", e);
|
||
println!("This is a simulation of atomic package removal.");
|
||
println!("");
|
||
println!("In a real OSTree system, this would:");
|
||
println!("1. Create a staging deployment from current system");
|
||
println!("2. Remove the package from the staging environment");
|
||
println!("3. Create a new OSTree commit");
|
||
println!("4. Deploy the new commit (requires reboot to activate)");
|
||
println!("");
|
||
println!("Package '{}' would be removed atomically.", package_name);
|
||
return Ok(());
|
||
}
|
||
};
|
||
|
||
// Perform actual atomic removal
|
||
println!("🚀 Creating staging deployment...");
|
||
let staging_ref = ostree_manager.create_staging_deployment()?;
|
||
println!("✅ Staging deployment created: {}", staging_ref);
|
||
|
||
println!("🗑️ Removing package from staging environment...");
|
||
ostree_manager.remove_packages_from_staging(&[package_name.to_string()])?;
|
||
println!("✅ Package removed from staging environment");
|
||
|
||
println!("💾 Committing staging deployment...");
|
||
let commit_message = format!("Remove package: {}", package_name);
|
||
let new_commit = ostree_manager.commit_staging_deployment(&commit_message)?;
|
||
println!("✅ New commit created: {}", new_commit);
|
||
|
||
println!("🚀 Deploying new commit...");
|
||
ostree_manager.deploy_new_commit(&new_commit)?;
|
||
println!("✅ New deployment ready");
|
||
|
||
println!("");
|
||
println!("🎉 Package '{}' removed atomically!", package_name);
|
||
println!("🔄 Reboot required to activate changes");
|
||
println!("");
|
||
println!("To activate: sudo reboot");
|
||
println!("To rollback: apt-ostree rollback");
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn upgrade_system() -> AptOstreeResult<()> {
|
||
info!("Upgrading system");
|
||
|
||
println!("=== apt-ostree upgrade ===");
|
||
|
||
// Initialize OSTree manager
|
||
let ostree_manager = match OstreeManager::new() {
|
||
Ok(manager) => manager,
|
||
Err(e) => {
|
||
println!("⚠️ Warning: Not running in OSTree system: {}", e);
|
||
println!("This is a simulation of atomic system upgrade.");
|
||
println!("");
|
||
println!("In a real OSTree system, this would:");
|
||
println!("1. Create a staging deployment from current system");
|
||
println!("2. Run 'apt upgrade' in the staging environment");
|
||
println!("3. Create a new OSTree commit with all updates");
|
||
println!("4. Deploy the new commit (requires reboot to activate)");
|
||
println!("");
|
||
println!("System would be upgraded atomically.");
|
||
return Ok(());
|
||
}
|
||
};
|
||
|
||
// Perform actual atomic upgrade
|
||
println!("🚀 Creating staging deployment...");
|
||
let staging_ref = ostree_manager.create_staging_deployment()?;
|
||
println!("✅ Staging deployment created: {}", staging_ref);
|
||
|
||
println!("⬆️ Upgrading packages in staging environment...");
|
||
ostree_manager.upgrade_packages_in_staging()?;
|
||
println!("✅ Packages upgraded in staging environment");
|
||
|
||
println!("💾 Committing staging deployment...");
|
||
let commit_message = "System upgrade: apt upgrade".to_string();
|
||
let new_commit = ostree_manager.commit_staging_deployment(&commit_message)?;
|
||
println!("✅ New commit created: {}", new_commit);
|
||
|
||
println!("🚀 Deploying new commit...");
|
||
ostree_manager.deploy_new_commit(&new_commit)?;
|
||
println!("✅ New deployment ready");
|
||
|
||
println!("");
|
||
println!("🎉 System upgraded atomically!");
|
||
println!("🔄 Reboot required to activate changes");
|
||
println!("");
|
||
println!("To activate: sudo reboot");
|
||
println!("To rollback: apt-ostree rollback");
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn show_system_status() -> AptOstreeResult<()> {
|
||
info!("Showing system status");
|
||
|
||
println!("=== apt-ostree status ===");
|
||
|
||
// Try to get OSTree status
|
||
match OstreeManager::new() {
|
||
Ok(ostree_manager) => {
|
||
println!("🌳 OSTree System Status:");
|
||
println!("");
|
||
|
||
let status = ostree_manager.get_system_status()?;
|
||
println!("{}", status);
|
||
|
||
// Check rollback availability
|
||
match ostree_manager.check_rollback_available() {
|
||
Ok(true) => println!("🔄 Rollback available: apt-ostree rollback"),
|
||
Ok(false) => println!("🔄 No rollback available"),
|
||
Err(e) => println!("⚠️ Could not check rollback status: {}", e),
|
||
}
|
||
}
|
||
Err(e) => {
|
||
println!("⚠️ Not running in OSTree system: {}", e);
|
||
println!("");
|
||
println!("This system does not support atomic package operations.");
|
||
println!("Use standard apt commands instead.");
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn rollback_system() -> AptOstreeResult<()> {
|
||
info!("Rolling back system");
|
||
|
||
println!("=== apt-ostree rollback ===");
|
||
|
||
// Initialize OSTree manager
|
||
let ostree_manager = match OstreeManager::new() {
|
||
Ok(manager) => manager,
|
||
Err(e) => {
|
||
println!("⚠️ Error: Not running in OSTree system: {}", e);
|
||
println!("Rollback is only available in OSTree systems.");
|
||
return Ok(());
|
||
}
|
||
};
|
||
|
||
// Check if rollback is available
|
||
match ostree_manager.check_rollback_available() {
|
||
Ok(true) => {
|
||
println!("🔄 Rollback available");
|
||
println!("");
|
||
println!("This will rollback to the previous deployment.");
|
||
println!("All changes since the last deployment will be lost.");
|
||
println!("");
|
||
println!("⚠️ WARNING: This operation cannot be undone!");
|
||
println!("");
|
||
|
||
// In a real implementation, we would prompt for confirmation
|
||
println!("🚀 Performing rollback...");
|
||
ostree_manager.rollback_to_previous()?;
|
||
println!("✅ Rollback completed successfully");
|
||
println!("🔄 Reboot required to activate rollback");
|
||
println!("");
|
||
println!("To activate: sudo reboot");
|
||
}
|
||
Ok(false) => {
|
||
println!("❌ No rollback available");
|
||
println!("Only one deployment exists on this system.");
|
||
}
|
||
Err(e) => {
|
||
println!("⚠️ Error checking rollback status: {}", e);
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|