From 7d79124dbbfb65b08321a6f3cbe2acc86d212d36 Mon Sep 17 00:00:00 2001 From: jbtrystram Date: Fri, 28 Nov 2025 18:08:34 +0100 Subject: [PATCH] install/bootupd: chroot to deployment When `--src-imgref` is passed, the deployed systemd does not match the running environnement. In this case, let's chroot into the deployment before calling bootupd. This makes sure we are using the binaries shipped in the image (and relevant config files such as grub fragements). We could do that in all cases but i kept it behind the `--src-imgref` option since when using the target container as the buildroot it will have no impact, and we expect this scenario to be the most common. In CoreOS we have a specific test that checks if the bootloader was installed with the `grub2-install` of the image. Fixes https://github.com/bootc-dev/bootc/issues/1559 Also see https://github.com/bootc-dev/bootc/issues/1455 Signed-off-by: jbtrystram --- crates/lib/src/bootloader.rs | 51 +++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/crates/lib/src/bootloader.rs b/crates/lib/src/bootloader.rs index 6126cef9e..57c0f16f6 100644 --- a/crates/lib/src/bootloader.rs +++ b/crates/lib/src/bootloader.rs @@ -8,6 +8,7 @@ use fn_error_context::context; use bootc_blockdev::{Partition, PartitionTable}; use bootc_mount as mount; +use rustix::mount::UnmountFlags; use crate::bootc_composefs::boot::mount_esp; use crate::{discoverable_partition_specification, utils}; @@ -50,22 +51,60 @@ pub(crate) fn install_via_bootupd( // bootc defaults to only targeting the platform boot method. let bootupd_opts = (!configopts.generic_image).then_some(["--update-firmware", "--auto"]); - let abs_deployment_path = deployment_path.map(|v| rootfs.join(v)); - let src_root_arg = if let Some(p) = abs_deployment_path.as_deref() { - vec!["--src-root", p.as_str()] + let abs_deployment_path = deployment_path.map(|deploy| rootfs.join(deploy)); + // When not running inside the target container (through `--src-imgref`) we chroot + // into the deployment before running bootupd. This makes sure we use binaries + // from the target image rather than the buildroot + let bind_mount_dirs = ["/dev", "/run", "/proc", "/sys"]; + let chroot_args = if let Some(target_root) = abs_deployment_path.as_deref() { + tracing::debug!("Setting up bind-mounts before chrooting to the target deployment"); + for src in bind_mount_dirs { + let dest = target_root + // joining an absolute path + // makes it replace self, so we strip the prefix + .join_os(src.strip_prefix("/").unwrap()); + tracing::debug!("bind mounting {}", dest.display()); + rustix::mount::mount_bind_recursive(src, dest)?; + } + // Append the `bootupctl` command, it will be passed as + // an argument to chroot + vec![target_root.as_str(), "bootupctl"] } else { vec![] }; + let devpath = device.path(); println!("Installing bootloader via bootupd"); - Command::new("bootupctl") + let mut bootupctl = if abs_deployment_path.is_some() { + Command::new("chroot") + } else { + Command::new("bootupctl") + }; + let install_result = bootupctl + .args(chroot_args) .args(["backend", "install", "--write-uuid"]) .args(verbose) .args(bootupd_opts.iter().copied().flatten()) - .args(src_root_arg) .args(["--device", devpath.as_str(), rootfs.as_str()]) .log_debug() - .run_inherited_with_cmd_context() + .run_inherited_with_cmd_context(); + + // Clean up the mounts after ourselves + if let Some(target_root) = abs_deployment_path { + let mut unmount_res = Ok(()); + for dir in bind_mount_dirs { + let mount = target_root + .join(dir.strip_prefix("/").unwrap()) + .into_std_path_buf(); + if let Err(e) = rustix::mount::unmount(&mount, UnmountFlags::DETACH) { + tracing::warn!("Error unmounting {}: {e}", mount.display()); + unmount_res = Err(e.into()); + } + } + install_result.and(unmount_res) + } else { + install_result + } } #[context("Installing bootloader")]