apt-tx/README.md
robojerk ac4987c7d1 Update README to reflect current implementation
- Updated line count from ~260 to ~500 lines
- Removed thiserror dependency reference (only anyhow now)
- Added AptConfig documentation and configuration options
- Updated API documentation with all new methods
- Added configuration usage examples
- Fixed rollback claim (now implemented, not missing)
- Added configuration_usage example reference
- All examples and tests verified working
2025-09-13 11:24:37 -07:00

8.9 KiB

APT Wrapper

A simple DNF-like API wrapper around APT for porting rpm-ostree to apt-ostree.

Purpose

This library provides a simple transaction interface that mimics DNF's imperative model, making it easier to adapt rpm-ostree code for Debian/Ubuntu systems.

Features

  • Simple transaction interface: add_package(), resolve(), commit(), rollback()
  • DNF-like API: Easy to port from rpm-ostree
  • Version-based rollback: Track versions and restore previous states
  • Minimal dependencies: Only anyhow
  • ~500 lines total: Focused and maintainable

Quick Start

use apt_wrapper::{AptTransaction, init};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize
    init()?;
    
    // Create transaction
    let mut tx = AptTransaction::new()?;
    
    // Add packages
    tx.add_package("vim")?;
    tx.add_package("git")?;
    
    // Resolve dependencies
    tx.resolve()?;
    
    // Commit transaction
    tx.commit()?;
    
    // If something goes wrong, rollback
    // tx.rollback()?;
    
    Ok(())
}

API

AptTransaction

pub struct AptTransaction {
    packages: Vec<String>,
    newly_installed: HashSet<String>,  // packages that weren't installed before
    upgraded: HashMap<String, String>, // package -> old_version for upgrades
    config: AptConfig,  // APT configuration options
}

impl AptTransaction {
    pub fn new() -> Result<Self>;                    // Create new transaction (default config)
    pub fn with_config(config: AptConfig) -> Result<Self>;  // Create with custom config
    pub fn for_testing() -> Result<Self>;            // Create for testing (dry run)
    pub fn add_package(&mut self, name: &str) -> Result<()>;  // Add package
    pub fn resolve(&self) -> Result<()>;             // Resolve dependencies
    pub fn commit(&mut self) -> Result<()>;          // Commit transaction
    pub fn rollback(&self) -> Result<()>;            // Rollback transaction
    pub fn packages(&self) -> &[String];             // Get package list
    pub fn changed_packages(&self) -> Vec<String>;   // Get changed packages
    pub fn is_empty(&self) -> bool;                  // Check if empty
    pub fn config(&self) -> &AptConfig;              // Get configuration
    pub fn set_config(&mut self, config: AptConfig); // Update configuration
    pub fn enable_dry_run(&mut self);                // Enable dry run mode
    pub fn disable_dry_run(&mut self);               // Disable dry run mode
    pub fn enable_quiet(&mut self);                  // Enable quiet mode
    pub fn disable_quiet(&mut self);                 // Disable quiet mode
}

Utility Functions

pub fn init() -> Result<()>;                                    // Initialize
pub fn search_packages(query: &str) -> Result<Vec<String>>;     // Search packages
pub fn is_package_installed(name: &str) -> Result<bool>;        // Check installed
pub fn get_package_info(name: &str) -> Result<AptPackage>;      // Get package info

Configuration

pub struct AptConfig {
    pub no_install_recommends: bool,  // Don't install recommended packages
    pub no_install_suggests: bool,    // Don't install suggested packages
    pub assume_yes: bool,             // Assume yes to all prompts
    pub quiet: bool,                  // Quiet output
    pub dry_run: bool,                // Dry run mode (don't actually execute)
}

impl AptConfig {
    pub fn new() -> Self;             // Create with default values
    pub fn for_testing() -> Self;     // Create for testing (dry run + quiet)
}

Installation

Add to your Cargo.toml:

[dependencies]
apt-wrapper = "0.1.0"

Using as a Crate

Adding to Your Project

  1. Add the dependency to your Cargo.toml:
[dependencies]
apt-wrapper = "0.1.0"
  1. Import the crate in your Rust code:
use apt_wrapper::{AptTransaction, AptConfig, init, search_packages, is_package_installed};
  1. Initialize the library before use:
fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Always call init() first
    init()?;
    
    // Now you can use the library
    let mut tx = AptTransaction::new()?;
    // ... rest of your code
}

