deb: default to ephemeral context with local builds
Some checks failed
CI / build (push) Failing after 1m28s

This commit is contained in:
2026-01-06 18:15:05 +01:00
parent 1c9f6cccd2
commit f3417c7a16
3 changed files with 154 additions and 156 deletions

View File

@@ -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<Self, Box<dyn Error>> {
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<dyn Error>> {
// 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<dyn Error>> {
// 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<dyn Error>> {
// 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(

148
src/deb/ephemeral.rs Normal file
View File

@@ -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<Self, Box<dyn Error>> {
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<dyn Error>> {
// 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<dyn Error>> {
// 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<dyn Error>> {
// 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
);
}
}
}
}

View File

@@ -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
};