From 1017a8050889e09c4233711e51d1718ebb7bf4f5 Mon Sep 17 00:00:00 2001
From: John Eckersberg <jeckersb@redhat.com>
Date: Tue, 3 Feb 2026 11:48:08 -0500
Subject: [PATCH 1/2] seal-uki: break apart args for comment clarity

Signed-off-by: John Eckersberg <jeckersb@redhat.com>
---
 contrib/packaging/seal-uki | 30 ++++++++++++++++++------------
 1 file changed, 18 insertions(+), 12 deletions(-)

diff --git a/contrib/packaging/seal-uki b/contrib/packaging/seal-uki
index 9684dc8be..126988abc 100755
--- a/contrib/packaging/seal-uki
+++ b/contrib/packaging/seal-uki
@@ -21,17 +21,23 @@ fi
 
 mkdir -p "${output}"
 
-# Build the UKI using bootc container ukify
-# This computes the composefs digest, reads kargs from kargs.d, and invokes ukify
-#
+# Baseline ukify options
+ukifyargs=(--measure
+           --json pretty
+           --output "${output}/${kver}.efi")
+
+# Signing options, we use sbsign by default
+ukifyargs+=(--signtool sbsign
+            --secureboot-private-key "${secrets}/secureboot_key"
+            --secureboot-certificate "${secrets}/secureboot_cert")
+
+# Baseline container ukify options
+containerukifyargs=(--rootfs "${target}")
+
 # WORKAROUND: SELinux must be permissive for sealed UKI boot
 # See https://github.com/bootc-dev/bootc/issues/1826
-bootc container ukify --rootfs "${target}" \
-  --karg enforcing=0 \
-  -- \
-  --signtool sbsign \
-  --secureboot-private-key "${secrets}/secureboot_key" \
-  --secureboot-certificate "${secrets}/secureboot_cert" \
-  --measure \
-  --json pretty \
-  --output "${output}/${kver}.efi"
+containerukifyargs+=(--karg enforcing=0)
+
+# Build the UKI using bootc container ukify
+# This computes the composefs digest, reads kargs from kargs.d, and invokes ukify
+bootc container ukify "${containerukifyargs[@]}" -- "${ukifyargs[@]}"

From efbd374fdb3019440131bcc9298b998e4e7b6499 Mon Sep 17 00:00:00 2001
From: John Eckersberg <jeckersb@redhat.com>
Date: Tue, 3 Feb 2026 11:49:27 -0500
Subject: [PATCH 2/2] kernel: Add KernelPath enum

This just clarifies things in a few places to distinguish between a
UKI which has just a single path versus a traditional kernel with
separate vmlinuz and initramfs.

Also renames `find_uki_filename` to `find_uki_path` and updates the
return type to use `Utf8PathBuf` instead of just `String`.

Signed-off-by: John Eckersberg <jeckersb@redhat.com>
---
 crates/lib/src/kernel.rs | 91 +++++++++++++++++++++++-----------------
 crates/lib/src/ukify.rs  | 22 ++++------
 2 files changed, 59 insertions(+), 54 deletions(-)

diff --git a/crates/lib/src/kernel.rs b/crates/lib/src/kernel.rs
index c66841fce..6cfd85156 100644
--- a/crates/lib/src/kernel.rs
+++ b/crates/lib/src/kernel.rs
@@ -26,20 +26,28 @@ pub(crate) struct Kernel {
     pub(crate) unified: bool,
 }
 
