//! OSTree integration for apt-ostree compose //! //! This module handles real OSTree operations including: //! - Repository management //! - Commit creation and management //! - Tree operations //! - Reference management //! - Metadata handling use std::path::PathBuf; use std::process::Command; use tokio::fs; use apt_ostree::lib::error::{AptOstreeError, AptOstreeResult}; use crate::treefile::TreefileMetadata; /// OSTree integration manager pub struct OstreeIntegration { repo_path: PathBuf, workdir: PathBuf, } impl OstreeIntegration { /// Create a new OSTree integration instance pub fn new(repo_path: Option<&str>, workdir: &PathBuf) -> AptOstreeResult { let repo_path = if let Some(path) = repo_path { PathBuf::from(path) } else { workdir.join("ostree-repo") }; Ok(Self { repo_path, workdir: workdir.clone(), }) } /// Initialize OSTree repository pub async fn init_repository(&self) -> AptOstreeResult<()> { println!("Initializing OSTree repository at: {}", self.repo_path.display()); // Create repository directory fs::create_dir_all(&self.repo_path).await .map_err(|e| AptOstreeError::System(format!("Failed to create repo dir: {}", e)))?; // Initialize OSTree repository let output = Command::new("ostree") .args([ "init", "--repo", &self.repo_path.to_string_lossy(), "--mode=archive-z2" ]) .output() .map_err(|e| AptOstreeError::System(format!("Failed to run ostree init: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(AptOstreeError::System(format!("ostree init failed: {}", stderr))); } println!("✅ OSTree repository initialized"); Ok(()) } /// Create a new commit from the build directory pub async fn create_commit(&self, metadata: &TreefileMetadata, parent: Option<&str>) -> AptOstreeResult { println!("Creating OSTree commit..."); let build_root = self.workdir.join("build"); // Create commit let mut args = vec![ "commit", "--repo", &self.repo_path.to_string_lossy(), "--branch", &metadata.ref_name, "--tree", &format!("dir={}", build_root.display()), ]; // Add parent if specified if let Some(parent_ref) = parent { args.extend_from_slice(&["--parent", parent_ref]); } // Add metadata if let Some(version) = &metadata.version { args.extend_from_slice(&["--add-metadata-string", &format!("version={}", version)]); } if let Some(description) = &metadata.description { args.extend_from_slice(&["--add-metadata-string", &format!("description={}", description)]); } if let Some(timestamp) = &metadata.timestamp { args.extend_from_slice(&["--add-metadata-string", &format!("timestamp={}", timestamp)]); } // Add commit message let commit_message = format!("apt-ostree compose: {}", metadata.ref_name); args.extend_from_slice(&["--subject", &commit_message]); let output = Command::new("ostree") .args(&args) .output() .map_err(|e| AptOstreeError::System(format!("Failed to run ostree commit: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(AptOstreeError::System(format!("ostree commit failed: {}", stderr))); } // Extract commit hash from output let stdout = String::from_utf8_lossy(&output.stdout); let commit_hash = stdout .lines() .find(|line| line.contains("commit")) .and_then(|line| line.split_whitespace().last()) .unwrap_or("unknown"); println!("✅ OSTree commit created: {}", commit_hash); Ok(commit_hash.to_string()) } /// Update a reference to point to a new commit pub async fn update_reference(&self, ref_name: &str, commit_hash: &str) -> AptOstreeResult<()> { println!("Updating reference {} to commit {}", ref_name, commit_hash); let output = Command::new("ostree") .args([ "reset", "--repo", &self.repo_path.to_string_lossy(), ref_name, commit_hash ]) .output() .map_err(|e| AptOstreeError::System(format!("Failed to run ostree reset: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(AptOstreeError::System(format!("ostree reset failed: {}", stderr))); } println!("✅ Reference {} updated to {}", ref_name, commit_hash); Ok(()) } /// Create a summary file for the repository pub async fn create_summary(&self) -> AptOstreeResult<()> { println!("Creating OSTree repository summary..."); let output = Command::new("ostree") .args([ "summary", "--repo", &self.repo_path.to_string_lossy(), "--update" ]) .output() .map_err(|e| AptOstreeError::System(format!("Failed to run ostree summary: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(AptOstreeError::System(format!("ostree summary failed: {}", stderr))); } println!("✅ Repository summary created"); Ok(()) } /// Generate static delta files for efficient updates pub async fn generate_static_deltas(&self, from_ref: Option<&str>, to_ref: &str) -> AptOstreeResult<()> { println!("Generating static delta from {:?} to {}", from_ref, to_ref); let mut args = vec![ "static-delta", "generate", "--repo", &self.repo_path.to_string_lossy(), "--to", to_ref, ]; if let Some(from) = from_ref { args.extend_from_slice(&["--from", from]); } let output = Command::new("ostree") .args(&args) .output() .map_err(|e| AptOstreeError::System(format!("Failed to run ostree static-delta: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); println!("Warning: static delta generation had issues: {}", stderr); } else { println!("✅ Static delta generated"); } Ok(()) } /// Export repository to a tar archive pub async fn export_archive(&self, output_path: &str, ref_name: &str) -> AptOstreeResult<()> { println!("Exporting OSTree repository to: {}", output_path); let output = Command::new("ostree") .args([ "archive", "--repo", &self.repo_path.to_string_lossy(), "--ref", ref_name, output_path ]) .output() .map_err(|e| AptOstreeError::System(format!("Failed to run ostree archive: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(AptOstreeError::System(format!("ostree archive failed: {}", stderr))); } println!("✅ Repository exported to {}", output_path); Ok(()) } /// Get repository information pub async fn get_repo_info(&self) -> AptOstreeResult { let output = Command::new("ostree") .args([ "refs", "--repo", &self.repo_path.to_string_lossy() ]) .output() .map_err(|e| AptOstreeError::System(format!("Failed to run ostree refs: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(AptOstreeError::System(format!("ostree refs failed: {}", stderr))); } let stdout = String::from_utf8_lossy(&output.stdout); Ok(stdout.to_string()) } /// Check if a reference exists pub async fn reference_exists(&self, ref_name: &str) -> AptOstreeResult { let output = Command::new("ostree") .args([ "rev-parse", "--repo", &self.repo_path.to_string_lossy(), ref_name ]) .output(); match output { Ok(output) => Ok(output.status.success()), Err(_) => Ok(false), } } /// Get the commit hash for a reference pub async fn get_commit_hash(&self, ref_name: &str) -> AptOstreeResult> { let output = Command::new("ostree") .args([ "rev-parse", "--repo", &self.repo_path.to_string_lossy(), ref_name ]) .output(); match output { Ok(output) if output.status.success() => { let stdout = String::from_utf8_lossy(&output.stdout); Ok(Some(stdout.trim().to_string())) } _ => Ok(None), } } /// List all references in the repository pub async fn list_references(&self) -> AptOstreeResult> { let output = Command::new("ostree") .args([ "refs", "--repo", &self.repo_path.to_string_lossy() ]) .output() .map_err(|e| AptOstreeError::System(format!("Failed to run ostree refs: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(AptOstreeError::System(format!("ostree refs failed: {}", stderr))); } let stdout = String::from_utf8_lossy(&output.stdout); let refs: Vec = stdout .lines() .map(|line| line.trim().to_string()) .filter(|line| !line.is_empty()) .collect(); Ok(refs) } /// Clean up old commits and objects pub async fn cleanup_repository(&self, keep_refs: &[String]) -> AptOstreeResult<()> { println!("Cleaning up OSTree repository..."); // Get all references let all_refs = self.list_references().await?; // Find references to remove (those not in keep_refs) let refs_to_remove: Vec = all_refs .into_iter() .filter(|ref_name| !keep_refs.contains(ref_name)) .collect(); for ref_name in refs_to_remove { println!("Removing reference: {}", ref_name); let output = Command::new("ostree") .args([ "refs", "--delete", "--repo", &self.repo_path.to_string_lossy(), &ref_name ]) .output(); if let Ok(output) = output { if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); println!("Warning: failed to remove reference {}: {}", ref_name, stderr); } } } // Run garbage collection let output = Command::new("ostree") .args([ "refs", "--repo", &self.repo_path.to_string_lossy(), "--gc" ]) .output() .map_err(|e| AptOstreeError::System(format!("Failed to run ostree gc: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); println!("Warning: garbage collection had issues: {}", stderr); } println!("✅ Repository cleanup completed"); Ok(()) } }