# Rollback Implementation This document explains how the enhanced rollback functionality works in the APT-DNF Bridge. ## Overview The rollback system provides a comprehensive way to undo package installations and upgrades. It tracks what packages were changed during a transaction and can restore the system to its previous state using a two-phase approach. ## How It Works ### Package Tracking Both `Transaction` (core) and `TransactionV2` (advanced) track three types of package information: - **`previously_installed`**: Packages that were installed before the transaction - **`newly_installed`**: Packages that weren't installed before the transaction - **`upgraded`**: Packages that were upgraded, with their previous versions stored ### Two-Phase Rollback When `rollback()` is called, it performs two phases: 1. **Phase 1 - Remove New Packages**: Uses `apt remove -y` to remove all packages in `newly_installed` 2. **Phase 2 - Downgrade Upgraded Packages**: Uses `apt install -y package=oldversion` to restore previous versions ### Example Usage #### Core API ```rust use apt_dnf_bridge::{Transaction, Package}; #[tokio::main] async fn main() -> Result<(), Box> { // Create and commit a transaction let mut tx = Transaction::new(); tx.add_install(Package::new("vim", "2:8.1.2269-1ubuntu5.14", "amd64")).await?; tx.add_install(Package::new("curl", "7.68.0-1ubuntu2.18", "amd64")).await?; tx.resolve().await?; tx.commit().await?; // Later, rollback the changes tx.rollback().await?; Ok(()) } ``` #### Advanced API ```rust use apt_dnf_bridge::{TransactionV2, Package, BackendConfig}; #[tokio::main] async fn main() -> Result<(), Box> { // Create transaction with backend let config = BackendConfig::default(); let mut tx = TransactionV2::with_shell_backend(config).await?; // Add packages and commit tx.add_install(Package::new("git", "1:2.25.1-1ubuntu3.11", "amd64")).await?; tx.resolve().await?; tx.commit().await?; // Rollback using backend tx.rollback().await?; Ok(()) } ``` ## API Reference ### Core Transaction Methods ```rust impl Transaction { /// Get packages that were newly installed in this transaction. pub fn newly_installed(&self) -> &HashSet /// Get packages that were upgraded in this transaction. pub fn upgraded(&self) -> &HashMap /// Get packages that were previously installed before this transaction. pub fn previously_installed(&self) -> &HashSet /// Check if the transaction has any changes that can be rolled back. pub fn can_rollback(&self) -> bool /// Attempt to rollback the transaction (best-effort). pub async fn rollback(&self) -> Result<()> } ``` ### Advanced Transaction Methods ```rust impl TransactionV2 { /// Get packages that were newly installed in this transaction. pub fn newly_installed(&self) -> &HashSet /// Get packages that were upgraded in this transaction. pub fn upgraded(&self) -> &HashMap /// Get packages that were previously installed before this transaction. pub fn previously_installed(&self) -> &HashSet /// Check if the transaction has any changes that can be rolled back. pub fn can_rollback(&self) -> bool /// Get a list of all changed packages (newly installed + upgraded). pub fn changed_packages(&self) -> Vec /// Attempt to rollback the transaction using the two-phase approach. pub async fn rollback(&self) -> Result<()> } ``` ## Error Handling The rollback system uses a "fail fast" approach: - **Critical errors** (package not found, permission denied) cause immediate failure - **Soft errors** (package already removed) generate warnings but continue - If a specific version can't be installed, the package is removed instead ## Limitations This implementation handles the most common rollback scenarios: - ✅ New package installations - ✅ Package upgrades with available previous versions - ✅ Mixed transactions (some new, some upgrades) It does **not** handle: - Complex dependency changes - Configuration file modifications - System state beyond package versions - Concurrent package operations For more advanced rollback scenarios, consider using OSTree's native checkpoint/rollback functionality. ## Implementation Details ### Data Structures ```rust pub struct Transaction { operations: Vec, previously_installed: HashSet, // packages that were installed before newly_installed: HashSet, // packages that weren't installed before upgraded: HashMap, // package -> old_version for upgrades log_commands: bool, } ``` ### Key Methods - `add_install()`: Adds a package to the transaction - `commit()`: Installs packages and categorizes them as new vs upgraded - `rollback()`: Performs the two-phase rollback process - `changed_packages()`: Returns a list of what was changed ### State Management The tracking fields (`newly_installed`, `upgraded`) are only populated after a successful `commit()`. This ensures the rollback data reflects what actually happened during the transaction. ## Testing The rollback functionality is tested in the test suite, but actual package installation/removal is not performed in tests to avoid side effects on the test environment. ## OSTree Integration For atomic operations, use OSTree's native checkpoint/rollback: ```rust // 1. Create OSTree checkpoint let checkpoint = ostree_create_checkpoint()?; // 2. Run APT transaction let mut tx = Transaction::new(); tx.add_install(Package::new("vim", "", "")).await?; tx.commit().await?; // 3. Commit or rollback based on result if success { ostree_commit_changes()?; } else { ostree_rollback_to_checkpoint(checkpoint)?; } ``` ## Design Philosophy The current implementation follows these principles: - **Simplicity over completeness**: Handle common cases well - **Fail fast**: Clear error messages when things go wrong - **Maintainability**: Easy to understand and modify - **Reliability**: Works consistently in real-world scenarios This approach ensures the rollback system is robust, understandable, and maintainable while handling the vast majority of actual use cases.