From 1e29eb05c56b5def69986477fe381dcb3e006c6d Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 30 Mar 2026 19:33:12 +0000 Subject: [PATCH] install: Add discoverable-partitions config option Right now we default to DPS for composefs + systemd-boot. In Fedora 43+, GRUB has the `bli` module and supports this, so it *can* be used there. Make this configurable (mainly intended for base image builders) so that those with new enough GRUB can flip it on by default. We had a hacky thing here that removed the auto-injected `root=` arg if we detected composefs + sdboot; that can now instead flip on this flag, and then we ensure we don't inject it at all. Assisted-by: OpenCode (Claude Opus 4) Signed-off-by: Colin Walters --- contrib/packaging/install-rpm-and-setup | 11 +++++ crates/lib/src/bootc_composefs/boot.rs | 8 +--- crates/lib/src/install/baseline.rs | 41 +++++++++++++++++-- crates/lib/src/install/config.rs | 39 ++++++++++++++++++ docs/src/man/bootc-install-config.5.md | 13 ++++++ docs/src/man/bootc-install-to-disk.8.md | 28 +++++++++---- .../booted/readonly/052-test-bli-detection.nu | 30 ++++++++++++++ 7 files changed, 151 insertions(+), 19 deletions(-) create mode 100644 tmt/tests/booted/readonly/052-test-bli-detection.nu diff --git a/contrib/packaging/install-rpm-and-setup b/contrib/packaging/install-rpm-and-setup index 91dab9796..02531bdb4 100755 --- a/contrib/packaging/install-rpm-and-setup +++ b/contrib/packaging/install-rpm-and-setup @@ -21,5 +21,16 @@ env DRACUT_NO_XATTR=1 dracut --add bootc -vf /usr/lib/modules/$kver/initramfs.im # tests to know we're doing upstream CI. touch /usr/lib/.bootc-dev-stamp +# Fedora 43+ ships a GRUB with the BLI module, so enable DPS +# auto-discovery for root. This must run after our RPM is installed +# since older bootc doesn't recognize the discoverable-partitions key. +. /usr/lib/os-release +if [ "${ID}" = "fedora" ] && [ "${VERSION_ID}" -ge 43 ] 2>/dev/null; then + cat > /usr/lib/bootc/install/20-discoverable-partitions.toml <<'EOF' +[install] +discoverable-partitions = true +EOF +fi + # Workaround for https://github.com/bootc-dev/bootc/issues/1546 rm -rf /root/buildinfo /var/roothome/buildinfo diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index 4e945c065..c343fcacb 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -67,7 +67,7 @@ use std::io::Write; use std::path::Path; use anyhow::{Context, Result, anyhow, bail}; -use bootc_kernel_cmdline::utf8::{Cmdline, Parameter, ParameterKey}; +use bootc_kernel_cmdline::utf8::{Cmdline, Parameter}; use bootc_mount::tempmount::TempMount; use camino::{Utf8Path, Utf8PathBuf}; use cap_std_ext::{ @@ -600,12 +600,6 @@ pub(crate) fn setup_composefs_bls_boot( } }; - // Remove "root=" from kernel cmdline as systemd-auto-gpt-generator should use DPS - // UUID - if bootloader == Bootloader::Systemd { - cmdline_refs.remove(&ParameterKey::from("root")); - } - let is_upgrade = matches!(setup_type, BootSetupType::Upgrade(..)); let current_root = if is_upgrade { diff --git a/crates/lib/src/install/baseline.rs b/crates/lib/src/install/baseline.rs index 9b393b467..8a32e98c9 100644 --- a/crates/lib/src/install/baseline.rs +++ b/crates/lib/src/install/baseline.rs @@ -34,6 +34,31 @@ use bootc_kernel_cmdline::utf8::Cmdline; #[cfg(feature = "install-to-disk")] use bootc_mount::is_mounted_in_pid1_mountns; +/// Check whether DPS auto-discovery is enabled. When `true`, +/// `root=UUID=` is omitted and `systemd-gpt-auto-generator` discovers +/// the root partition via its DPS type GUID instead. +/// +/// Defaults to `true` for systemd-boot (which always implements BLI). +/// For GRUB the default is `false` because we cannot know at install +/// time whether the GRUB build includes the `bli` module — the module +/// is baked into the signed EFI binary with no external manifest. +/// Distros shipping a BLI-capable GRUB should set +/// `discoverable-partitions = true` in their install config. +#[cfg(feature = "install-to-disk")] +fn use_discoverable_partitions(state: &State) -> bool { + // Explicit config takes priority + if let Some(ref config) = state.install_config { + if let Some(v) = config.discoverable_partitions { + return v; + } + } + // systemd-boot always supports BLI + matches!( + state.config_opts.bootloader, + Some(crate::spec::Bootloader::Systemd) + ) +} + // This ensures we end up under 512 to be small-sized. pub(crate) const BOOTPN_SIZE_MB: u32 = 510; pub(crate) const EFIPN_SIZE_MB: u32 = 512; @@ -226,10 +251,15 @@ pub(crate) fn install_create_rootfs( }; let serial = device.serial.as_deref().unwrap_or(""); let model = device.model.as_deref().unwrap_or(""); + let discoverable = use_discoverable_partitions(state); println!("Block setup: {block_setup}"); println!(" Size: {}", device.size); println!(" Serial: {serial}"); println!(" Model: {model}"); + println!( + " Partitions: {}", + if discoverable { "Discoverable" } else { "UUID" } + ); let root_size = opts .root_size @@ -415,7 +445,6 @@ pub(crate) fn install_create_rootfs( opts.wipe, mkfs_options.iter().copied(), )?; - let rootarg = format!("root=UUID={root_uuid}"); let bootsrc = boot_uuid.as_ref().map(|uuid| format!("UUID={uuid}")); let bootarg = bootsrc.as_deref().map(|bootsrc| format!("boot={bootsrc}")); let boot = bootsrc.map(|bootsrc| MountSpec { @@ -434,8 +463,14 @@ pub(crate) fn install_create_rootfs( } } - // Add root= and rw argument - kargs.extend(&Cmdline::from(format!("{rootarg} {RW_KARG}"))); + // When discoverable-partitions is enabled, omit root= so that + // systemd-gpt-auto-generator discovers root by its DPS type GUID. + if discoverable { + kargs.extend(&Cmdline::from(RW_KARG)); + } else { + let rootarg = format!("root=UUID={root_uuid}"); + kargs.extend(&Cmdline::from(format!("{rootarg} {RW_KARG}"))); + } // Add boot= argument if present if let Some(bootarg) = bootarg { diff --git a/crates/lib/src/install/config.rs b/crates/lib/src/install/config.rs index a5194eb9c..b68aa2d0f 100644 --- a/crates/lib/src/install/config.rs +++ b/crates/lib/src/install/config.rs @@ -122,6 +122,13 @@ pub(crate) struct InstallConfiguration { pub(crate) bootupd: Option, /// Bootloader to use (grub, systemd, none) pub(crate) bootloader: Option, + /// Use the Discoverable Partitions Specification for root partition + /// discovery. When true, the `root=` kernel argument is omitted + /// and `systemd-gpt-auto-generator` discovers root via its DPS + /// type GUID. Requires the bootloader to implement the Boot Loader + /// Interface (systemd-boot always does, GRUB needs the `bli` module). + /// Defaults to false for broad compatibility. + pub(crate) discoverable_partitions: Option, } fn merge_basic(s: &mut Option, o: Option, _env: &EnvProperties) { @@ -206,6 +213,11 @@ impl Mergeable for InstallConfiguration { merge_basic(&mut self.boot_mount_spec, other.boot_mount_spec, env); self.bootupd.merge(other.bootupd, env); merge_basic(&mut self.bootloader, other.bootloader, env); + merge_basic( + &mut self.discoverable_partitions, + other.discoverable_partitions, + env, + ); if let Some(other_kargs) = other.kargs { self.kargs .get_or_insert_with(Default::default) @@ -901,3 +913,30 @@ bootloader = "grub" install.merge(other, &env); assert_eq!(install.bootloader, Some(Bootloader::None)); } + +#[test] +fn test_parse_discoverable_partitions() { + let c: InstallConfigurationToplevel = toml::from_str( + r##"[install] +discoverable-partitions = true +"##, + ) + .unwrap(); + assert_eq!(c.install.unwrap().discoverable_partitions, Some(true)); + + let c: InstallConfigurationToplevel = toml::from_str( + r##"[install] +discoverable-partitions = false +"##, + ) + .unwrap(); + assert_eq!(c.install.unwrap().discoverable_partitions, Some(false)); + + let c: InstallConfigurationToplevel = toml::from_str( + r##"[install] +root-fs-type = "xfs" +"##, + ) + .unwrap(); + assert_eq!(c.install.unwrap().discoverable_partitions, None); +} diff --git a/docs/src/man/bootc-install-config.5.md b/docs/src/man/bootc-install-config.5.md index a6f5156c5..2840a5422 100644 --- a/docs/src/man/bootc-install-config.5.md +++ b/docs/src/man/bootc-install-config.5.md @@ -33,6 +33,12 @@ The `install` section supports these subfields: - `boot-mount-spec`: A string specifying the /boot filesystem mount specification. If not provided and /boot is a separate mount, its UUID will be used. An empty string signals to omit boot mount kargs entirely. +- `discoverable-partitions`: Boolean. When `true`, root discovery uses the + Discoverable Partitions Specification via `systemd-gpt-auto-generator` and + the `root=` kernel argument is omitted. This requires the bootloader to + implement the Boot Loader Interface (BLI); systemd-boot always does, GRUB + needs the `bli` module (available in newer builds). Defaults to `true` + when using systemd-boot, `false` otherwise. # filesystem @@ -78,6 +84,13 @@ boot-mount-spec = "UUID=abcd-1234" bls-append-except-default = 'grub_users=""' ``` +Enable DPS auto-discovery for root (requires a BLI-capable bootloader): + +```toml +[install] +discoverable-partitions = true +``` + # SEE ALSO **bootc(1)** diff --git a/docs/src/man/bootc-install-to-disk.8.md b/docs/src/man/bootc-install-to-disk.8.md index 55580ed7b..5b4a7bd39 100644 --- a/docs/src/man/bootc-install-to-disk.8.md +++ b/docs/src/man/bootc-install-to-disk.8.md @@ -41,15 +41,25 @@ use `install to-filesystem` if you need precise control over the partition layou ### Root filesystem discovery -Note that by default when used with "type 1" bootloader setups (i.e. non-UKI) -a kernel argument `root=UUID=` is injected by default. -This provides compatibility with existing initramfs implementations. - -When used with the composefs backend and UKIs, it's recommended that -a bootloader implementing the DPS specification is used and that the root -partition is auto-discovered. In this configuration, `systemd-gpt-auto-generator` -in the initramfs will automatically find and mount the root partition based on -its DPS type GUID, without requiring an explicit `root=` kernel argument. +The root partition can be discovered at boot time in two ways: + +- **UUID mode** (default): A kernel argument `root=UUID=` is + injected, providing broad compatibility with all initramfs + implementations and bootloaders. + +- **DPS auto-discovery**: The `root=` kernel argument is omitted + entirely. `systemd-gpt-auto-generator` in the initramfs discovers + the root partition by its DPS type GUID. This enables transparent + block-layer changes (such as adding LUKS encryption) without + updating kernel arguments. DPS auto-discovery requires the + bootloader to implement the Boot Loader Interface (BLI). + systemd-boot always supports this; GRUB supports it only with + newer builds that include the `bli` module. + +When using systemd-boot, DPS auto-discovery is enabled by default. +For GRUB, container base images that ship a BLI-capable build should +set `discoverable-partitions = true` in their install configuration +(see **bootc-install-config**(5)). # OPTIONS diff --git a/tmt/tests/booted/readonly/052-test-bli-detection.nu b/tmt/tests/booted/readonly/052-test-bli-detection.nu new file mode 100644 index 000000000..9da40a10a --- /dev/null +++ b/tmt/tests/booted/readonly/052-test-bli-detection.nu @@ -0,0 +1,30 @@ +use std assert +use tap.nu + +tap begin "DPS root discovery when partition-uuids is false" + +# Parse os-release +let os = open /usr/lib/os-release + | lines + | filter {|l| $l != "" and not ($l | str starts-with "#") } + | parse "{key}={value}" + | reduce -f {} {|it, acc| $acc | upsert $it.key ($it.value | str trim -c '"') } + +let os_id = ($os.ID? | default "unknown") +let version_id = ($os.VERSION_ID? | default "0" | into int) + +# We inject this in our builds, but hopefully C10S gets this too at some point +if not ($os_id == "fedora" and $version_id >= 43) { + print $"# skip: only applies to Fedora 43+ \(found ($os_id) ($version_id)\)" + tap ok + exit 0 +} + +print $"Running on ($os_id) ($version_id), checking DPS root discovery" + +let cmdline = (open /proc/cmdline) +let has_root_karg = ($cmdline | str contains "root=") + +assert (not $has_root_karg) "Fedora 43+ should use DPS auto-discovery (no root= in cmdline)" + +tap ok