From f3417c7a1665afa2c37d647bdf13a98b05709ae7 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Tue, 6 Jan 2026 18:15:05 +0100 Subject: [PATCH] deb: default to ephemeral context with local builds --- src/deb/cross.rs | 143 ----------------------------------------- src/deb/ephemeral.rs | 148 +++++++++++++++++++++++++++++++++++++++++++ src/deb/mod.rs | 19 ++---- 3 files changed, 154 insertions(+), 156 deletions(-) create mode 100644 src/deb/ephemeral.rs diff --git a/src/deb/cross.rs b/src/deb/cross.rs index 1ce8f27..2782ca7 100644 --- a/src/deb/cross.rs +++ b/src/deb/cross.rs @@ -1,149 +1,6 @@ use crate::context; -use crate::context::{Context, ContextConfig}; -use directories::ProjectDirs; use std::collections::HashMap; use std::error::Error; -use std::fs; -use std::path::{Path, PathBuf}; -use tar::Archive; -use xz2::read::XzDecoder; - -pub struct EphemeralContextGuard { - previous_context: String, - chroot_path: PathBuf, -} - -impl EphemeralContextGuard { - pub fn new(series: &str) -> Result> { - let current_context_name = context::manager().current_name(); - - // Create a temporary directory for the chroot - let chroot_path_str = context::current().create_temp_dir()?; - let chroot_path = PathBuf::from(chroot_path_str); - - log::debug!( - "Creating new chroot for {} at {}...", - series, - chroot_path.display() - ); - - // Download and extract the chroot tarball - Self::download_and_extract_chroot(series, &chroot_path)?; - - // Switch to an ephemeral context to build the package in the chroot - context::manager().set_current_ephemeral(Context::new(ContextConfig::Unshare { - path: chroot_path.to_string_lossy().to_string(), - parent: Some(current_context_name.clone()), - })); - - Ok(Self { - previous_context: current_context_name, - chroot_path, - }) - } - - fn download_and_extract_chroot( - series: &str, - chroot_path: &PathBuf, - ) -> Result<(), Box> { - // Get project directories for caching - let proj_dirs = ProjectDirs::from("com", "pkh", "pkh") - .ok_or("Could not determine project directories")?; - let cache_dir = proj_dirs.cache_dir(); - fs::create_dir_all(cache_dir)?; - - // Create tarball filename based on series - let tarball_filename = format!("{}-buildd.tar.xz", series); - let tarball_path = cache_dir.join(&tarball_filename); - - // Download tarball if it doesn't exist - if !tarball_path.exists() { - log::debug!("Downloading chroot tarball for {}...", series); - Self::download_chroot_tarball(series, &tarball_path)?; - } else { - log::debug!("Using cached chroot tarball for {}", series); - } - - // Extract tarball to chroot directory - log::debug!("Extracting chroot tarball to {}...", chroot_path.display()); - Self::extract_tarball(&tarball_path, chroot_path)?; - - Ok(()) - } - - fn download_chroot_tarball(series: &str, tarball_path: &Path) -> Result<(), Box> { - // Use mmdebstrap to download the tarball to the cache directory - let status = context::current() - .command("mmdebstrap") - .arg("--variant=buildd") - .arg("--mode=unshare") - .arg("--format=tar") - .arg(series) - .arg(tarball_path.to_string_lossy().to_string()) - .status()?; - - if !status.success() { - return Err(format!("Failed to download chroot tarball for series {}", series).into()); - } - - Ok(()) - } - - fn extract_tarball( - tarball_path: &PathBuf, - chroot_path: &PathBuf, - ) -> Result<(), Box> { - // Create the chroot directory - fs::create_dir_all(chroot_path)?; - - // Open the tarball file - let tarball_file = std::fs::File::open(tarball_path)?; - let xz_decoder = XzDecoder::new(tarball_file); - let mut archive = Archive::new(xz_decoder); - - // Extract all files to the chroot directory - archive.unpack(chroot_path)?; - - Ok(()) - } -} - -impl Drop for EphemeralContextGuard { - fn drop(&mut self) { - log::debug!("Cleaning up ephemeral context..."); - // Reset to normal context - if let Err(e) = context::manager().set_current(&self.previous_context) { - log::error!("Failed to restore context {}: {}", self.previous_context, e); - } - - // Remove chroot directory - // We use the restored context to execute the cleanup command - let result = context::current() - .command("sudo") - .arg("rm") - .arg("-rf") - .arg(&self.chroot_path) - .status(); - - match result { - Ok(status) => { - if !status.success() { - log::error!( - "Failed to remove chroot directory {}", - self.chroot_path.display() - ); - } - } - Err(e) => { - log::error!( - "Failed to execute cleanup command for {}: {}", - self.chroot_path.display(), - e - ); - } - } - } -} /// Set environment variables for cross-compilation pub fn setup_environment( diff --git a/src/deb/ephemeral.rs b/src/deb/ephemeral.rs new file mode 100644 index 0000000..1fb0722 --- /dev/null +++ b/src/deb/ephemeral.rs @@ -0,0 +1,148 @@ +use crate::context; +use crate::context::{Context, ContextConfig}; +use directories::ProjectDirs; +use std::error::Error; +use std::fs; +use std::path::{Path, PathBuf}; +use tar::Archive; +use xz2::read::XzDecoder; + +/// An ephemeral unshare context guard that creates and manages a temporary chroot environment +/// for building packages with unshare permissions. +pub struct EphemeralContextGuard { + previous_context: String, + chroot_path: PathBuf, +} + +impl EphemeralContextGuard { + /// Create a new ephemeral unshare context for the specified series + pub fn new(series: &str) -> Result> { + let current_context_name = context::manager().current_name(); + + // Create a temporary directory for the chroot + let chroot_path_str = context::current().create_temp_dir()?; + let chroot_path = PathBuf::from(chroot_path_str); + + log::debug!( + "Creating new chroot for {} at {}...", + series, + chroot_path.display() + ); + + // Download and extract the chroot tarball + Self::download_and_extract_chroot(series, &chroot_path)?; + + // Switch to an ephemeral context to build the package in the chroot + context::manager().set_current_ephemeral(Context::new(ContextConfig::Unshare { + path: chroot_path.to_string_lossy().to_string(), + parent: Some(current_context_name.clone()), + })); + + Ok(Self { + previous_context: current_context_name, + chroot_path, + }) + } + + fn download_and_extract_chroot( + series: &str, + chroot_path: &PathBuf, + ) -> Result<(), Box> { + // Get project directories for caching + let proj_dirs = ProjectDirs::from("com", "pkh", "pkh") + .ok_or("Could not determine project directories")?; + let cache_dir = proj_dirs.cache_dir(); + fs::create_dir_all(cache_dir)?; + + // Create tarball filename based on series + let tarball_filename = format!("{}-buildd.tar.xz", series); + let tarball_path = cache_dir.join(&tarball_filename); + + // Download tarball if it doesn't exist + if !tarball_path.exists() { + log::debug!("Downloading chroot tarball for {}...", series); + Self::download_chroot_tarball(series, &tarball_path)?; + } else { + log::debug!("Using cached chroot tarball for {}", series); + } + + // Extract tarball to chroot directory + log::debug!("Extracting chroot tarball to {}...", chroot_path.display()); + Self::extract_tarball(&tarball_path, chroot_path)?; + + Ok(()) + } + + fn download_chroot_tarball(series: &str, tarball_path: &Path) -> Result<(), Box> { + // Use mmdebstrap to download the tarball to the cache directory + let status = context::current() + .command("mmdebstrap") + .arg("--variant=buildd") + .arg("--mode=unshare") + .arg("--format=tar") + .arg(series) + .arg(tarball_path.to_string_lossy().to_string()) + .status()?; + + if !status.success() { + return Err(format!("Failed to download chroot tarball for series {}", series).into()); + } + + Ok(()) + } + + fn extract_tarball( + tarball_path: &PathBuf, + chroot_path: &PathBuf, + ) -> Result<(), Box> { + // Create the chroot directory + fs::create_dir_all(chroot_path)?; + + // Open the tarball file + let tarball_file = std::fs::File::open(tarball_path)?; + let xz_decoder = XzDecoder::new(tarball_file); + let mut archive = Archive::new(xz_decoder); + + // Extract all files to the chroot directory + archive.unpack(chroot_path)?; + + Ok(()) + } +} + +impl Drop for EphemeralContextGuard { + fn drop(&mut self) { + log::debug!("Cleaning up ephemeral context..."); + // Reset to normal context + if let Err(e) = context::manager().set_current(&self.previous_context) { + log::error!("Failed to restore context {}: {}", self.previous_context, e); + } + + // Remove chroot directory + // We use the restored context to execute the cleanup command + let result = context::current() + .command("sudo") + .arg("rm") + .arg("-rf") + .arg(&self.chroot_path) + .status(); + + match result { + Ok(status) => { + if !status.success() { + log::error!( + "Failed to remove chroot directory {}", + self.chroot_path.display() + ); + } + } + Err(e) => { + log::error!( + "Failed to execute cleanup command for {}: {}", + self.chroot_path.display(), + e + ); + } + } + } +} diff --git a/src/deb/mod.rs b/src/deb/mod.rs index 45a8c42..0ef0ff8 100644 --- a/src/deb/mod.rs +++ b/src/deb/mod.rs @@ -1,4 +1,5 @@ mod cross; +mod ephemeral; mod local; mod sbuild; @@ -42,21 +43,13 @@ pub fn build_binary_package( let mode = if let Some(m) = mode { m } else { - // For cross-compilation, we use local with an ephemeral context - // created by the cross-compilation handler (see below) - if cross { - BuildMode::Local - } else { - // By default, we use sbuild - BuildMode::Sbuild - } + // By default, we use local build + BuildMode::Local }; - // Specific case: native cross-compilation, we don't allow that - // instead this wraps to an automatic unshare chroot - // using an ephemeral context - let _guard = if cross && mode == BuildMode::Local { - Some(cross::EphemeralContextGuard::new(series)?) + // Create an ephemeral unshare context for all Local builds + let _guard = if mode == BuildMode::Local { + Some(ephemeral::EphemeralContextGuard::new(series)?) } else { None };