-/// Internal-only kernel wrapper with extra information (paths to
-/// vmlinuz, initramfs) that are useful but we don't want to leak out
-/// via serialization to inspection.
+/// Path to kernel component(s)
+///
+/// UKI kernels only have the single PE binary, whereas
+/// traditional "vmlinuz" kernels have distinct kernel and
+/// initramfs.
+pub(crate) enum KernelPath {
+    Uki(Utf8PathBuf),
+    Vmlinuz {
+        path: Utf8PathBuf,
+        initramfs: Utf8PathBuf,
+    },
+}
+
+/// Internal-only kernel wrapper with extra path information that are
+/// useful but we don't want to leak out via serialization to
+/// inspection.
 ///
 /// `Kernel` implements `From<KernelInternal>` so we can just `.into()`
 /// to get the "public" form where needed.
 pub(crate) struct KernelInternal {
     pub(crate) kernel: Kernel,
-    /// Path to vmlinuz for traditional kernels.
-    /// This is `None` for UKI images.
-    pub(crate) vmlinuz: Option<Utf8PathBuf>,
-    /// Path to initramfs.img for traditional kernels.
-    /// This is `None` for UKI images.
-    pub(crate) initramfs: Option<Utf8PathBuf>,
+    pub(crate) path: KernelPath,
 }
 
 impl From<KernelInternal> for Kernel {
@@ -57,18 +65,14 @@ impl From<KernelInternal> for Kernel {
 /// Returns `None` if no kernel is found.
 pub(crate) fn find_kernel(root: &Dir) -> Result<Option<KernelInternal>> {
     // First, try to find a UKI
-    if let Some(uki_filename) = find_uki_filename(root)? {
-        let version = uki_filename
-            .strip_suffix(".efi")
-            .unwrap_or(&uki_filename)
-            .to_owned();
+    if let Some(uki_path) = find_uki_path(root)? {
+        let version = uki_path.file_stem().unwrap_or(uki_path.as_str()).to_owned();
         return Ok(Some(KernelInternal {
             kernel: Kernel {
                 version,
                 unified: true,
             },
-            vmlinuz: None,
-            initramfs: None,
+            path: KernelPath::Uki(uki_path),
         }));
     }
 
@@ -85,19 +89,21 @@ pub(crate) fn find_kernel(root: &Dir) -> Result<Option<KernelInternal>> {
                 version,
                 unified: false,
             },
-            vmlinuz: Some(vmlinuz),
-            initramfs: Some(initramfs),
+            path: KernelPath::Vmlinuz {
+                path: vmlinuz,
+                initramfs,
+            },
         }));
     }
 
     Ok(None)
 }
 
-/// Returns the filename of the first UKI found in the container root, if any.
+/// Returns the path to the first UKI found in the container root, if any.
 ///
 /// Looks in `/boot/EFI/Linux/*.efi`. If multiple UKIs are present, returns
 /// the first one in sorted order for determinism.
