Skip to content

Commit 38d8381

Browse files
committed
install/bootupd: chroot to deployment
When `--src-imgref` is passed, the deployed systemd does not match the running environnement. In this case, let's run bootupd from inside the deployment. This makes sure we are using the binaries shipped in the image (and relevant config files such as grub fragements). We use bwrap to set up the chroot for a easier handling of the API filesystems. 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 #1559 Also see #1455 Assisted-by: OpenCode (Opus 4.5) Signed-off-by: jbtrystram <jbtrystram@redhat.com>
1 parent b901498 commit 38d8381

4 files changed

Lines changed: 154 additions & 20 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ jobs:
8989
# Install tests
9090
sudo bootc-integration-tests install-alongside localhost/bootc-install
9191
92+
# inspect system state after the install tests.
93+
sudo lsblk
94+
sudo mount
95+
9296
# system-reinstall-bootc tests
9397
cargo build --release -p system-reinstall-bootc
9498

crates/lib/src/bootloader.rs

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::fs::create_dir_all;
22
use std::process::Command;
33

44
use anyhow::{Context, Result, anyhow, bail};
5-
use bootc_utils::CommandRunExt;
5+
use bootc_utils::{BwrapCmd, CommandRunExt};
66
use camino::Utf8Path;
77
use cap_std_ext::cap_std::fs::Dir;
88
use cap_std_ext::dirext::CapStdExtDirExt;
@@ -91,22 +91,67 @@ pub(crate) fn install_via_bootupd(
9191
// bootc defaults to only targeting the platform boot method.
9292
let bootupd_opts = (!configopts.generic_image).then_some(["--update-firmware", "--auto"]);
9393

94-
let abs_deployment_path = deployment_path.map(|v| rootfs.join(v));
95-
let src_root_arg = if let Some(p) = abs_deployment_path.as_deref() {
96-
vec!["--src-root", p.as_str()]
94+
// When not running inside the target container (through `--src-imgref`) we use
95+
// will bwrap as a chroot to run bootupctl from the deployment.
96+
// This makes sure we use binaries from the target image rather than the buildroot.
97+
// In that case, the target rootfs is replaced with `/` because this is just used by
98+
// bootupd to find the backing device.
99+
let rootfs_mount = if deployment_path.is_none() {
100+
rootfs.as_str()
97101
} else {
98-
vec![]
102+
"/"
99103
};
100-
let devpath = device.path();
104+
101105
println!("Installing bootloader via bootupd");
102-
Command::new("bootupctl")
103-
.args(["backend", "install", "--write-uuid"])
104-
.args(verbose)
105-
.args(bootupd_opts.iter().copied().flatten())
106-
.args(src_root_arg)
107-
.args(["--device", devpath.as_str(), rootfs.as_str()])
108-
.log_debug()
109-
.run_inherited_with_cmd_context()
106+
107+
// Build the bootupctl arguments
108+
let mut bootupd_args: Vec<&str> = vec!["backend", "install", "--write-uuid"];
109+
if let Some(v) = verbose {
110+
bootupd_args.push(v);
111+
}
112+
113+
if let Some(ref opts) = bootupd_opts {
114+
bootupd_args.extend(opts.iter().copied());
115+
}
116+
bootupd_args.extend(["--device", device.path().as_str(), rootfs_mount]);
117+
118+
// Run inside a bwrap container. It takes care of mounting and creating
119+
// the necessary API filesystems in the target deployment and acts as
120+
// a nicer `chroot`.
121+
if let Some(deploy) = deployment_path {
122+
let target_root = rootfs.join(deploy);
123+
let boot_path = rootfs.join("boot");
124+
125+
tracing::debug!("Running bootupctl via bwrap in {}", target_root);
126+
127+
// Prepend "bootupctl" to the args for bwrap
128+
let mut bwrap_args = vec!["bootupctl"];
129+
bwrap_args.extend(bootupd_args);
130+
131+
let mut cmd = BwrapCmd::new(target_root.as_str())
132+
// Bind mount /boot from the physical target root so bootupctl can find
133+
// the boot partition and install the bootloader there
134+
.bind(boot_path.as_str(), "/boot")
135+
// Bind the target block device inside the bwrap container so bootupctl can access it
136+
.bind_device(device.path().as_str());
137+
138+
// Also bind all partitions of the tafet block device
139+
for partition in &device.partitions {
140+
cmd = cmd.bind_device(&partition.node);
141+
}
142+
// // TODO : is it needed ?
143+
// cmd.setenv(
144+
// "PATH",
145+
// "/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin",
146+
// )
147+
cmd.run(bwrap_args)
148+
} else {
149+
// Running directly without chroot
150+
Command::new("bootupctl")
151+
.args(&bootupd_args)
152+
.log_debug()
153+
.run_inherited_with_cmd_context()
154+
}
110155
}
111156

112157
#[context("Installing bootloader")]

crates/utils/src/bwrap.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/// Builder for running commands inside a target os tree using bubblewrap (bwrap).
2+
use std::ffi::OsStr;
3+
use std::process::Command;
4+
5+
use anyhow::Result;
6+
7+
use crate::CommandRunExt;
8+
9+
/// Builder for running commands inside a target directory using bwrap.
10+
#[derive(Debug, Default)]
11+
pub struct BwrapCmd<'a> {
12+
/// The target directory to use as root for the container
13+
chroot_path: &'a str,
14+
/// Bind mounts in format (source, target)
15+
bind_mounts: Vec<(&'a str, &'a str)>,
16+
/// Device nodes to bind into the container
17+
devices: Vec<&'a str>,
18+
/// Environment variables to set
19+
env_vars: Vec<(&'a str, &'a str)>,
20+
}
21+
22+
impl<'a> BwrapCmd<'a> {
23+
/// Create a new BwrapCmd builder with the given root directory.
24+
pub fn new(path: &'a str) -> Self {
25+
Self {
26+
chroot_path: path,
27+
..Default::default()
28+
}
29+
}
30+
31+
/// Add a bind mount from source to target inside the container.
32+
pub fn bind(mut self, source: &'a str, target: &'a str) -> Self {
33+
self.bind_mounts.push((source, target));
34+
self
35+
}
36+
37+
/// Bind a device node into the container.
38+
pub fn bind_device(mut self, device: &'a str) -> Self {
39+
self.devices.push(device);
40+
self
41+
}
42+
43+
/// Set an environment variable for the command.
44+
pub fn setenv(mut self, key: &'a str, value: &'a str) -> Self {
45+
self.env_vars.push((key, value));
46+
self
47+
}
48+
49+
/// Run the specified command inside the container.
50+
pub fn run<S: AsRef<OsStr>>(self, args: impl IntoIterator<Item = S>) -> Result<()> {
51+
let mut cmd = Command::new("bwrap");
52+
53+
// Bind the root filesystem
54+
cmd.args(["--bind", self.chroot_path, "/"]);
55+
56+
// Setup API filesystems
57+
// See https://systemd.io/API_FILE_SYSTEMS/
58+
cmd.args(["--proc", "/proc"]);
59+
cmd.args(["--dev", "/dev"]);
60+
cmd.args(["--ro-bind", "/sys", "/sys"]);
61+
62+
// Add bind mounts
63+
for (source, target) in &self.bind_mounts {
64+
cmd.args(["--bind", source, target]);
65+
}
66+
67+
// Add device bind mounts
68+
for device in self.devices {
69+
cmd.args(["--dev-bind", device, device]);
70+
}
71+
72+
// Add environment variables
73+
for (key, value) in &self.env_vars {
74+
cmd.args(["--setenv", key, value]);
75+
}
76+
77+
// Command to run
78+
cmd.arg("--");
79+
cmd.args(args);
80+
81+
cmd.log_debug().run_inherited_with_cmd_context()
82+
}
83+
}

crates/utils/src/lib.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@
22
//! things here that only depend on the standard library and
33
//! "core" crates.
44
//!
5+
mod bwrap;
6+
pub use bwrap::*;
57
mod command;
68
pub use command::*;
7-
mod path;
8-
pub use path::*;
99
mod iterators;
1010
pub use iterators::*;
11-
mod timestamp;
12-
pub use timestamp::*;
13-
mod tracing_util;
14-
pub use tracing_util::*;
11+
mod path;
12+
pub use path::*;
1513
/// Re-execute the current process
1614
pub mod reexec;
1715
mod result_ext;
1816
pub use result_ext::*;
17+
mod timestamp;
18+
pub use timestamp::*;
19+
mod tracing_util;
20+
pub use tracing_util::*;
1921

2022
/// The name of our binary
2123
pub const NAME: &str = "bootc";

0 commit comments

Comments
 (0)