From 78e9b37054f6ce108b85cbea9ca7542df581896e Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 15 Nov 2025 13:06:25 -0500 Subject: [PATCH] install: Fix DPS support This fixes bootc's use of the Discoverable Partition Specification (DPS) to properly support systemd-gpt-auto-generator. Previously, bootc was incorrectly setting filesystem UUIDs to the DPS partition type UUID value, which caused UUID collisions and prevented proper DPS functionality. It's still a TODO on our side to support systemd-repart in this flow. Note we go back to using random filesystem UUIDs with this, but per above we should likely reinitialize them on boot via repart. Note we remove root= parameter from kernel cmdline for composefs sealed images, allowing systemd-gpt-auto-generator to auto-discover the root partition and we test this. Fixes: #1771 Assisted-by: Claude Code (Sonnet 4.5) Signed-off-by: Colin Walters --- Dockerfile.cfsuki | 5 +---- crates/blockdev/src/blockdev.rs | 1 + crates/lib/src/bootc_composefs/boot.rs | 6 +----- crates/lib/src/install/baseline.rs | 18 ++++++++++++------ crates/tests-integration/src/composefs_bcvk.rs | 7 +++++++ docs/src/man/bootc-install-to-disk.8.md | 13 +++++++++++++ .../booted/readonly/030-test-composefs.nu | 14 ++++++++++++++ 7 files changed, 49 insertions(+), 15 deletions(-) diff --git a/Dockerfile.cfsuki b/Dockerfile.cfsuki index 6dab68449..2fd9bb047 100644 --- a/Dockerfile.cfsuki +++ b/Dockerfile.cfsuki @@ -28,10 +28,7 @@ RUN --mount=type=secret,id=key \ # Should be generated externally test -n "${COMPOSEFS_FSVERITY}" - # Inject the composefs kernel argument and specify a root with the x86_64 DPS UUID. - # TODO: Discoverable partition fleshed out, or drop root UUID as systemd-stub extension - # TODO: https://github.com/containers/composefs-rs/issues/183 - cmdline="composefs=${COMPOSEFS_FSVERITY} root=UUID=4f68bce3-e8cd-4db1-96e7-fbcaf984b709 console=ttyS0,115200n8 enforcing=0 rw" + cmdline="composefs=${COMPOSEFS_FSVERITY} console=ttyS0,115200n8 enforcing=0 rw" # pesign uses NSS database so create it from input cert/key mkdir pesign diff --git a/crates/blockdev/src/blockdev.rs b/crates/blockdev/src/blockdev.rs index 284a76031..3905b7b87 100644 --- a/crates/blockdev/src/blockdev.rs +++ b/crates/blockdev/src/blockdev.rs @@ -37,6 +37,7 @@ pub struct Device { // Filesystem-related properties pub label: Option, pub fstype: Option, + pub uuid: Option, pub path: Option, } diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index fd4c71981..08c19d0ee 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -51,7 +51,6 @@ use crate::{ BOOT_LOADER_ENTRIES, COMPOSEFS_CMDLINE, ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOT_DIGEST, STAGED_BOOT_LOADER_ENTRIES, STATE_DIR_ABS, USER_CFG, USER_CFG_STAGED, }, - discoverable_partition_specification::this_arch_root, install::RW_KARG, spec::{Bootloader, Host}, }; @@ -412,10 +411,7 @@ pub(crate) fn setup_composefs_bls_boot( ( Utf8PathBuf::from("/sysroot"), get_esp_partition(&sysroot_parent)?.0, - Cmdline::from(format!( - "root=UUID={} {RW_KARG} {COMPOSEFS_CMDLINE}={id_hex}", - this_arch_root() - )), + Cmdline::from(format!("{RW_KARG} {COMPOSEFS_CMDLINE}={id_hex}")), fs, bootloader, ) diff --git a/crates/lib/src/install/baseline.rs b/crates/lib/src/install/baseline.rs index cff80ce7c..40a537e11 100644 --- a/crates/lib/src/install/baseline.rs +++ b/crates/lib/src/install/baseline.rs @@ -41,8 +41,6 @@ pub(crate) const EFIPN_SIZE_MB: u32 = 512; /// We need more space than ostree as we have UKIs and UKI addons /// We might also need to store UKIs for pinned deployments pub(crate) const CFS_EFIPN_SIZE_MB: u32 = 1024; -/// The GPT type for "linux" -pub(crate) const LINUX_PARTTYPE: &str = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"; #[cfg(feature = "install-to-disk")] pub(crate) const PREPBOOT_GUID: &str = "9E1A2D38-C612-4316-AA26-8B49521E5A8B"; #[cfg(feature = "install-to-disk")] @@ -113,7 +111,8 @@ fn mkfs<'a>( let devinfo = bootc_blockdev::list_dev(dev.into())?; let size = ostree_ext::glib::format_size(devinfo.size); - let u = uuid::Uuid::parse_str(crate::discoverable_partition_specification::this_arch_root())?; + // Generate a random UUID for the filesystem + let u = uuid::Uuid::new_v4(); let mut t = Task::new( &format!("Creating {label} filesystem ({fs}) on device {dev} (size={size})"), @@ -315,9 +314,11 @@ pub(crate) fn install_create_rootfs( let root_size = root_size .map(|v| Cow::Owned(format!("size={v}MiB, "))) .unwrap_or_else(|| Cow::Borrowed("")); + let rootpart_uuid = + uuid::Uuid::parse_str(crate::discoverable_partition_specification::this_arch_root())?; writeln!( &mut partitioning_buf, - r#"{root_size}type={LINUX_PARTTYPE}, name="root""# + r#"{root_size}type={rootpart_uuid}, name="root""# )?; tracing::debug!("Partitioning: {partitioning_buf}"); Task::new("Initializing partitions", "sfdisk") @@ -336,9 +337,14 @@ pub(crate) fn install_create_rootfs( let base_partitions = &bootc_blockdev::partitions_of(&devpath)?; let root_partition = base_partitions.find_partno(rootpn)?; - if root_partition.parttype.as_str() != LINUX_PARTTYPE { + // Verify the partition type matches the DPS root partition type for this architecture + let expected_parttype = crate::discoverable_partition_specification::this_arch_root(); + if !root_partition + .parttype + .eq_ignore_ascii_case(expected_parttype) + { anyhow::bail!( - "root partition {partno} has type {}; expected {LINUX_PARTTYPE}", + "root partition {rootpn} has type {}; expected {expected_parttype}", root_partition.parttype.as_str() ); } diff --git a/crates/tests-integration/src/composefs_bcvk.rs b/crates/tests-integration/src/composefs_bcvk.rs index 41f46e596..35eae10f6 100644 --- a/crates/tests-integration/src/composefs_bcvk.rs +++ b/crates/tests-integration/src/composefs_bcvk.rs @@ -80,6 +80,13 @@ fn inner_tests() -> Vec { assert_eq!(verity_from_status.unwrap(), verity_from_cmdline); + // Verify that we booted via systemd-gpt-auto-generator by checking + // that /proc/cmdline does NOT contain a root= parameter + let has_root_param = cmdline.iter().any(|entry| { + entry.key() == "root".into() + }); + assert!(!has_root_param, "Sealed composefs image should not have root= in kernel cmdline; systemd-gpt-auto-generator should discover the root partition via DPS"); + Ok(()) })] .into_iter() diff --git a/docs/src/man/bootc-install-to-disk.8.md b/docs/src/man/bootc-install-to-disk.8.md index 124456336..b470b7200 100644 --- a/docs/src/man/bootc-install-to-disk.8.md +++ b/docs/src/man/bootc-install-to-disk.8.md @@ -19,6 +19,19 @@ the container image, alongside any required system partitions such as the EFI system partition. Use `install to-filesystem` for anything more complex such as RAID, LVM, LUKS etc. +## Partitioning details + +The default as of bootc 1.11 uses the [Discoverable Partitions Specification](https://uapi-group.org/specifications/specs/discoverable_partitions_specification/) +for the generated root filesystem, as well as any required system partitions +such as the EFI system partition. + +Note that by default when used with "type 1" bootloader setups (i.e. non-UKI) +a kernel argument `root=UUID=` is injected by default. + +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. + # OPTIONS diff --git a/tmt/tests/booted/readonly/030-test-composefs.nu b/tmt/tests/booted/readonly/030-test-composefs.nu index b9978c4a8..b7f028e44 100644 --- a/tmt/tests/booted/readonly/030-test-composefs.nu +++ b/tmt/tests/booted/readonly/030-test-composefs.nu @@ -3,12 +3,26 @@ use tap.nu tap begin "composefs integration smoke test" +def parse_cmdline [] { + open /proc/cmdline | str trim | split row " " +} + # Detect composefs by checking if composefs field is present let st = bootc status --json | from json let is_composefs = ($st.status.booted.composefs? != null) let expecting_composefs = ($env.BOOTC_variant? | default "" | find "composefs") != null if $expecting_composefs { assert $is_composefs + # When using systemd-boot with DPS (Discoverable Partition Specification), + # /proc/cmdline should NOT contain a root= parameter because systemd-gpt-auto-generator + # discovers the root partition automatically + # Note that there is `bootctl --json=pretty` but it doesn't actually output JSON + let bootctl_output = (bootctl) + if ($bootctl_output | str contains 'Product: systemd-boot') { + let cmdline = parse_cmdline + let has_root_param = ($cmdline | any { |param| $param | str starts-with 'root=' }) + assert (not $has_root_param) "systemd-boot image should not have root= in kernel cmdline; systemd-gpt-auto-generator should discover the root partition via DPS" + } } if $is_composefs {