Crate Features

The crate provides these main components:

  • AptTransaction: Main transaction interface for package operations
  • AptConfig: Configuration options for APT behavior
  • init(): Initialize the library (required before use)
  • search_packages(): Search for available packages
  • is_package_installed(): Check if a package is installed
  • get_package_info(): Get detailed package information

Error Handling

The crate uses anyhow::Result for error handling. All functions return Result<T, anyhow::Error>:

use anyhow::Result;

fn example() -> Result<()> {
    let mut tx = AptTransaction::new()?;  // Returns Result<AptTransaction, anyhow::Error>
    tx.add_package("vim")?;               // Returns Result<(), anyhow::Error>
    tx.commit()?;                         // Returns Result<(), anyhow::Error>
    Ok(())
}

Complete Example

Here's a complete example of using the crate:

use apt_wrapper::{AptTransaction, AptConfig, init, search_packages, is_package_installed};
use anyhow::Result;

fn main() -> Result<()> {
    // Initialize the library
    init()?;
    
    // Search for packages
    let editors = search_packages("editor")?;
    println!("Available editors: {:?}", editors);
    
    // Check if vim is installed
    if !is_package_installed("vim")? {
        println!("vim is not installed, installing...");
        
        // Create transaction
        let mut tx = AptTransaction::new()?;
        tx.add_package("vim")?;
        tx.add_package("git")?;
        
        // Resolve and commit
        tx.resolve()?;
        tx.commit()?;
        
        println!("Packages installed successfully!");
    } else {
        println!("vim is already installed");
    }
    
    Ok(())
}

Building Your Project

Once you've added the dependency, build your project normally:

cargo build
cargo run

The crate will automatically handle APT integration and provide the DNF-like interface for your application.

Usage

Basic Transaction

use apt_wrapper::AptTransaction;

let mut tx = AptTransaction::new()?;
tx.add_package("vim")?;
tx.add_package("git")?;
tx.resolve()?;
tx.commit()?;

Search Packages

use apt_wrapper::search_packages;

let packages = search_packages("editor")?;
for package in packages {
    println!("Found: {}", package);
}

Check Installation

use apt_wrapper::is_package_installed;

if is_package_installed("vim")? {
    println!("vim is installed");
}

Rollback Support

use apt_wrapper::AptTransaction;

let mut tx = AptTransaction::new()?;
tx.add_package("vim")?;
tx.add_package("git")?;
tx.resolve()?;

// Commit the transaction
tx.commit()?;

// If something goes wrong later, rollback
tx.rollback()?;

// Check what was changed
println!("Changed packages: {:?}", tx.changed_packages());

Configuration Options

use apt_wrapper::{AptTransaction, AptConfig};

// Create with custom configuration
let config = AptConfig {
    no_install_recommends: true,
    no_install_suggests: true,
    assume_yes: true,
    quiet: false,
    dry_run: true,
};

let mut tx = AptTransaction::with_config(config)?;

// Or modify configuration at runtime
tx.enable_dry_run();
tx.enable_quiet();

// For testing
let mut test_tx = AptTransaction::for_testing()?;

Testing

cargo test

Examples

cargo run --example simple_usage
cargo run --example configuration_usage

Design Philosophy

This wrapper is designed to be:

  1. Simple: Minimal API surface, easy to understand
  2. Focused: Only what's needed for apt-ostree porting
  3. DNF-like: Familiar interface for rpm-ostree developers
  4. Minimal: ~500 lines total, no complex abstractions

Differences from DNF

  • APT is declarative: Dependencies are resolved automatically
  • No complex repo management: APT uses simple text files
  • Simpler error handling: APT provides clear error messages
  • Transaction rollback: Implemented two-phase rollback (remove new, downgrade upgraded)

OSTree Integration

For atomic operations, use OSTree's native checkpoint/rollback:

// 1. Create OSTree checkpoint
let checkpoint = ostree_create_checkpoint()?;

// 2. Run APT transaction
let mut tx = AptTransaction::new()?;
tx.add_package("vim")?;
tx.commit()?;

// 3. Commit or rollback based on result
if success {
    ostree_commit_changes()?;
} else {
    ostree_rollback_to_checkpoint(checkpoint)?;
}

License

MIT License - see LICENSE file for details.

Documentation

Contributing

This is a focused tool for apt-ostree. Contributions should maintain simplicity and focus on the core use case.