1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-05 06:45:13 +01:00

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 <walters@verbum.org>
This commit is contained in:
Colin Walters
2025-11-15 13:06:25 -05:00
parent 63d09b6a7e
commit a998bfc3f1
7 changed files with 49 additions and 15 deletions

View File

@@ -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

View File

@@ -37,6 +37,7 @@ pub struct Device {
// Filesystem-related properties
pub label: Option<String>,
pub fstype: Option<String>,
pub uuid: Option<String>,
pub path: Option<String>,
}

View File

@@ -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,
)

View File

@@ -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()
);
}

View File

@@ -80,6 +80,13 @@ fn inner_tests() -> Vec<Trial> {
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()

View File

@@ -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=<uuid of filesystem>` 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
<!-- BEGIN GENERATED OPTIONS -->

View File

@@ -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 {