-fn find_uki_filename(root: &Dir) -> Result<Option<String>> {
+fn find_uki_path(root: &Dir) -> Result<Option<Utf8PathBuf>> {
     let Some(boot) = root.open_dir_optional(crate::install::BOOT)? else {
         return Ok(None);
     };
@@ -120,13 +126,15 @@ fn find_uki_filename(root: &Dir) -> Result<Option<String>> {
 
     // Sort for deterministic behavior when multiple UKIs are present
     uki_files.sort();
-    Ok(uki_files.into_iter().next())
+    Ok(uki_files
+        .into_iter()
+        .next()
+        .map(|filename| Utf8PathBuf::from(format!("boot/{EFI_LINUX}/{filename}"))))
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
-    use camino::Utf8Path;
     use cap_std_ext::{cap_std, cap_tempfile, dirext::CapStdExtDirExt};
 
     #[test]
@@ -148,18 +156,19 @@ mod tests {
         let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel");
         assert_eq!(kernel_internal.kernel.version, "6.12.0-100.fc41.x86_64");
         assert!(!kernel_internal.kernel.unified);
-        assert_eq!(
-            kernel_internal.vmlinuz.as_deref(),
-            Some(Utf8Path::new(
-                "usr/lib/modules/6.12.0-100.fc41.x86_64/vmlinuz"
-            ))
-        );
-        assert_eq!(
-            kernel_internal.initramfs.as_deref(),
-            Some(Utf8Path::new(
-                "usr/lib/modules/6.12.0-100.fc41.x86_64/initramfs.img"
-            ))
-        );
+        match &kernel_internal.path {
+            KernelPath::Vmlinuz { path, initramfs } => {
+                assert_eq!(
+                    path.as_str(),
+                    "usr/lib/modules/6.12.0-100.fc41.x86_64/vmlinuz"
+                );
+                assert_eq!(
+                    initramfs.as_str(),
+                    "usr/lib/modules/6.12.0-100.fc41.x86_64/initramfs.img"
+                );
+            }
+            KernelPath::Uki(_) => panic!("Expected Vmlinuz, got Uki"),
+        }
         Ok(())
     }
 
@@ -172,8 +181,12 @@ mod tests {
         let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel");
         assert_eq!(kernel_internal.kernel.version, "fedora-6.12.0");
         assert!(kernel_internal.kernel.unified);
-        assert!(kernel_internal.vmlinuz.is_none());
-        assert!(kernel_internal.initramfs.is_none());
+        match &kernel_internal.path {
+            KernelPath::Uki(path) => {
+                assert_eq!(path.as_str(), "boot/EFI/Linux/fedora-6.12.0.efi");
+            }
+            KernelPath::Vmlinuz { .. } => panic!("Expected Uki, got Vmlinuz"),
+        }
         Ok(())
     }
 
@@ -197,7 +210,7 @@ mod tests {
     }
 
     #[test]
-    fn test_find_uki_filename_sorted() -> Result<()> {
+    fn test_find_uki_path_sorted() -> Result<()> {
         let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
         tempdir.create_dir_all("boot/EFI/Linux")?;
         tempdir.atomic_write("boot/EFI/Linux/zzz.efi", b"fake uki")?;
@@ -205,8 +218,8 @@ mod tests {
         tempdir.atomic_write("boot/EFI/Linux/mmm.efi", b"fake uki")?;
 
         // Should return first in sorted order
-        let filename = find_uki_filename(&tempdir)?.expect("should find uki");
-        assert_eq!(filename, "aaa.efi");
+        let path = find_uki_path(&tempdir)?.expect("should find uki");
+        assert_eq!(path.as_str(), "boot/EFI/Linux/aaa.efi");
         Ok(())
     }
 }
diff --git a/crates/lib/src/ukify.rs b/crates/lib/src/ukify.rs
index f4b03e0de..05e5d86ed 100644
--- a/crates/lib/src/ukify.rs
+++ b/crates/lib/src/ukify.rs
@@ -54,21 +54,13 @@ pub(crate) fn build_ukify(
     let kernel = crate::kernel::find_kernel(&root)?
         .ok_or_else(|| anyhow::anyhow!("No kernel found in {rootfs}"))?;
 
-    // We can only build a UKI from a traditional kernel, not from an existing UKI
-    if kernel.kernel.unified {
-        anyhow::bail!(
-            "Cannot build UKI: rootfs already contains a UKI at boot/EFI/Linux/{}.efi",
-            kernel.kernel.version
-        );
-    }
-
-    // Get paths from the kernel info
-    let vmlinuz_path = kernel
-        .vmlinuz
-        .ok_or_else(|| anyhow::anyhow!("Traditional kernel should have vmlinuz path"))?;
-    let initramfs_path = kernel
-        .initramfs
-        .ok_or_else(|| anyhow::anyhow!("Traditional kernel should have initramfs path"))?;
+    // Extract vmlinuz and initramfs paths, or bail if this is already a UKI
+    let (vmlinuz_path, initramfs_path) = match kernel.path {
+        crate::kernel::KernelPath::Vmlinuz { path, initramfs } => (path, initramfs),
+        crate::kernel::KernelPath::Uki(path) => {
+            anyhow::bail!("Cannot build UKI: rootfs already contains a UKI at {path}");
+        }
+    };
 
     // Verify kernel and initramfs exist
     if !root
