# Upgrade Command Implementation Guide ## Overview The `upgrade` command is high complexity (247 lines in rpm-ostree) and handles system upgrades with automatic update integration, driver registration checking, and multiple upgrade paths. ## Current Implementation Status - ✅ Basic upgrade command exists in apt-ostree - ❌ Missing automatic update policy integration - ❌ Missing driver registration checking - ❌ Missing preview/check modes - ❌ Missing multiple upgrade APIs ## Implementation Requirements ### Phase 1: Option Parsing and Validation #### Files to Modify: - `src/main.rs` - Add upgrade command options - `src/system.rs` - Enhance upgrade method - `src/daemon.rs` - Add upgrade D-Bus methods #### Implementation Steps: **1.1 Update CLI Options (src/main.rs)** ```rust #[derive(Debug, Parser)] pub struct UpgradeOpts { /// Initiate a reboot after operation is complete #[arg(short = 'r', long)] reboot: bool, /// Permit deployment of chronologically older trees #[arg(long)] allow_downgrade: bool, /// Just preview package differences (implies --unchanged-exit-77) #[arg(long)] preview: bool, /// Just check if an upgrade is available (implies --unchanged-exit-77) #[arg(long)] check: bool, /// Do not download latest ostree and APT data #[arg(short = 'C', long)] cache_only: bool, /// Just download latest ostree and APT data, don't deploy #[arg(long)] download_only: bool, /// If no new deployment made, exit 77 #[arg(long)] unchanged_exit_77: bool, /// Force an upgrade even if an updates driver is registered #[arg(long)] bypass_driver: bool, /// Prevent automatic deployment finalization on shutdown #[arg(long)] lock_finalization: bool, /// For automated use only; triggered by automatic timer #[arg(long)] trigger_automatic_update_policy: bool, } ``` **1.2 Add Option Validation (src/main.rs)** ```rust impl UpgradeOpts { pub fn validate(&self) -> Result<(), Box> { // Check incompatible options if self.reboot && self.preview { return Err("Cannot specify both --reboot and --preview".into()); } if self.reboot && self.check { return Err("Cannot specify both --reboot and --check".into()); } if self.preview && (self.install_packages.is_some() || self.uninstall_packages.is_some()) { return Err("Cannot specify both --preview and --install/--uninstall".into()); } // Set implied options if self.preview { // preview implies unchanged_exit_77 } if self.check { // check implies unchanged_exit_77 } Ok(()) } } ``` ### Phase 2: Automatic Update Policy Check #### Files to Modify: - `src/system.rs` - Add automatic update policy checking - `src/daemon.rs` - Add automatic update trigger method #### Implementation Steps: **2.1 Add Automatic Update Policy (src/system.rs)** ```rust impl AptOstreeSystem { pub async fn get_automatic_update_policy(&self) -> Result, Box> { // Check systemd service status let output = tokio::process::Command::new("systemctl") .args(["is-enabled", "apt-ostreed-automatic.timer"]) .output() .await?; if output.status.success() { // Check policy configuration let policy_file = Path::new("/etc/apt-ostree/automatic.conf"); if policy_file.exists() { let content = tokio::fs::read_to_string(policy_file).await?; // Parse policy (stage, check, etc.) Ok(Some("stage".to_string())) // Default for now } else { Ok(Some("check".to_string())) // Default policy } } else { Ok(None) // Automatic updates disabled } } pub async fn trigger_automatic_update(&self, mode: &str) -> Result> { // Check if automatic updates are enabled let policy = self.get_automatic_update_policy().await?; if policy.is_none() { return Ok(false); // Automatic updates disabled } // Trigger automatic update based on mode match mode { "check" => { // Just check for updates self.check_for_updates().await } "auto" => { // Perform automatic update self.perform_automatic_update().await } _ => Err("Invalid automatic update mode".into()) } } } ``` **2.2 Add Automatic Update D-Bus Method (src/daemon.rs)** ```rust #[dbus_interface(name = "org.aptostree.dev")] impl AptOstreeDaemon { /// Trigger automatic update async fn trigger_automatic_update(&self, options: HashMap) -> Result> { let mode = options.get("mode") .and_then(|v| v.as_str()) .unwrap_or("auto"); let system = AptOstreeSystem::new().await?; let enabled = system.trigger_automatic_update(mode).await?; Ok(enabled) } } ``` ### Phase 3: Driver Registration Check #### Files to Modify: - `src/system.rs` - Add driver registration checking - `src/daemon.rs` - Add driver management #### Implementation Steps: **3.1 Add Driver Registration Check (src/system.rs)** ```rust impl AptOstreeSystem { pub async fn check_driver_registration(&self) -> Result, Box> { // Check for registered update drivers // This would check for systemd services or other update mechanisms // For now, check for common update services let services = [ "apt-daily.timer", "apt-daily-upgrade.timer", "unattended-upgrades", ]; for service in &services { let output = tokio::process::Command::new("systemctl") .args(["is-active", service]) .output() .await?; if output.status.success() { return Ok(Some(service.to_string())); } } Ok(None) // No drivers registered } pub async fn error_if_driver_registered(&self) -> Result<(), Box> { if let Some(driver) = self.check_driver_registration().await? { return Err(format!("Update driver '{}' is registered. Use --bypass-driver to override.", driver).into()); } Ok(()) } } ``` ### Phase 4: API Selection and Daemon Communication #### Files to Modify: - `src/system.rs` - Add multiple upgrade APIs - `src/daemon.rs` - Add upgrade methods - `src/client.rs` - Add client communication #### Implementation Steps: **4.1 Add Multiple Upgrade APIs (src/system.rs)** ```rust impl AptOstreeSystem { pub async fn upgrade_system(&self, opts: &UpgradeOpts) -> Result> { // Build options dictionary let mut options = HashMap::new(); options.insert("reboot".to_string(), Value::Bool(opts.reboot)); options.insert("allow-downgrade".to_string(), Value::Bool(opts.allow_downgrade)); options.insert("cache-only".to_string(), Value::Bool(opts.cache_only)); options.insert("download-only".to_string(), Value::Bool(opts.download_only)); options.insert("lock-finalization".to_string(), Value::Bool(opts.lock_finalization)); // Choose API based on options if opts.install_packages.is_some() || opts.uninstall_packages.is_some() { // Use UpdateDeployment API for package changes self.update_deployment_with_packages(opts, &options).await } else { // Use Upgrade API for system upgrade self.perform_system_upgrade(&options).await } } async fn perform_system_upgrade(&self, options: &HashMap) -> Result> { // 1. Check for available updates let updates = self.check_for_updates().await?; if updates.is_empty() { return Err("No updates available".into()); } // 2. Download updates (if not cache-only) if !options.get("cache-only").and_then(|v| v.as_bool()).unwrap_or(false) { self.download_updates(&updates).await?; } // 3. Create new deployment (if not download-only) if !options.get("download-only").and_then(|v| v.as_bool()).unwrap_or(false) { let new_commit = self.create_upgrade_commit(&updates).await?; self.update_deployment(&new_commit).await?; } // 4. Handle reboot if options.get("reboot").and_then(|v| v.as_bool()).unwrap_or(false) { self.schedule_reboot().await?; } Ok("upgrade-completed".to_string()) } async fn update_deployment_with_packages(&self, opts: &UpgradeOpts, options: &HashMap) -> Result> { // Handle package installation/removal during upgrade let install_packages = opts.install_packages.as_ref().unwrap_or(&Vec::new()); let uninstall_packages = opts.uninstall_packages.as_ref().unwrap_or(&Vec::new()); // Create new deployment with package changes let new_commit = self.create_deployment_with_packages(install_packages, uninstall_packages).await?; self.update_deployment(&new_commit).await?; Ok("deployment-updated".to_string()) } } ``` **4.2 Add Upgrade D-Bus Methods (src/daemon.rs)** ```rust #[dbus_interface(name = "org.aptostree.dev")] impl AptOstreeDaemon { /// Perform system upgrade async fn upgrade(&self, options: HashMap) -> Result> { let system = AptOstreeSystem::new().await?; let transaction_id = system.perform_system_upgrade(&options).await?; Ok(transaction_id) } /// Update deployment with package changes async fn update_deployment(&self, options: HashMap) -> Result> { let system = AptOstreeSystem::new().await?; let transaction_id = system.update_deployment_with_packages(&options).await?; Ok(transaction_id) } } ``` ### Phase 5: Transaction Monitoring #### Files to Modify: - `src/client.rs` - Add transaction monitoring - `src/system.rs` - Add transaction management #### Implementation Steps: **5.1 Add Transaction Monitoring (src/client.rs)** ```rust impl AptOstreeClient { pub async fn monitor_upgrade_transaction(&self, transaction_id: &str, opts: &UpgradeOpts) -> Result<(), Box> { // Monitor transaction progress let mut progress = 0; loop { let status = self.get_transaction_status(transaction_id).await?; match status { TransactionStatus::Running(percent) => { if percent != progress { progress = percent; println!("Progress: {}%", progress); } } TransactionStatus::Completed => { println!("Upgrade completed successfully"); break; } TransactionStatus::Failed(error) => { return Err(error.into()); } } tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; } // Handle unchanged exit 77 if opts.unchanged_exit_77 { // Check if any changes were made if !self.were_changes_made(transaction_id).await? { std::process::exit(77); } } Ok(()) } } #[derive(Debug)] enum TransactionStatus { Running(u32), Completed, Failed(String), } ``` **5.2 Add Transaction Management (src/system.rs)** ```rust impl AptOstreeSystem { pub async fn create_transaction(&self, operation: &str) -> Result> { // Create unique transaction ID let transaction_id = format!("{}-{}", operation, uuid::Uuid::new_v4()); // Store transaction state self.store_transaction_state(&transaction_id, "running").await?; Ok(transaction_id) } pub async fn update_transaction_progress(&self, transaction_id: &str, progress: u32) -> Result<(), Box> { // Update transaction progress self.store_transaction_progress(transaction_id, progress).await?; Ok(()) } pub async fn complete_transaction(&self, transaction_id: &str, success: bool) -> Result<(), Box> { let status = if success { "completed" } else { "failed" }; self.store_transaction_state(transaction_id, status).await?; Ok(()) } } ``` ## Main Upgrade Command Implementation ### Files to Modify: - `src/main.rs` - Main upgrade command logic ### Implementation: ```rust async fn upgrade_command(opts: UpgradeOpts) -> Result<(), Box> { // 1. Validate options opts.validate()?; // 2. Check automatic update policy if !opts.trigger_automatic_update_policy { let system = AptOstreeSystem::new().await?; if let Some(policy) = system.get_automatic_update_policy().await? { println!("note: automatic updates ({}) are enabled", policy); } } // 3. Check driver registration (unless bypassed) if !opts.bypass_driver { let system = AptOstreeSystem::new().await?; system.error_if_driver_registered().await?; } // 4. Handle automatic update trigger if opts.trigger_automatic_update_policy || opts.preview || opts.check { let client = AptOstreeClient::new().await?; let mode = if opts.preview || opts.check { "check" } else { "auto" }; let mut options = HashMap::new(); options.insert("mode".to_string(), Value::String(mode.to_string())); let enabled = client.trigger_automatic_update(options).await?; if !enabled { println!("Automatic updates are not enabled; exiting..."); return Ok(()); } return Ok(()); } // 5. Perform manual upgrade let system = AptOstreeSystem::new().await?; let transaction_id = system.upgrade_system(&opts).await?; // 6. Monitor transaction let client = AptOstreeClient::new().await?; client.monitor_upgrade_transaction(&transaction_id, &opts).await?; Ok(()) } ``` ## Testing Strategy ### Unit Tests ```rust #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_upgrade_options_validation() { let mut opts = UpgradeOpts { reboot: true, preview: true, ..Default::default() }; assert!(opts.validate().is_err()); opts.preview = false; assert!(opts.validate().is_ok()); } #[tokio::test] async fn test_driver_registration_check() { let system = AptOstreeSystem::new().await.unwrap(); let driver = system.check_driver_registration().await.unwrap(); // Test based on system state } #[tokio::test] async fn test_automatic_update_policy() { let system = AptOstreeSystem::new().await.unwrap(); let policy = system.get_automatic_update_policy().await.unwrap(); // Test policy detection } } ``` ### Integration Tests ```rust #[tokio::test] async fn test_upgrade_command_integration() { let opts = UpgradeOpts { reboot: false, allow_downgrade: false, preview: false, check: false, cache_only: false, download_only: false, unchanged_exit_77: false, bypass_driver: false, lock_finalization: false, trigger_automatic_update_policy: false, install_packages: None, uninstall_packages: None, }; let result = upgrade_command(opts).await; assert!(result.is_ok()); } ``` ## Dependencies to Add Add to `Cargo.toml`: ```toml [dependencies] uuid = { version = "1.0", features = ["v4"] } tokio = { version = "1.0", features = ["process", "time"] } serde_json = "1.0" ``` ## Implementation Checklist - [ ] Add CLI options for all upgrade modes - [ ] Implement option validation logic - [ ] Add automatic update policy checking - [ ] Implement driver registration checking - [ ] Add multiple upgrade APIs (Upgrade, UpdateDeployment) - [ ] Implement automatic update trigger - [ ] Add transaction monitoring - [ ] Handle unchanged exit 77 logic - [ ] Add comprehensive error handling - [ ] Write unit and integration tests - [ ] Update documentation ## References - rpm-ostree source: `src/app/rpmostree-builtin-upgrade.cxx` (247 lines) - systemd service management - APT automatic update configuration - OSTree deployment management