diff --git a/src/context/unshare.rs b/src/context/unshare.rs index 8b80c6a..89ac3de 100644 --- a/src/context/unshare.rs +++ b/src/context/unshare.rs @@ -207,11 +207,22 @@ impl UnshareDriver { cmd.arg("-w").arg(dir); } - cmd.arg("--").arg("bash").arg("-c").arg(format!( - "mount -t proc proc /proc; mkdir /dev/pts; mount -t devpts devpts /dev/pts; touch /dev/ptmx; mount --bind /dev/pts/ptmx /dev/ptmx; {} {}", - program, - args.iter().map(|a| format!("\"{a}\"")).collect::>().join(" ") - )); + // Build the bash command: set up /dev/pts and run the program + // /proc should already be bind-mounted from the host before entering the namespace + let program_args = args + .iter() + .map(|a| format!("\"{a}\"")) + .collect::>() + .join(" "); + + cmd.arg("--") + .arg("bash") + .arg("-c") + .arg(format!( + "mkdir -p /dev/pts; mount -t devpts devpts /dev/pts 2>/dev/null || true; touch /dev/ptmx; mount --bind /dev/pts/ptmx /dev/ptmx 2>/dev/null || true; {} {}", + program, + program_args + )); cmd } diff --git a/src/deb/ephemeral.rs b/src/deb/ephemeral.rs index 58ebc00..2efe84b 100644 --- a/src/deb/ephemeral.rs +++ b/src/deb/ephemeral.rs @@ -134,7 +134,12 @@ impl EphemeralContextGuard { // Create device nodes in the chroot log::debug!("Creating device nodes in chroot..."); - Self::create_device_nodes(chroot_path, ctx_for_devices)?; + Self::create_device_nodes(chroot_path, ctx_for_devices.clone())?; + + // Bind mount /proc from host into chroot (before entering unshare namespace) + // This allows /proc to work in containers where mounting inside unshare fails + log::debug!("Bind-mounting /proc into chroot..."); + Self::bind_mount_proc(chroot_path, ctx_for_devices)?; Ok(()) } @@ -286,6 +291,43 @@ impl EphemeralContextGuard { Ok(()) } + /// Bind mount /proc from host into the chroot + /// This is done before entering the unshare namespace, so it works in containers + fn bind_mount_proc( + chroot_path: &Path, + ctx: Arc, + ) -> Result<(), Box> { + let proc_path = chroot_path.join("proc"); + + // Ensure /proc directory exists in chroot + fs::create_dir_all(&proc_path)?; + + // Check if we're running as root + let is_root = crate::utils::root::is_root()?; + + // Bind mount host's /proc into chroot (with sudo if not root) + let mut cmd = ctx.command(if is_root { "mount" } else { "sudo" }); + if !is_root { + cmd.arg("mount"); + } + let status = cmd + .arg("--bind") + .arg("/proc") + .arg(proc_path.to_string_lossy().to_string()) + .status()?; + + if !status.success() { + log::warn!( + "Could not bind-mount /proc into chroot at {}. Some packages may not install correctly.", + proc_path.display() + ); + } else { + log::debug!("Bind-mounted /proc into chroot at {}", proc_path.display()); + } + + Ok(()) + } + /// Mark the build as successful, which will trigger chroot cleanup on drop pub fn mark_build_successful(&mut self) { self.build_succeeded = true; @@ -310,6 +352,18 @@ impl Drop for EphemeralContextGuard { // Check if we're running as root to avoid unnecessary sudo let is_root = crate::utils::root::is_root().unwrap_or(false); + // Unmount /proc from chroot before removing (ignore errors) + let proc_path = self.chroot_path.join("proc"); + let _ = if is_root { + self.base_ctx.command("umount").arg(&proc_path).status() + } else { + self.base_ctx + .command("sudo") + .arg("umount") + .arg(&proc_path) + .status() + }; + let result = if is_root { self.base_ctx .command("rm")