# πŸ”„ **apt-ostree User Overlay System Architecture** ## πŸ“‹ **Overview** This document outlines the user overlay system architecture for apt-ostree, based on analysis of how rpm-ostree implements user overlays, transient filesystem modifications, and overlayfs integration. The user overlay system allows users to make temporary changes to the system without creating new OSTree commits, providing a lightweight way to test modifications. ## πŸ”— **Related Documents** - **Core Architecture**: [Overview](overview.md) | [CLI-Daemon Separation](cli-daemon-separation.md) - **Package Management**: [APT Integration](apt-library-analysis.md) | [Package Overrides](package-overrides.md) - **System Operations**: [Transaction System](transaction-system.md) | [Live Updates](live-updates.md) - **Security**: [Responsibility Analysis](responsibility-analysis.md) | [Error Handling](error-handling-analysis.md) ## πŸ—οΈ **Architecture Overview** ### **Component Separation** ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ CLI Client β”‚ β”‚ Rust Core β”‚ β”‚ Rust Daemon β”‚ β”‚ (apt-ostree) │◄──►│ (DBus) │◄──►│ (aptostreed) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β€’ usroverlay β”‚ β”‚ β€’ Client Logic β”‚ β”‚ β€’ Overlay β”‚ β”‚ β€’ apply-live β”‚ β”‚ β€’ DBus Client β”‚ β”‚ β€’ Filesystem β”‚ β”‚ β€’ reset β”‚ β”‚ β€’ Overlay Mgmt β”‚ β”‚ β€’ State Mgmt β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### **Responsibility Distribution** #### **CLI Client (`apt-ostree`)** - **Command parsing** for user overlay subcommands - **User interface** and overlay management - **DBus communication** with daemon - **Overlay validation** and processing #### **Daemon (`apt-ostreed`)** - **Overlay creation** and management - **Filesystem overlay** implementation - **Overlay state** persistence - **Conflict resolution** and validation ## πŸ” **rpm-ostree Implementation Analysis** ### **User Overlay Commands Structure** Based on `rpmostree-builtin-usroverlay.cxx`, rpm-ostree provides these user overlay subcommands: ```c static RpmOstreeCommand usroverlay_subcommands[] = { { "apply", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT, "Apply a user overlay to the system", rpmostree_usroverlay_builtin_apply }, { "list", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD, "List current user overlays", rpmostree_usroverlay_builtin_list }, { "reset", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT, "Reset a user overlay", rpmostree_usroverlay_builtin_reset }, { "remove", RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT, "Remove a user overlay", rpmostree_usroverlay_builtin_remove }, { NULL, (RpmOstreeBuiltinFlags)0, NULL, NULL } }; ``` ### **Key Insights from rpm-ostree** 1. **Transient Modifications**: User overlays are temporary and don't persist across reboots 2. **Filesystem Overlay**: Uses overlayfs or similar to layer changes over base system 3. **User Isolation**: Each user can have their own overlays 4. **Live Application**: Changes can be applied immediately without rebooting ## πŸš€ **apt-ostree Implementation Strategy** ### **1. CLI Command Structure** ```rust // src/main.rs - User overlay command handling async fn usroverlay_commands(args: &[String]) -> AptOstreeResult<()> { if args.is_empty() { show_usroverlay_help(); return Ok(()); } let subcommand = &args[0]; match subcommand.as_str() { "apply" => usroverlay_apply(&args[1..]).await?, "list" => usroverlay_list(&args[1..]).await?, "reset" => usroverlay_reset(&args[1..]).await?, "remove" => usroverlay_remove(&args[1..]).await?, _ => { println!("❌ Unknown usroverlay subcommand: {}", subcommand); show_usroverlay_help(); } } Ok(()) } ``` ### **2. User Overlay System** #### **Core User Overlay Manager** ```rust // src/usroverlay/usroverlay_manager.rs pub struct UserOverlayManager { ostree_manager: Arc, overlay_fs_manager: Arc, overlay_store: Arc>, security_manager: Arc, } impl UserOverlayManager { pub async fn apply_user_overlay( &self, overlay_name: &str, source_path: &Path, target_path: &str, user_id: u32, session_id: String, options: OverlayOptions, ) -> Result { // Validate overlay request self.validate_overlay_request(overlay_name, source_path, target_path).await?; // Check user authorization self.security_manager .authorize_user_overlay(user_id) .await?; // Create overlay filesystem let overlay_mount = self.overlay_fs_manager .create_overlay(overlay_name, target_path, options.clone()) .await?; // Copy source files to overlay self.copy_files_to_overlay(&overlay_mount, source_path, target_path).await?; // Mount overlay to target location self.overlay_fs_manager .mount_overlay(&overlay_mount, target_path) .await?; // Store overlay information let overlay_info = UserOverlay { name: overlay_name.to_string(), target_path: target_path.to_string(), source_path: source_path.to_string_lossy().to_string(), user_id, session_id, mount_point: overlay_mount.mount_point.clone(), created_at: chrono::Utc::now(), options, }; self.overlay_store .write() .await .add_overlay(overlay_info) .await?; Ok(OverlayResult::Success { message: format!("User overlay '{}' applied successfully", overlay_name), mount_point: overlay_mount.mount_point, details: Some(format!("Target: {}", target_path)), }) } pub async fn list_user_overlays(&self, user_id: Option) -> Result, Error> { let overlays = self.overlay_store .read() .await .list_overlays(user_id) .await?; Ok(overlays) } pub async fn reset_user_overlay( &self, overlay_name: &str, user_id: u32, session_id: String, ) -> Result { // Get overlay information let overlay_info = self.overlay_store .read() .await .get_overlay(overlay_name) .await?; if overlay_info.is_none() { return Err(Error::OverlayNotFound(overlay_name.to_string())); } let overlay_info = overlay_info.unwrap(); // Check user authorization if overlay_info.user_id != user_id { return Err(Error::OverlayAccessDenied( "Cannot reset overlay owned by another user".to_string(), )); } // Unmount overlay self.overlay_fs_manager .unmount_overlay(&overlay_info.mount_point) .await?; // Reset overlay files self.overlay_fs_manager .reset_overlay_files(&overlay_info.mount_point) .await?; // Remount overlay self.overlay_fs_manager .mount_overlay(&overlay_info.mount_point, &overlay_info.target_path) .await?; Ok(OverlayResult::Success { message: format!("User overlay '{}' reset successfully", overlay_name), mount_point: overlay_info.mount_point.clone(), details: Some("Overlay files reset to base state".to_string()), }) } pub async fn remove_user_overlay( &self, overlay_name: &str, user_id: u32, session_id: String, ) -> Result { // Get overlay information let overlay_info = self.overlay_store .read() .await .get_overlay(overlay_name) .await?; if overlay_info.is_none() { return Err(Error::OverlayNotFound(overlay_name.to_string())); } let overlay_info = overlay_info.unwrap(); // Check user authorization if overlay_info.user_id != user_id { return Err(Error::OverlayAccessDenied( "Cannot remove overlay owned by another user".to_string(), )); } // Unmount overlay self.overlay_fs_manager .unmount_overlay(&overlay_info.mount_point) .await?; // Remove overlay filesystem self.overlay_fs_manager .remove_overlay(&overlay_info.mount_point) .await?; // Remove from store self.overlay_store .write() .await .remove_overlay(overlay_name) .await?; Ok(OverlayResult::Success { message: format!("User overlay '{}' removed successfully", overlay_name), mount_point: overlay_info.mount_point.clone(), details: Some("Overlay completely removed".to_string()), }) } async fn validate_overlay_request( &self, overlay_name: &str, source_path: &Path, target_path: &str, ) -> Result<(), Error> { // Check if overlay name is valid if !self.is_valid_overlay_name(overlay_name) { return Err(Error::InvalidOverlayName( "Overlay name contains invalid characters".to_string(), )); } // Check if overlay name already exists if self.overlay_store .read() .await .has_overlay(overlay_name) .await? { return Err(Error::OverlayAlreadyExists(overlay_name.to_string())); } // Check if source path exists and is accessible if !source_path.exists() { return Err(Error::SourcePathNotFound( source_path.to_string_lossy().to_string(), )); } // Check if target path is valid if !self.is_valid_target_path(target_path) { return Err(Error::InvalidTargetPath( "Target path must be under /usr".to_string(), )); } // Check if target path is already under an overlay if self.is_path_under_overlay(target_path).await? { return Err(Error::TargetPathUnderOverlay( "Target path is already under another overlay".to_string(), )); } Ok(()) } async fn copy_files_to_overlay( &self, overlay_mount: &OverlayMount, source_path: &Path, target_path: &str, ) -> Result<(), Error> { let overlay_path = &overlay_mount.overlay_path; if source_path.is_file() { // Copy single file let target_file = overlay_path.join(target_path); if let Some(parent) = target_file.parent() { tokio::fs::create_dir_all(parent).await?; } tokio::fs::copy(source_path, &target_file).await?; } else if source_path.is_dir() { // Copy directory recursively self.copy_directory_recursive(source_path, overlay_path, target_path).await?; } Ok(()) } async fn copy_directory_recursive( &self, source_dir: &Path, overlay_path: &Path, target_path: &str, ) -> Result<(), Error> { let target_dir = overlay_path.join(target_path); tokio::fs::create_dir_all(&target_dir).await?; let mut entries = tokio::fs::read_dir(source_dir).await?; while let Some(entry) = entries.next_entry().await? { let source_file = entry.path(); let file_name = source_file.file_name().unwrap(); let target_file = target_dir.join(file_name); if source_file.is_file() { tokio::fs::copy(&source_file, &target_file).await?; } else if source_file.is_dir() { self.copy_directory_recursive( &source_file, overlay_path, &target_file.strip_prefix(overlay_path)?.to_string_lossy(), ).await?; } } Ok(()) } fn is_valid_overlay_name(&self, name: &str) -> bool { // Overlay names must be alphanumeric with hyphens and underscores name.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') && !name.is_empty() && name.len() <= 64 } fn is_valid_target_path(&self, path: &str) -> bool { // Target paths must be under /usr path.starts_with("/usr/") && !path.contains("..") } async fn is_path_under_overlay(&self, path: &str) -> bool { let overlays = self.list_user_overlays(None).await?; for overlay in overlays { if path.starts_with(&overlay.target_path) { return true; } } false } } ``` ### **3. Overlay Filesystem Management** #### **OverlayFS Integration** ```rust // src/usroverlay/overlay_fs_manager.rs pub struct OverlayFsManager { overlay_base_path: PathBuf, mount_manager: Arc, } impl OverlayFsManager { pub async fn create_overlay( &self, overlay_name: &str, target_path: &str, options: OverlayOptions, ) -> Result { // Create overlay directory structure let overlay_path = self.overlay_base_path.join(overlay_name); let upper_path = overlay_path.join("upper"); let work_path = overlay_path.join("work"); let mount_point = overlay_path.join("mount"); tokio::fs::create_dir_all(&upper_path).await?; tokio::fs::create_dir_all(&work_path).await?; tokio::fs::create_dir_all(&mount_point).await?; // Set up overlayfs mount let mount_options = self.build_mount_options(target_path, &upper_path, &work_path, &options).await?; let mount = OverlayMount { name: overlay_name.to_string(), overlay_path: overlay_path.clone(), upper_path, work_path, mount_point, target_path: target_path.to_string(), options, }; Ok(mount) } pub async fn mount_overlay( &self, overlay_mount: &OverlayMount, target_path: &str, ) -> Result<(), Error> { // Create target directory if it doesn't exist tokio::fs::create_dir_all(target_path).await?; // Build mount command let mount_cmd = self.build_mount_command(overlay_mount, target_path).await?; // Execute mount let output = tokio::process::Command::new("mount") .args(&mount_cmd) .output() .await?; if !output.status.success() { return Err(Error::MountFailed { stderr: String::from_utf8_lossy(&output.stderr).to_string(), exit_code: output.status.code(), }); } Ok(()) } pub async fn unmount_overlay(&self, mount_point: &str) -> Result<(), Error> { // Unmount overlay let output = tokio::process::Command::new("umount") .arg(mount_point) .output() .await?; if !output.status.success() { return Err(Error::UnmountFailed { stderr: String::from_utf8_lossy(&output.stderr).to_string(), exit_code: output.status.code(), }); } Ok(()) } pub async fn remove_overlay(&self, mount_point: &str) -> Result<(), Error> { // Unmount first self.unmount_overlay(mount_point).await?; // Remove overlay directory let overlay_path = Path::new(mount_point).parent().unwrap(); if overlay_path.exists() { tokio::fs::remove_dir_all(overlay_path).await?; } Ok(()) } pub async fn reset_overlay_files(&self, mount_point: &str) -> Result<(), Error> { // Get overlay path from mount point let overlay_path = Path::new(mount_point).parent().unwrap(); let upper_path = overlay_path.join("upper"); if upper_path.exists() { // Remove all files from upper layer tokio::fs::remove_dir_all(&upper_path).await?; tokio::fs::create_dir_all(&upper_path).await?; } Ok(()) } async fn build_mount_options( &self, target_path: &str, upper_path: &Path, work_path: &Path, options: &OverlayOptions, ) -> Result, Error> { let mut mount_options = Vec::new(); // Basic overlayfs options mount_options.push("type=overlay".to_string()); mount_options.push(format!("lowerdir={}", target_path)); mount_options.push(format!("upperdir={}", upper_path.display())); mount_options.push(format!("workdir={}", work_path.display())); // Additional options if options.read_only { mount_options.push("ro".to_string()); } if options.allow_other { mount_options.push("allow_other".to_string()); } if options.default_permissions { mount_options.push("default_permissions".to_string()); } Ok(mount_options) } async fn build_mount_command( &self, overlay_mount: &OverlayMount, target_path: &str, ) -> Result, Error> { let mut cmd = Vec::new(); // Mount type cmd.push("-t".to_string()); cmd.push("overlay".to_string()); // Source (overlay) cmd.push("overlay".to_string()); // Target cmd.push(target_path.to_string()); // Options let options = self.build_mount_options( target_path, &overlay_mount.upper_path, &overlay_mount.work_path, &overlay_mount.options, ).await?; cmd.push("-o".to_string()); cmd.push(options.join(",")); Ok(cmd) } } ``` ### **4. Overlay Store and Persistence** #### **Overlay Storage System** ```rust // src/usroverlay/overlay_store.rs pub struct OverlayStore { database: Arc>, storage_path: PathBuf, } impl OverlayStore { pub async fn add_overlay( &self, overlay_info: UserOverlay, ) -> Result<(), Error> { // Save to database self.database .write() .await .save_overlay(&overlay_info.name, &overlay_info) .await?; // Save to file system for recovery let file_path = self.storage_path.join(format!("{}.json", overlay_info.name)); let content = serde_json::to_string_pretty(&overlay_info)?; tokio::fs::write(&file_path, content).await?; Ok(()) } pub async fn get_overlay( &self, overlay_name: &str, ) -> Result, Error> { // Try database first if let Some(overlay_info) = self.database .read() .await .load_overlay(overlay_name) .await? { return Ok(Some(overlay_info)); } // Fallback to file system let file_path = self.storage_path.join(format!("{}.json", overlay_name)); if file_path.exists() { let content = tokio::fs::read_to_string(&file_path).await?; let overlay_info: UserOverlay = serde_json::from_str(&content)?; return Ok(Some(overlay_info)); } Ok(None) } pub async fn list_overlays(&self, user_id: Option) -> Result, Error> { // Get from database let overlays = self.database .read() .await .list_overlays(user_id) .await?; Ok(overlays) } pub async fn remove_overlay( &self, overlay_name: &str, ) -> Result<(), Error> { // Remove from database self.database .write() .await .delete_overlay(overlay_name) .await?; // Remove from file system let file_path = self.storage_path.join(format!("{}.json", overlay_name)); if file_path.exists() { tokio::fs::remove_file(&file_path).await?; } Ok(()) } pub async fn has_overlay(&self, overlay_name: &str) -> Result { let overlay_info = self.get_overlay(overlay_name).await?; Ok(overlay_info.is_some()) } pub async fn cleanup_orphaned_overlays(&self) -> Result<(), Error> { let overlays = self.list_overlays(None).await?; for overlay in overlays { // Check if overlay mount point still exists if !Path::new(&overlay.mount_point).exists() { // Remove orphaned overlay self.remove_overlay(&overlay.name).await?; } } Ok(()) } } ``` ### **5. CLI Command Implementations** #### **Apply Command** ```rust // src/commands/usroverlay_apply.rs pub async fn usroverlay_apply(args: &[String]) -> AptOstreeResult<()> { let mut osname = None; let mut overlay_name = None; let mut source_path = None; let mut target_path = None; let mut read_only = false; let mut allow_other = false; let mut default_permissions = false; // Parse arguments let mut i = 0; while i < args.len() { match args[i].as_str() { "--os" => { if i + 1 < args.len() { osname = Some(args[i + 1].clone()); i += 2; } else { return Err(AptOstreeError::InvalidArgument("--os requires a value".to_string())); } } "--stateroot" => { if i + 1 < args.len() { osname = Some(args[i + 1].clone()); i += 2; } else { return Err(AptOstreeError::InvalidArgument("--stateroot requires a value".to_string())); } } "--read-only" => { read_only = true; i += 1; } "--allow-other" => { allow_other = true; i += 1; } "--default-permissions" => { default_permissions = true; i += 1; } _ => { if overlay_name.is_none() { overlay_name = Some(args[i].clone()); } else if source_path.is_none() { source_path = Some(args[i].clone()); } else if target_path.is_none() { target_path = Some(args[i].clone()); } else { return Err(AptOstreeError::InvalidArgument( format!("Unexpected argument: {}", args[i]), )); } i += 1; } } } // Validate arguments let overlay_name = overlay_name.ok_or_else(|| { AptOstreeError::InvalidArgument("OVERLAY_NAME is required".to_string()) })?; let source_path = source_path.ok_or_else(|| { AptOstreeError::InvalidArgument("SOURCE_PATH is required".to_string()) })?; let target_path = target_path.ok_or_else(|| { AptOstreeError::InvalidArgument("TARGET_PATH is required".to_string()) })?; // Initialize user overlay manager let overlay_manager = UserOverlayManager::new(osname.as_deref()).await?; // Get user and session information let user_id = get_current_user_id()?; let session_id = get_current_session_id().await?; // Build overlay options let options = OverlayOptions { read_only, allow_other, default_permissions, }; // Apply user overlay let result = overlay_manager .apply_user_overlay( &overlay_name, Path::new(&source_path), &target_path, user_id, session_id, options, ) .await?; // Display results match result { OverlayResult::Success { message, mount_point, details } => { println!("βœ… {}", message); println!("πŸ“ Mount point: {}", mount_point); if let Some(details) = details { println!("ℹ️ {}", details); } } OverlayResult::Failure { message, details } => { println!("❌ {}", message); if let Some(details) = details { println!("ℹ️ {}", details); } } } Ok(()) } ``` #### **List Command** ```rust // src/commands/usroverlay_list.rs pub async fn usroverlay_list(args: &[String]) -> AptOstreeResult<()> { let mut osname = None; let mut user_id = None; // Parse arguments let mut i = 0; while i < args.len() { match args[i].as_str() { "--os" => { if i + 1 < args.len() { osname = Some(args[i + 1].clone()); i += 2; } else { return Err(AptOstreeError::InvalidArgument("--os requires a value".to_string())); } } "--stateroot" => { if i + 1 < args.len() { osname = Some(args[i + 1].clone()); i += 2; } else { return Err(AptOstreeError::InvalidArgument("--stateroot requires a value".to_string())); } } "--user" => { if i + 1 < args.len() { user_id = Some(args[i + 1].parse::().map_err(|_| { AptOstreeError::InvalidArgument("--user requires a valid user ID".to_string()) })?); i += 2; } else { return Err(AptOstreeError::InvalidArgument("--user requires a value".to_string())); } } _ => { return Err(AptOstreeError::InvalidArgument( format!("Unknown option: {}", args[i]), )); } } } // Initialize user overlay manager let overlay_manager = UserOverlayManager::new(osname.as_deref()).await?; // List overlays let overlays = overlay_manager.list_user_overlays(user_id).await?; // Display results if overlays.is_empty() { println!("πŸ‘€ No user overlays found"); } else { println!("πŸ‘€ User overlays ({}):", overlays.len()); println!("====================="); for overlay in overlays { println!(" β€’ {}: {} β†’ {}", overlay.name, overlay.source_path, overlay.target_path ); println!(" User: {} | Created: {}", overlay.user_id, overlay.created_at.format("%Y-%m-%d %H:%M:%S UTC") ); println!(" Mount: {}", overlay.mount_point); println!(" Options: read_only={}, allow_other={}, default_permissions={}", overlay.options.read_only, overlay.options.allow_other, overlay.options.default_permissions ); println!(); } } Ok(()) } ``` ## πŸ” **Security and Privileges** ### **1. Overlay Authorization** ```rust // Security checks for user overlays impl SecurityManager { pub async fn authorize_user_overlay(&self, user_id: u32) -> Result<(), SecurityError> { // Check if user has permission to create overlays let action = "org.projectatomic.aptostree.usroverlay"; self.check_authorization(action, user_id, HashMap::new()).await?; // Check if user has quota remaining if !self.check_user_overlay_quota(user_id).await? { return Err(SecurityError::QuotaExceeded( "User overlay quota exceeded".to_string(), )); } Ok(()) } async fn check_user_overlay_quota(&self, user_id: u32) -> Result { // Get current overlay count for user let current_overlays = self.get_user_overlay_count(user_id).await?; // Get user's overlay quota let quota = self.get_user_overlay_quota(user_id).await?; Ok(current_overlays < quota) } } ``` ### **2. Overlay Validation** ```rust // Validate overlay requests impl UserOverlayManager { async fn validate_overlay_safety( &self, source_path: &Path, target_path: &str, ) -> Result<(), Error> { // Check if source contains potentially dangerous files if self.contains_dangerous_files(source_path).await? { return Err(Error::DangerousFilesDetected( "Source contains potentially dangerous files".to_string(), )); } // Check if target path is critical system path if self.is_critical_system_path(target_path).await? { return Err(Error::CriticalSystemPath( "Cannot overlay critical system paths".to_string(), )); } // Check overlay size limits let source_size = self.calculate_source_size(source_path).await?; if source_size > self.get_overlay_size_limit().await? { return Err(Error::OverlaySizeExceeded( "Overlay size exceeds limits".to_string(), )); } Ok(()) } async fn contains_dangerous_files(&self, path: &Path) -> Result { // List of potentially dangerous file patterns let dangerous_patterns = [ "*.so", "*.dll", "*.exe", "*.sh", "*.py", "*.rb", "*.pl", "*.conf", "*.config", "*.ini", "*.cfg", ]; if path.is_file() { for pattern in &dangerous_patterns { if path.to_string_lossy().ends_with(&pattern[1..]) { return Ok(true); } } } else if path.is_dir() { let mut entries = tokio::fs::read_dir(path).await?; while let Some(entry) = entries.next_entry().await? { if self.contains_dangerous_files(&entry.path()).await? { return Ok(true); } } } Ok(false) } } ``` ## πŸ“Š **Performance Optimization** ### **1. Overlay Caching** ```rust // Cache overlay information impl UserOverlayManager { pub async fn get_cached_overlay( &self, overlay_name: &str, ) -> Result, Error> { // Check cache first if let Some(cached) = self.cache.get_overlay(overlay_name).await? { return Ok(Some(cached)); } // Fetch from store let overlay_info = self.overlay_store .read() .await .get_overlay(overlay_name) .await?; // Cache the result if let Some(ref info) = overlay_info { self.cache.cache_overlay(info).await?; } Ok(overlay_info) } } ``` ### **2. Parallel Overlay Processing** ```rust // Parallel overlay operations impl UserOverlayManager { pub async fn batch_overlays( &self, overlays: Vec, ) -> Result, Error> { let mut tasks = JoinSet::new(); // Spawn parallel overlay tasks for overlay_request in overlays { let overlay_manager = self.clone(); tasks.spawn(async move { overlay_manager .apply_user_overlay( &overlay_request.name, &overlay_request.source_path, &overlay_request.target_path, overlay_request.user_id, overlay_request.session_id, overlay_request.options, ) .await }); } // Collect results let mut results = Vec::new(); while let Some(result) = tasks.join_next().await { results.push(result??); } Ok(results) } } ``` ## πŸ§ͺ **Testing Strategy** ### **1. Unit Tests** ```rust #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_user_overlay_creation() { let overlay_manager = UserOverlayManager::new().await.unwrap(); let result = overlay_manager .apply_user_overlay( "test-overlay", Path::new("/tmp/test-source"), "/usr/local/test", 1000, "session-123".to_string(), OverlayOptions::default(), ) .await .unwrap(); assert!(matches!(result, OverlayResult::Success { .. })); } #[tokio::test] async fn test_user_overlay_reset() { let overlay_manager = UserOverlayManager::new().await.unwrap(); // First create an overlay overlay_manager .apply_user_overlay( "test-overlay", Path::new("/tmp/test-source"), "/usr/local/test", 1000, "session-123".to_string(), OverlayOptions::default(), ) .await .unwrap(); // Then reset it let result = overlay_manager .reset_user_overlay("test-overlay", 1000, "session-123".to_string()) .await .unwrap(); assert!(matches!(result, OverlayResult::Success { .. })); } } ``` ### **2. Integration Tests** ```rust #[tokio::test] async fn test_full_overlay_workflow() { // Set up test environment let test_repo = create_test_repository().await?; // Initialize overlay manager let overlay_manager = UserOverlayManager::new(&test_repo.path()).await?; // Test overlay creation let result = overlay_manager .apply_user_overlay( "test-overlay", Path::new("/tmp/test-source"), "/usr/local/test", 1000, "session-123".to_string(), OverlayOptions::default(), ) .await?; assert!(matches!(result, OverlayResult::Success { .. })); // Verify overlay exists let overlays = overlay_manager.list_user_overlays(Some(1000)).await?; assert!(overlays.iter().any(|o| o.name == "test-overlay")); // Test overlay reset let reset_result = overlay_manager .reset_user_overlay("test-overlay", 1000, "session-123".to_string()) .await?; assert!(matches!(reset_result, OverlayResult::Success { .. })); // Test overlay removal let remove_result = overlay_manager .remove_user_overlay("test-overlay", 1000, "session-123".to_string()) .await?; assert!(matches!(remove_result, OverlayResult::Success { .. })); // Verify overlay removed let overlays_after = overlay_manager.list_user_overlays(Some(1000)).await?; assert!(!overlays_after.iter().any(|o| o.name == "test-overlay")); } ``` ## πŸš€ **Future Enhancements** ### **1. Advanced Overlay Features** - **Overlay templates** and presets - **Overlay inheritance** and composition - **Overlay versioning** and rollback - **Overlay sharing** between users ### **2. Performance Improvements** - **Overlay compression** and optimization - **Background overlay** processing - **Overlay caching** strategies - **Overlay cleanup** automation ### **3. Integration Features** - **External overlay** sources and repositories - **Overlay monitoring** and alerting - **Overlay analytics** and reporting - **Automated overlay** testing and validation --- This architecture provides a solid foundation for implementing production-ready user overlays in apt-ostree, maintaining compatibility with the rpm-ostree ecosystem while providing robust transient filesystem modification capabilities. ## πŸ—ΊοΈ **Implementation Roadmap** ### **Phase 1: Core Foundation** πŸ—οΈ - [ ] **Basic Overlay Structure** - Core overlay manager and filesystem integration - [ ] **OverlayFS Integration** - Linux OverlayFS mounting and management - [ ] **Basic CLI Commands** - Overlay apply, list, reset, and remove commands ### **Phase 2: Advanced Features** πŸš€ - [ ] **User Management** - User-specific overlay isolation and quotas - [ ] **Path Validation** - Safe path checking and system protection - [ ] **Overlay Persistence** - Overlay state management and recovery ### **Phase 3: Production Features** 🎯 - [ ] **Security Integration** - Polkit authorization and privilege management - [ ] **Performance Optimization** - Caching and parallel processing - [ ] **Monitoring & Logging** - Overlay usage tracking and audit trails ### **Phase 4: Integration & Testing** πŸ§ͺ - [ ] **Comprehensive Testing** - Unit, integration, and system tests - [ ] **Performance Testing** - Overlay performance benchmarks - [ ] **Production Deployment** - Production-ready overlay system --- This architecture provides a solid foundation for implementing production-ready user overlays in apt-ostree, maintaining compatibility with the rpm-ostree ecosystem while providing robust transient filesystem modification capabilities.