From 4c45b3424fc8a3b14b64d8f6e1f0ed023655a5bf Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 14 May 2026 12:25:09 +0530 Subject: [PATCH 1/7] ukify: Allow passing path to kernel and initramfs While building a sealed UKI image we'd want to remove the original kernel + initramfs from the final image and have only the final UKI present. This was not possible before as `bootc container ukify` expected kernel + initramfs to be present in `usr/lib/modules` of container root We now accept a parameter `kernel_dir` which must be of the format `/path/$kernel_ver` for `bootc container ukify` Fixes: #2185 Signed-off-by: Pragyan Poudyal --- crates/lib/src/cli.rs | 31 ++++++++++++++ crates/lib/src/ukify.rs | 54 ++++++++++++++++--------- docs/src/man/bootc-container-ukify.8.md | 4 ++ 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/crates/lib/src/cli.rs b/crates/lib/src/cli.rs index dd80fd6ad..9a7c307ff 100644 --- a/crates/lib/src/cli.rs +++ b/crates/lib/src/cli.rs @@ -442,6 +442,13 @@ pub(crate) enum ContainerOpts { #[clap(long)] write_dumpfile_to: Option, + /// The directory containing the kernel and initramfs.img + /// Must be of the format /parent/$kernel_version + /// + /// Ex. /boot/6.18.7-100.fc42.x86_64 + #[clap(long)] + kernel_dir: Option, + /// Additional arguments to pass to ukify (after `--`). #[clap(last = true)] args: Vec, @@ -1902,12 +1909,36 @@ async fn run_from_opt(opt: Opt) -> Result<()> { kargs, allow_missing_verity, write_dumpfile_to, + kernel_dir, args, } => { + let kernel = match kernel_dir { + Some(kernel_dir) => { + let kver = kernel_dir + .components() + .last() + .ok_or_else(|| anyhow::anyhow!("Could not determine kernel version"))?; + + Some(crate::kernel::KernelInternal { + kernel: crate::kernel::Kernel { + unified: false, + version: kver.to_string(), + }, + k_type: crate::kernel::KernelType::Vmlinuz { + path: kernel_dir.join("vmlinuz"), + initramfs: kernel_dir.join("initramfs.img"), + }, + }) + } + + None => None, + }; + crate::ukify::build_ukify( &rootfs, &kargs, &args, + kernel, allow_missing_verity, write_dumpfile_to.as_deref(), ) diff --git a/crates/lib/src/ukify.rs b/crates/lib/src/ukify.rs index 265b867c4..523836217 100644 --- a/crates/lib/src/ukify.rs +++ b/crates/lib/src/ukify.rs @@ -15,6 +15,7 @@ use fn_error_context::context; use crate::bootc_composefs::digest::compute_composefs_digest; use crate::bootc_composefs::status::ComposefsCmdline; +use crate::kernel::KernelInternal; /// Build a UKI from the given rootfs. /// @@ -30,6 +31,7 @@ pub(crate) async fn build_ukify( rootfs: &Utf8Path, extra_kargs: &[String], args: &[OsString], + kernel: Option, allow_missing_fsverity: bool, write_dumpfile_to: Option<&Utf8Path>, ) -> Result<()> { @@ -52,12 +54,14 @@ pub(crate) async fn build_ukify( let root = Dir::open_ambient_dir(rootfs, cap_std_ext::cap_std::ambient_authority()) .with_context(|| format!("Opening rootfs {rootfs}"))?; - // Find the kernel - let kernel = crate::kernel::find_kernel(&root)? - .ok_or_else(|| anyhow::anyhow!("No kernel found in {rootfs}"))?; + let kernel_final = match kernel { + Some(ref kernel) => kernel, + None => &crate::kernel::find_kernel(&root)? + .ok_or_else(|| anyhow::anyhow!("No kernel found in {rootfs}"))?, + }; // Extract vmlinuz and initramfs paths, or bail if this is already a UKI - let (vmlinuz_path, initramfs_path) = match kernel.k_type { + let (vmlinuz_path, initramfs_path) = match &kernel_final.k_type { crate::kernel::KernelType::Vmlinuz { path, initramfs } => (path, initramfs), crate::kernel::KernelType::Uki { path, .. } => { anyhow::bail!("Cannot build UKI: rootfs already contains a UKI at {path}"); @@ -65,17 +69,31 @@ pub(crate) async fn build_ukify( }; // Verify kernel and initramfs exist - if !root - .try_exists(&vmlinuz_path) - .context("Checking for vmlinuz")? - { - anyhow::bail!("Kernel not found at {vmlinuz_path}"); - } - if !root - .try_exists(&initramfs_path) - .context("Checking for initramfs")? - { - anyhow::bail!("Initramfs not found at {initramfs_path}"); + // + // NOTE: Not using cap_std here as the vmlinuz/initramfs path from + // args can be outside of "rootfs" + if kernel.is_some() { + if !vmlinuz_path.exists() { + anyhow::bail!("Kernel not found at {vmlinuz_path}"); + } + + if !initramfs_path.exists() { + anyhow::bail!("Initramfs not found at {initramfs_path}"); + } + } else { + if !root + .try_exists(&vmlinuz_path) + .context("Checking for vmlinuz")? + { + anyhow::bail!("Kernel not found at {vmlinuz_path}"); + } + + if !root + .try_exists(&initramfs_path) + .context("Checking for initramfs")? + { + anyhow::bail!("Initramfs not found at {initramfs_path}"); + } } // Compute the composefs digest @@ -105,7 +123,7 @@ pub(crate) async fn build_ukify( .arg("--initrd") .arg(&initramfs_path) .arg("--uname") - .arg(&kernel.kernel.version) + .arg(&kernel_final.kernel.version) .arg("--cmdline") .arg(&cmdline_str) .arg("--os-release") @@ -134,7 +152,7 @@ mod tests { let tempdir = tempfile::tempdir().unwrap(); let path = Utf8Path::from_path(tempdir.path()).unwrap(); - let result = build_ukify(path, &[], &[], false, None).await; + let result = build_ukify(path, &[], &[], None, false, None).await; assert!(result.is_err()); let err = format!("{:#}", result.unwrap_err()); assert!( @@ -156,7 +174,7 @@ mod tests { ) .unwrap(); - let result = build_ukify(path, &[], &[], false, None).await; + let result = build_ukify(path, &[], &[], None, false, None).await; assert!(result.is_err()); let err = format!("{:#}", result.unwrap_err()); assert!( diff --git a/docs/src/man/bootc-container-ukify.8.md b/docs/src/man/bootc-container-ukify.8.md index bad9e10cc..d98e32589 100644 --- a/docs/src/man/bootc-container-ukify.8.md +++ b/docs/src/man/bootc-container-ukify.8.md @@ -35,6 +35,10 @@ Any additional arguments after `--` are passed through to ukify unchanged. Write a dumpfile to this path +**--kernel-dir**=*KERNEL_DIR* + + The directory containing the kernel and initramfs.img Must be of the format /parent/$kernel_version + # EXAMPLES From fe967f9b9910da7e2ab92cf08887b57a5f32aac9 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 14 May 2026 21:13:52 +0530 Subject: [PATCH 2/7] dockerfile/uki: Rework to remove kernel + initrd Now that we can pass kernel and initrd paths to `bootc ukify`, rework our UKI Dockerfile to remove kernel + initrd from the final layer and only keep the UKI This still will not *remove* the kernel + initrd from the tarball but have whiteout instead See https://github.com/bootc-dev/bootc/issues/2027#issuecomment-4244181869 Signed-off-by: Pragyan Poudyal --- Dockerfile | 44 ++++++++-- contrib/packaging/finalize-uki | 15 +--- contrib/packaging/seal-uki | 86 +++++++++++++------ crates/tests-integration/src/container.rs | 2 + tmt/tests/Dockerfile.upgrade | 34 ++++++-- tmt/tests/booted/tap.nu | 28 +++++- .../test-install-to-filesystem-var-mount.sh | 55 ++++++++---- 7 files changed, 192 insertions(+), 72 deletions(-) diff --git a/Dockerfile b/Dockerfile index 31f515800..780f347ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -83,7 +83,7 @@ RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ # Install systemd-ukify and systemd-boot for UKIs # This also installs systemd-boot for the grub UKI case which is not ideal... if [[ "${boot_type}" == "uki" ]]; then - pkgs_to_install+=(systemd-ukify) + pkgs_to_install+=(systemd-ukify binutils) fi if [[ ${#pkgs_to_install[@]} -gt 0 ]]; then @@ -135,7 +135,10 @@ ARG pkgversion ARG SOURCE_DATE_EPOCH ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} # Build RPM directly from source, using cached target directory -RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome RPM_VERSION="${pkgversion}" /src/contrib/packaging/build-rpm +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ + --mount=type=cache,target=/src/target \ + --mount=type=cache,target=/var/roothome \ + RPM_VERSION="${pkgversion}" /src/contrib/packaging/build-rpm # Build a systemd-sysext containing just the bootc binary. # Skips RPM machinery entirely for fast incremental rebuilds. @@ -218,7 +221,7 @@ COPY --from=update-generated-from-code /src/docs/src/*.schema.json /docs/src/ # ---- # Perform all filesystem transformations except generating the sealed UKI (if configured) -FROM base as base-penultimate +FROM base as base-penultimate-source ARG variant ARG bootloader ARG boot_type @@ -247,6 +250,10 @@ rm -rf /var/cache rm -rf /run/rhsm EORUN + +FROM base-penultimate-source as base-penultimate +ARG boot_type + # Configure the rootfs ARG rootfs="" RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ @@ -265,9 +272,19 @@ RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp COPY --from=packaging /usr-extras/ /usr/ # Clean up package manager caches RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ - --mount=type=bind,from=packaging,src=/,target=/run/packaging \ + --mount=type=bind,from=base-penultimate-source,src=/,target=/run/base-penultimate-src \ + --mount=type=bind,from=packaging,src=/,target=/run/packaging <&2 - exit 1 -fi +kver=$1 +shift # Create the EFI directory structure mkdir -p /boot/EFI/Linux @@ -36,12 +31,6 @@ mkdir -p /boot/EFI/Linux target=/boot/EFI/Linux/${kver}.efi cp "${uki_src}/${kver}.efi" "${target}" -# Remove the raw kernel and initramfs since we're using a UKI now. -# NOTE: We intentionally keep these for now until bcvk is updated to extract -# kernel/initramfs from UKIs in subdirectories. Once bcvk PR #144 is fixed -# to look for .efi files in /usr/lib/modules//, we can uncomment this. -# rm -v "/usr/lib/modules/${kver}/vmlinuz" "/usr/lib/modules/${kver}/initramfs.img" - # NOTE: We used to create a symlink from /usr/lib/modules/${kver}/${kver}.efi to the UKI # for tooling compatibility. However, composefs-boot's find_uki_components() doesn't # handle symlinks correctly and fails with "is not a regular file". The UKI is already diff --git a/contrib/packaging/seal-uki b/contrib/packaging/seal-uki index 66de92ffd..bfc7c4bec 100755 --- a/contrib/packaging/seal-uki +++ b/contrib/packaging/seal-uki @@ -2,32 +2,70 @@ # Generate a sealed UKI with embedded composefs digest set -xeuo pipefail -# Path to the desired root filesystem -target=$1 -shift -# Write to this directory -output=$1 -shift -# Path to secrets directory -secrets=$1 -shift -allow_missing_verity=$1 -shift -seal_state=$1 -shift - -if [[ $seal_state == "sealed" && $allow_missing_verity == "true" ]]; then +missing_verity=() + +while [ ! -z "${1:-}" ]; do + case "$1" in + # Path to the desired root filesystem + "--target") + target="$2" + shift + shift + ;; + + # Write to this directory + "--output") + output="$2" + shift + shift + ;; + + # Path to secrets directory + "--secrets") + secrets="$2" + shift + shift + ;; + + "--allow-missing-verity") + missing_verity=(--allow-missing-verity) + shift + ;; + + "--seal-state") + seal_state="$2" + shift + shift + ;; + + # Path to the directory containing kernel and initramfs + "--kernel-dir") + kernel_dir="$2" + shift + shift + ;; + + * ) + echo "Argument $1 not understood" + exit 1 + ;; + esac +done + +if [[ $seal_state == "sealed" && ${#missing_verity[@]} -gt 0 ]]; then echo "Cannot have missing verity with sealed UKI" >&2 exit 1 fi -# Find the kernel version (needed for output filename) -kver=$(bootc container inspect --rootfs "${target}" --json | jq -r '.kernel.version') -if [ -z "$kver" ] || [ "$kver" = "null" ]; then - echo "Error: No kernel found" >&2 - exit 1 +if [[ -z $kernel_dir ]]; then + echo "kernel dir is required" >&2 + exit 1 fi +kernel_params=(--kernel-dir "$kernel_dir") + +kver=$(basename "$kernel_dir") + mkdir -p "${output}" # Baseline ukify options @@ -45,12 +83,6 @@ fi # Baseline container ukify options containerukifyargs=(--rootfs "${target}") -missing_verity=() - -if [[ $allow_missing_verity == "true" ]]; then - missing_verity+=(--allow-missing-verity) -fi - # Build the UKI using bootc container ukify # This computes the composefs digest, reads kargs from kargs.d, and invokes ukify -bootc container ukify "${containerukifyargs[@]}" "${missing_verity[@]}" -- "${ukifyargs[@]}" +bootc container ukify "${containerukifyargs[@]}" "${kernel_params[@]}" "${missing_verity[@]}" -- "${ukifyargs[@]}" diff --git a/crates/tests-integration/src/container.rs b/crates/tests-integration/src/container.rs index a7d8e89d6..b03a69f58 100644 --- a/crates/tests-integration/src/container.rs +++ b/crates/tests-integration/src/container.rs @@ -51,6 +51,8 @@ pub(crate) fn test_bootc_container_inspect() -> Result<()> { .as_bool() .expect("kernel.unified should be a boolean"); + println!("kernel: {kernel:#?}"); + let is_uki = std::env::var("BOOTC_boot_type").is_ok_and(|var| var == "uki"); if let Some(variant) = std::env::var("BOOTC_variant").ok() { diff --git a/tmt/tests/Dockerfile.upgrade b/tmt/tests/Dockerfile.upgrade index 561e2e0a7..3b1ab73c3 100644 --- a/tmt/tests/Dockerfile.upgrade +++ b/tmt/tests/Dockerfile.upgrade @@ -13,6 +13,18 @@ ARG filesystem=ext4 FROM scratch AS packaging COPY contrib/packaging / +# Get kernel + initrd from the UKI +FROM localhost/bootc as kernel +ARG boot_type +RUN <<-EOF + if test "${boot_type}" = "uki"; then + kver=$(bootc container inspect --rootfs / --json | jq -r '.kernel.version') + mkdir -p "/boot/$kver" + objcopy -O binary --only-section=.initrd "/boot/EFI/Linux/$kver.efi" "/boot/$kver/initramfs.img" + objcopy -O binary --only-section=.linux "/boot/EFI/Linux/$kver.efi" "/boot/$kver/vmlinuz" + fi +EOF + # Create the upgrade content (a simple marker file). # For UKI builds, we also remove the existing UKI so that seal-uki can # regenerate it with the correct composefs digest for this derived image. @@ -36,16 +48,26 @@ RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=secret,id=secureboot_key \ --mount=type=secret,id=secureboot_cert \ --mount=type=bind,from=packaging,src=/,target=/run/packaging \ + --mount=type=bind,from=kernel,src=/,target=/run/kernel \ --mount=type=bind,from=upgrade-base,src=/,target=/run/target <> /tmp/Containerfile.drop-lbis <<-EOF - FROM base as base-final - RUN rm -rf /boot/EFI/Linux/*.efi + allow_missing_verity=() - FROM base as sealed-uki - RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ - --mount=type=bind,from=base-final,src=/,target=/run/target \ - /usr/bin/seal-uki /run/target /out /run/secrets $allow_missing_verity $seal_state + if [[ "$(bootc status --json | jq -r '.status.booted.composefs.missingVerityAllowed')" == "true" ]]; then + allow_missing_verity=("--allow-missing-verity") + fi - FROM base-final + seal_state="unsealed" - # Copy the sealed UKI and finalize the image remove raw kernel, create symlinks - RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ - --mount=type=bind,from=sealed-uki,src=/,target=/run/sealed-uki \ - /usr/bin/finalize-uki /run/sealed-uki/out + cat >> /tmp/Containerfile.drop-lbis <<-EOF +FROM base as kernel +RUN <<-RUNEOF + kver=\$(bootc container inspect --rootfs / --json | jq -r '.kernel.version') + mkdir -p "/boot/\$kver" + objcopy -O binary --only-section=.initrd "/boot/EFI/Linux/\$kver.efi" "/boot/\$kver/initramfs.img" + objcopy -O binary --only-section=.linux "/boot/EFI/Linux/\$kver.efi" "/boot/\$kver/vmlinuz" +RUNEOF + +FROM base as base-final +RUN rm -rf /boot/EFI/Linux/*.efi + +FROM base as sealed-uki +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ + --mount=type=bind,from=kernel,src=/,target=/run/kernel \ + --mount=type=bind,from=base-final,src=/,target=/run/target < Date: Fri, 15 May 2026 12:51:30 +0530 Subject: [PATCH 3/7] test/integration: Test vmlinuz non-existence with UKI vmlinuz and intrd should not be present in UKI images; add test for the same Signed-off-by: Pragyan Poudyal --- crates/tests-integration/src/container.rs | 27 +++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/tests-integration/src/container.rs b/crates/tests-integration/src/container.rs index b03a69f58..14396107b 100644 --- a/crates/tests-integration/src/container.rs +++ b/crates/tests-integration/src/container.rs @@ -1,8 +1,10 @@ +use cap_std_ext::cap_std::ambient_authority; +use cap_std_ext::cap_std::fs::Dir; use indoc::indoc; use scopeguard::defer; use serde::Deserialize; -use std::fs; use std::process::Command; +use std::{fs, path::Path}; use anyhow::{Context, Result}; use camino::Utf8Path; @@ -51,8 +53,6 @@ pub(crate) fn test_bootc_container_inspect() -> Result<()> { .as_bool() .expect("kernel.unified should be a boolean"); - println!("kernel: {kernel:#?}"); - let is_uki = std::env::var("BOOTC_boot_type").is_ok_and(|var| var == "uki"); if let Some(variant) = std::env::var("BOOTC_variant").ok() { @@ -74,6 +74,24 @@ pub(crate) fn test_bootc_container_inspect() -> Result<()> { ); // Version should be non-empty after stripping extension assert!(!version.is_empty(), "version should not be empty for UKI"); + + let target_root = Dir::open_ambient_dir(TARGET, ambient_authority()) + .with_context(|| format!("{TARGET} not found"))?; + + // For UKI make sure vmlinuz + initrd don't exist + let usr_lib_mod = Path::new("usr/lib/modules").join(version); + assert!( + target_root.exists(&usr_lib_mod), + "'{usr_lib_mod:?}' does not exist" + ); + assert!( + !target_root.exists(usr_lib_mod.join("vmlinuz")), + "vmlinuz should not exist for UKI" + ); + assert!( + !target_root.exists(usr_lib_mod.join("initramfs.img")), + "initramfs should not exist for UKI" + ); } o => eprintln!("notice: Unhandled variant for kernel check: {o:?}"), } @@ -190,6 +208,8 @@ fn test_variant_base_crosscheck() -> Result<()> { Ok(()) } +const TARGET: &str = "/run/target"; + /// Verify exported tar has correct size/mode/content vs source. /// Checks all critical paths (kernel, boot) plus ~10% random sample. pub(crate) fn test_container_export_tar() -> Result<()> { @@ -197,7 +217,6 @@ pub(crate) fn test_container_export_tar() -> Result<()> { use std::io::Read; use std::os::unix::fs::MetadataExt; - const TARGET: &str = "/run/target"; const CRITICAL: &[&str] = &["usr/lib/modules/", "usr/lib/ostree-boot/", "boot/"]; anyhow::ensure!( From e676885a707ac68a3f6fd451248b9857ad11809d Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Mon, 18 May 2026 12:57:33 +0530 Subject: [PATCH 4/7] test/tmt: Don't run bootloader-none test for composefs Bootloader set to none is not supported with the composefs backend so we skip tests with this option for composefs backend Signed-off-by: Pragyan Poudyal --- tmt/plans/integration.fmf | 2 ++ tmt/tests/booted/test-install-bootloader-none.nu | 5 ++++- tmt/tests/booted/test-install-no-boot-dir.nu | 9 +++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tmt/plans/integration.fmf b/tmt/plans/integration.fmf index b3366648b..9830acd87 100644 --- a/tmt/plans/integration.fmf +++ b/tmt/plans/integration.fmf @@ -217,6 +217,7 @@ execute: how: fmf test: - /tmt/tests/tests/test-37-install-no-boot-dir + extra-fixme_skip_if_composefs: true /plan-37-upgrade-check-status: summary: Verify upgrade --check populates cached update in status @@ -232,6 +233,7 @@ execute: how: fmf test: - /tmt/tests/tests/test-38-install-bootloader-none + extra-fixme_skip_if_composefs: true /plan-39-upgrade-tag: summary: Test bootc upgrade --tag functionality with containers-storage diff --git a/tmt/tests/booted/test-install-bootloader-none.nu b/tmt/tests/booted/test-install-bootloader-none.nu index ef89deae7..ab36260e6 100644 --- a/tmt/tests/booted/test-install-bootloader-none.nu +++ b/tmt/tests/booted/test-install-bootloader-none.nu @@ -2,7 +2,10 @@ # tmt: # summary: Test bootc install with --bootloader=none # duration: 30m -# +# extra: +# # bootloader=none is not supported for composefs +# fixme_skip_if_composefs: true + use std assert use tap.nu diff --git a/tmt/tests/booted/test-install-no-boot-dir.nu b/tmt/tests/booted/test-install-no-boot-dir.nu index bc012fd66..82494abb5 100644 --- a/tmt/tests/booted/test-install-no-boot-dir.nu +++ b/tmt/tests/booted/test-install-no-boot-dir.nu @@ -2,7 +2,12 @@ # tmt: # summary: Test bootc install to-filesystem without /boot directory # duration: 30m -# +# extra: +# # bootloader=none is not supported for composefs and this test fails +# # when trying to install bootloader for composefs. For ostree, the +# # bootloader installation is simply skipped +# fixme_skip_if_composefs: true + use std assert use tap.nu @@ -19,7 +24,7 @@ def main [] { mount -o loop disk.img /var/mnt setenforce 0 - + tap run_install $"bootc install to-filesystem --disable-selinux --bootloader=none --source-imgref ($target_image) /var/mnt" umount /var/mnt From aa7a57335e140f59b3c105c40732d43d1cd06c07 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Tue, 19 May 2026 14:45:09 +0530 Subject: [PATCH 5/7] image-proxy: Use privileged user when pull from containers storage We were defaulting to unprivileged user "nobody" when pulling an image, but pulling from containers-storage was failing as it requires extra privileges. Default to the current user, usually root, when pulling from containers-storage Signed-off-by: Pragyan Poudyal --- crates/lib/src/bootc_composefs/status.rs | 13 ++++++++----- crates/lib/src/bootc_composefs/update.rs | 2 +- crates/lib/src/install.rs | 3 +-- crates/ostree-ext/src/container/mod.rs | 14 ++++++++++++++ crates/ostree-ext/src/container/store.rs | 8 +------- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/crates/lib/src/bootc_composefs/status.rs b/crates/lib/src/bootc_composefs/status.rs index ac1b261dd..49a6349c8 100644 --- a/crates/lib/src/bootc_composefs/status.rs +++ b/crates/lib/src/bootc_composefs/status.rs @@ -36,7 +36,8 @@ use bootc_utils::try_deserialize_timestamp; use cap_std_ext::{cap_std::fs::Dir, dirext::CapStdExtDirExt}; use ostree_container::OstreeImageReference; use ostree_ext::container::{self as ostree_container}; -use ostree_ext::containers_image_proxy; +use ostree_ext::containers_image_proxy::{ImageProxy, ImageReference}; + use ostree_ext::oci_spec; use ostree_ext::{container::deploy::ORIGIN_CONTAINER, oci_spec::image::ImageConfiguration}; @@ -379,14 +380,16 @@ pub(crate) fn list_bootloader_entries(storage: &Storage) -> Result Result { let mut config = crate::deploy::new_proxy_config(); - ostree_ext::container::merge_default_container_proxy_opts(&mut config)?; - let proxy = containers_image_proxy::ImageProxy::new_with_config(config).await?; + + ostree_ext::container::apply_container_proxy_opts_for_transport(&mut config, imgref.transport)?; + + let proxy = ImageProxy::new_with_config(config).await?; let img = proxy - .open_image(&imgref) + .open_image_ref(&imgref) .await .with_context(|| format!("Opening image {imgref}"))?; diff --git a/crates/lib/src/bootc_composefs/update.rs b/crates/lib/src/bootc_composefs/update.rs index a1fb2722f..79c823159 100644 --- a/crates/lib/src/bootc_composefs/update.rs +++ b/crates/lib/src/bootc_composefs/update.rs @@ -58,7 +58,7 @@ pub(crate) async fn is_image_pulled( imgref: &ImageReference, ) -> Result<(Option, ImgConfigManifest)> { let imgref_repr = imgref.to_image_proxy_ref()?; - let img_config_manifest = get_container_manifest_and_config(&imgref_repr.to_string()).await?; + let img_config_manifest = get_container_manifest_and_config(&imgref_repr).await?; let img_digest = img_config_manifest.manifest.config().digest().digest(); diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index c9fcaf88c..d49c6a350 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -2017,8 +2017,7 @@ async fn install_to_filesystem_impl( // Pre-flight disk space check for native composefs install path. { let imgref = &state.source.imageref; - let imgref_repr = imgref.to_string(); - let img_manifest_config = get_container_manifest_and_config(&imgref_repr).await?; + let img_manifest_config = get_container_manifest_and_config(&imgref).await?; crate::store::ensure_composefs_dir(&rootfs.physical_root)?; // Use init_path since the repo may not exist yet during install let (cfs_repo, _created) = crate::store::ComposefsRepository::init_path( diff --git a/crates/ostree-ext/src/container/mod.rs b/crates/ostree-ext/src/container/mod.rs index 3419bdf82..e73361852 100644 --- a/crates/ostree-ext/src/container/mod.rs +++ b/crates/ostree-ext/src/container/mod.rs @@ -496,6 +496,20 @@ pub fn version_for_config(config: &oci_spec::image::ImageConfiguration) -> Optio None } +/// Apply appropriate container proxy options based on transport type +pub fn apply_container_proxy_opts_for_transport( + config: &mut containers_image_proxy::ImageProxyConfig, + transport: Transport, +) -> Result<()> { + if transport == Transport::ContainerStorage { + // Fetching from containers-storage, may require privileges to read files + merge_default_container_proxy_opts_with_isolation(config, None) + } else { + // Apply our defaults to the proxy config + merge_default_container_proxy_opts(config) + } +} + pub mod deploy; mod encapsulate; pub use encapsulate::*; diff --git a/crates/ostree-ext/src/container/store.rs b/crates/ostree-ext/src/container/store.rs index 8acf5831e..f810bb7ca 100644 --- a/crates/ostree-ext/src/container/store.rs +++ b/crates/ostree-ext/src/container/store.rs @@ -635,13 +635,7 @@ impl ImageImporter { imgref: &OstreeImageReference, mut config: ImageProxyConfig, ) -> Result { - if imgref.imgref.transport == Transport::ContainerStorage { - // Fetching from containers-storage, may require privileges to read files - merge_default_container_proxy_opts_with_isolation(&mut config, None)?; - } else { - // Apply our defaults to the proxy config - merge_default_container_proxy_opts(&mut config)?; - } + apply_container_proxy_opts_for_transport(&mut config, imgref.imgref.transport)?; let proxy = ImageProxy::new_with_config(config).await?; system_repo_journal_print( From eeab2751da1c36497b8f89bc3b1b3b3b7ed532d8 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 28 May 2026 15:40:39 +0530 Subject: [PATCH 6/7] dockerfile: Separate layer for removing kernel + initrd Baseconfigs tests were failing in CI because the initrd which had the expected baseconfigs was being deleted in the same layer it was being created in. Move the deletion to a completely separate layer Signed-off-by: Pragyan Poudyal --- Dockerfile | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 780f347ae..d5a7404a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -221,11 +221,10 @@ COPY --from=update-generated-from-code /src/docs/src/*.schema.json /docs/src/ # ---- # Perform all filesystem transformations except generating the sealed UKI (if configured) -FROM base as base-penultimate-source +FROM base as base-source ARG variant ARG bootloader ARG boot_type -ARG baseconfigs="" # Switch to a signed systemd-boot, if configured RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ @@ -251,8 +250,10 @@ rm -rf /run/rhsm EORUN -FROM base-penultimate-source as base-penultimate +FROM base-source as base-penultimate-source ARG boot_type +ARG variant +ARG baseconfigs="" # Configure the rootfs ARG rootfs="" @@ -272,9 +273,14 @@ RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp COPY --from=packaging /usr-extras/ /usr/ # Clean up package manager caches RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ - --mount=type=bind,from=base-penultimate-source,src=/,target=/run/base-penultimate-src \ --mount=type=bind,from=packaging,src=/,target=/run/packaging < Date: Tue, 2 Jun 2026 13:57:28 +0530 Subject: [PATCH 7/7] cli: Add cmd `bootc internals uki-extract` So we can just use bootc to extract the `.linux` and `.initrd` sections from the UKI and not have to use objcopy Signed-off-by: Pragyan Poudyal --- Dockerfile | 2 +- crates/lib/src/cli.rs | 53 ++++++++++++++++++- tmt/tests/Dockerfile.upgrade | 4 +- tmt/tests/booted/tap.nu | 4 +- .../test-install-to-filesystem-var-mount.sh | 4 +- 5 files changed, 56 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index d5a7404a9..86def9dd8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -83,7 +83,7 @@ RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ # Install systemd-ukify and systemd-boot for UKIs # This also installs systemd-boot for the grub UKI case which is not ideal... if [[ "${boot_type}" == "uki" ]]; then - pkgs_to_install+=(systemd-ukify binutils) + pkgs_to_install+=(systemd-ukify) fi if [[ ${#pkgs_to_install[@]} -gt 0 ]]; then diff --git a/crates/lib/src/cli.rs b/crates/lib/src/cli.rs index 9a7c307ff..2a9159b49 100644 --- a/crates/lib/src/cli.rs +++ b/crates/lib/src/cli.rs @@ -4,7 +4,7 @@ use std::ffi::{CString, OsStr, OsString}; use std::fs::File; -use std::io::{BufWriter, Seek}; +use std::io::{BufWriter, Seek, SeekFrom}; use std::os::fd::AsFd; use std::os::unix::process::CommandExt; use std::process::Command; @@ -27,6 +27,7 @@ use composefs_boot::BootOps as _; use etc_merge::{compute_diff, print_diff}; use fn_error_context::context; use indoc::indoc; +use ocidir::cap_std::ambient_authority; use ostree::gio; use ostree_container::store::PrepareResult; use ostree_ext::container as ostree_container; @@ -626,6 +627,19 @@ pub(crate) enum FsverityOpts { }, } +#[derive(Debug, clap::Subcommand, PartialEq, Eq)] +pub(crate) enum UkiSubcommands { + /// Extract kernel + initrd from a UKI + /// The output (vmlinuz + initramfs.img) is placed in a directory named + /// after the kernel version found in the UKI + Extract { + /// The path to the UKI PE + path: Utf8PathBuf, + /// The output path + output_path: Utf8PathBuf, + }, +} + /// Hidden, internal only options #[derive(Debug, clap::Subcommand, PartialEq, Eq)] pub(crate) enum InternalsOpts { @@ -742,6 +756,9 @@ pub(crate) enum InternalsOpts { /// Block device inspection tools. #[clap(subcommand)] Blockdev(BlockdevOpts), + /// For various UKI operations + #[clap(subcommand)] + Uki(UkiSubcommands), } /// Subcommands for `bootc internals blockdev`. @@ -2319,6 +2336,40 @@ async fn run_from_opt(opt: Opt) -> Result<()> { println!(); Ok(()) } + InternalsOpts::Uki(uki_opts) => match uki_opts { + UkiSubcommands::Extract { path, output_path } => { + let mut uki_file = + std::fs::File::open(&path).with_context(|| format!("Opening {path}"))?; + + let uname = + composefs_boot::uki::get_text_section_buffered(&mut uki_file, ".uname") + .context("Getting uname")?; + + std::fs::create_dir_all(&output_path).context("Creating output directory")?; + + let output_dir = Dir::open_ambient_dir(&output_path, ambient_authority()) + .context("Opening output dir")?; + output_dir.create_dir(&uname)?; + + let output_dir = output_dir.open_dir(&uname)?; + + for (section_name, file_name) in + [(".linux", "vmlinuz"), (".initrd", "initramfs.img")] + { + uki_file + .seek(SeekFrom::Start(0)) + .context("Seeking to start")?; + let section = + composefs_boot::uki::get_section_buffered(&mut uki_file, section_name) + .with_context(|| format!("Getting {section_name} section"))?; + output_dir + .write(file_name, section) + .with_context(|| format!("Writing {file_name}"))?; + } + + Ok(()) + } + }, }, Opt::State(opts) => match opts { StateOpts::WipeOstree => { diff --git a/tmt/tests/Dockerfile.upgrade b/tmt/tests/Dockerfile.upgrade index 3b1ab73c3..b66393114 100644 --- a/tmt/tests/Dockerfile.upgrade +++ b/tmt/tests/Dockerfile.upgrade @@ -19,9 +19,7 @@ ARG boot_type RUN <<-EOF if test "${boot_type}" = "uki"; then kver=$(bootc container inspect --rootfs / --json | jq -r '.kernel.version') - mkdir -p "/boot/$kver" - objcopy -O binary --only-section=.initrd "/boot/EFI/Linux/$kver.efi" "/boot/$kver/initramfs.img" - objcopy -O binary --only-section=.linux "/boot/EFI/Linux/$kver.efi" "/boot/$kver/vmlinuz" + bootc internals uki extract /boot/EFI/Linux/$kver.efi /boot fi EOF diff --git a/tmt/tests/booted/tap.nu b/tmt/tests/booted/tap.nu index f571bfff4..c25a89479 100644 --- a/tmt/tests/booted/tap.nu +++ b/tmt/tests/booted/tap.nu @@ -102,9 +102,7 @@ export def make_uki_containerfile [containerfile: string] { FROM base as kernel RUN <<-EOF kver=$\(bootc container inspect --rootfs / --json | jq -r '.kernel.version'\) - mkdir -p /boot/$kver - objcopy -O binary --only-section=.initrd /boot/EFI/Linux/$kver.efi /boot/$kver/initramfs.img - objcopy -O binary --only-section=.linux /boot/EFI/Linux/$kver.efi /boot/$kver/vmlinuz + bootc internals uki extract /boot/EFI/Linux/$kver.efi /boot EOF FROM base as base-final diff --git a/tmt/tests/booted/test-install-to-filesystem-var-mount.sh b/tmt/tests/booted/test-install-to-filesystem-var-mount.sh index c29b4154d..84e09da8e 100644 --- a/tmt/tests/booted/test-install-to-filesystem-var-mount.sh +++ b/tmt/tests/booted/test-install-to-filesystem-var-mount.sh @@ -46,9 +46,7 @@ if [[ $is_composefs != "null" && $boot_type == "uki" ]]; then FROM base as kernel RUN <<-RUNEOF kver=\$(bootc container inspect --rootfs / --json | jq -r '.kernel.version') - mkdir -p "/boot/\$kver" - objcopy -O binary --only-section=.initrd "/boot/EFI/Linux/\$kver.efi" "/boot/\$kver/initramfs.img" - objcopy -O binary --only-section=.linux "/boot/EFI/Linux/\$kver.efi" "/boot/\$kver/vmlinuz" + bootc internals uki extract /boot/EFI/Linux/\$kver.efi /boot RUNEOF FROM base as base-final