Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion crates/lib/src/bootloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ const SYSTEMD_KEY_DIR: &str = "loader/keys";
/// bootc does not use the entry-token at all.
const KERNEL_INSTALL_CONF_ROOT: &str = "/tmp";

/// First systemd release whose `bootctl install` accepts `--random-seed`.
/// See: <https://www.freedesktop.org/software/systemd/man/latest/bootctl.html>
const BOOTCTL_RANDOM_SEED_MIN_VERSION: u32 = 257;

/// Mount the first ESP found among backing devices at /boot/efi.
///
/// This is used by the install-alongside path to clean stale bootloader
Expand Down Expand Up @@ -256,7 +260,16 @@ pub(crate) fn install_systemd_boot(
];

if configopts.generic_image {
bootctl_args.extend(["--random-seed", "no", "--no-variables"]);
bootctl_args.push("--no-variables");
// `--random-seed` was only added to `bootctl install` in systemd 257.
let systemd_version = bootctl_systemd_version()?;
if systemd_version >= BOOTCTL_RANDOM_SEED_MIN_VERSION {
bootctl_args.extend(["--random-seed", "no"]);
} else {
tracing::debug!(
"Skipping --random-seed: requires systemd >= {BOOTCTL_RANDOM_SEED_MIN_VERSION}, found {systemd_version}"
);
}
}

Command::new("bootctl")
Expand Down Expand Up @@ -312,6 +325,24 @@ pub(crate) fn install_systemd_boot(
Ok(())
}

#[context("Querying bootctl version")]
fn bootctl_systemd_version() -> Result<u32> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checks the systemd version in the source image, which might be different than the target image. At this point, we'd have already generated the EROFS. So I think it'd be better to chroot into the mounted EROFS and then get the version

@yeetypete yeetypete Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Johan-Liebert1 thanks for the review. Currently bootctl install runs using the the version in the source image. Should we change this to use the target image version as well? Otherwise we end up gating the flag for the wrong systemd version (we gate for the target when the source version is used).

In my case these were the same because I was running bootc install to-disk inside the container I was deploying but I guess there are probably some implications I'm not aware of with this change.

@Johan-Liebert1 Johan-Liebert1 Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, in the ostree case we chroot into the new deployment and invoke bootupd from there. I think that should be a separate effort, unrealted to this PR

let out = Command::new("bootctl").arg("--version").run_get_string()?;
parse_systemd_version(&out)
}

/// Parse the systemd major version from `bootctl --version` output, whose first
/// line looks like `systemd 259 (259.5-0ubuntu3)`.
fn parse_systemd_version(output: &str) -> Result<u32> {
output
.split_whitespace()
.nth(1)
.and_then(|s| s.parse::<u32>().ok())
.ok_or_else(|| {
anyhow!("Could not parse systemd version from bootctl --version: {output:?}")
})
}

#[context("Installing bootloader using zipl")]
pub(crate) fn install_via_zipl(device: &bootc_blockdev::Device, boot_uuid: &str) -> Result<()> {
// Identify the target boot partition from UUID
Expand Down Expand Up @@ -389,3 +420,31 @@ pub(crate) fn install_via_zipl(device: &bootc_blockdev::Device, boot_uuid: &str)
.log_debug()
.run_inherited_with_cmd_context()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_systemd_version() {
// The first line of `bootctl --version`. the trailing feature line is ignored.
let cases = [
("systemd 259 (259.5-0ubuntu3)", 259),
("systemd 257 (257-26.el10-g1d19ad5)", 257),
("systemd 255 (255.4-1ubuntu8.16)", 255),
];
for (input, expected) in cases {
assert_eq!(
parse_systemd_version(input).unwrap(),
expected,
"input: {input:?}"
);
}
for bad in ["", "systemd", "not a version string"] {
assert!(
parse_systemd_version(bad).is_err(),
"should reject: {bad:?}"
);
}
}
}