From d2c358e0dfb18f127b899ce472c97fbc00c1c564 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Wed, 12 Nov 2025 16:01:41 +0530 Subject: [PATCH 1/3] composefs/install: Install UKI Addons globally Until now we were scoping passed in UKI Addons to specific deployments, but usecases like ignition, luks (for now at least), require the addons to be applied to all deployments Signed-off-by: Pragyan Poudyal --- crates/lib/src/bootc_composefs/boot.rs | 35 ++++++++++++++++++-------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index 5cc475b75..07f4f0e66 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -57,7 +57,7 @@ use crate::{ use crate::install::{RootSetup, State}; -/// Contains the EFP's filesystem UUID. Used by grub +/// Contains the ESP's filesystem UUID. Used by grub pub(crate) const EFI_UUID_FILE: &str = "efiuuid.cfg"; /// The EFI Linux directory pub(crate) const EFI_LINUX: &str = "EFI/Linux"; @@ -75,6 +75,13 @@ const VMLINUZ: &str = "vmlinuz"; /// This is relative to the ESP pub(crate) const SYSTEMD_UKI_DIR: &str = "EFI/Linux/bootc"; +pub(crate) const GLOBAL_UKI_ADDON_DIR: &str = "loader/addons"; + +/// Whether to install all passed in UKI Addons as global +/// For now, we don't have a reason to scope addons to a specific deployment, but we want to in the +/// future +pub const INSTALL_ADDONS_AS_GLOBAL: bool = true; + pub(crate) enum BootSetupType<'a> { /// For initial setup, i.e. install to-disk Setup( @@ -642,16 +649,23 @@ fn write_pe_to_esp( }); } - // Write the UKI to ESP - let efi_linux_path = mounted_efi.as_ref().join(match bootloader { - Bootloader::Grub => EFI_LINUX, - Bootloader::Systemd => SYSTEMD_UKI_DIR, + // Directory to write the PortableExecutable to + let pe_install_dir = mounted_efi.as_ref().join(match pe_type { + PEType::UkiAddon if INSTALL_ADDONS_AS_GLOBAL => GLOBAL_UKI_ADDON_DIR, + + PEType::Uki | PEType::UkiAddon => match bootloader { + Bootloader::Grub => EFI_LINUX, + Bootloader::Systemd => SYSTEMD_UKI_DIR, + }, }); - create_dir_all(&efi_linux_path).context("Creating EFI/Linux")?; + create_dir_all(&pe_install_dir) + .with_context(|| format!("Creating {}", pe_install_dir.display()))?; + + let final_pe_path = match (INSTALL_ADDONS_AS_GLOBAL, file_path.parent()) { + (true, ..) => pe_install_dir, - let final_pe_path = match file_path.parent() { - Some(parent) => { + (false, Some(parent)) => { let renamed_path = match parent.as_str().ends_with(EFI_ADDON_DIR_EXT) { true => { let dir_name = format!("{}{}", uki_id.to_hex(), EFI_ADDON_DIR_EXT); @@ -665,13 +679,13 @@ fn write_pe_to_esp( false => parent.to_path_buf(), }; - let full_path = efi_linux_path.join(renamed_path); + let full_path = pe_install_dir.join(renamed_path); create_dir_all(&full_path)?; full_path } - None => efi_linux_path, + (false, None) => pe_install_dir, }; let pe_dir = Dir::open_ambient_dir(&final_pe_path, ambient_authority()) @@ -922,6 +936,7 @@ pub(crate) fn setup_composefs_uki_boot( })?; if !addons.iter().any(|passed_addon| passed_addon == addon_name) { + tracing::debug!("Skipping addon '{addon_name}'"); continue; } } From 2906324269ee7b9036411fc761001a73f2f9ed5e Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Wed, 12 Nov 2025 16:19:04 +0530 Subject: [PATCH 2/3] bls-config: Rename 'efi' key to 'uki' Key 'uki' is more appropriate for us as the PE Binary that the key points to is a UKI Signed-off-by: Pragyan Poudyal --- crates/lib/src/bootc_composefs/boot.rs | 8 ++--- crates/lib/src/bootc_composefs/delete.rs | 14 ++++---- crates/lib/src/bootc_composefs/state.rs | 6 ++-- crates/lib/src/bootc_composefs/status.rs | 14 ++++---- crates/lib/src/parsers/bls_config.rs | 44 ++++++++++++------------ 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index 07f4f0e66..ad4e9805e 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -504,7 +504,7 @@ pub(crate) fn setup_composefs_bls_boot( .with_title(title) .with_sort_key(default_sort_key.into()) .with_version(version) - .with_cfg(BLSConfigType::NonEFI { + .with_cfg(BLSConfigType::NonUKI { linux: entry_paths.abs_entries_path.join(&id_hex).join(VMLINUZ), initrd: vec![entry_paths.abs_entries_path.join(&id_hex).join(INITRD)], options: Some(cmdline_refs), @@ -515,7 +515,7 @@ pub(crate) fn setup_composefs_bls_boot( let symlink_to = &symlink_to[0]; match bls_config.cfg_type { - BLSConfigType::NonEFI { + BLSConfigType::NonUKI { ref mut linux, ref mut initrd, .. @@ -810,8 +810,8 @@ fn write_systemd_uki_config( let mut bls_conf = BLSConfig::default(); bls_conf .with_title(boot_label.boot_label) - .with_cfg(BLSConfigType::EFI { - efi: format!("/{SYSTEMD_UKI_DIR}/{}{}", id.to_hex(), EFI_EXT).into(), + .with_cfg(BLSConfigType::UKI { + uki: format!("/{SYSTEMD_UKI_DIR}/{}{}", id.to_hex(), EFI_EXT).into(), }) .with_sort_key(default_sort_key.into()) .with_version(boot_label.version.unwrap_or(default_sort_key.into())); diff --git a/crates/lib/src/bootc_composefs/delete.rs b/crates/lib/src/bootc_composefs/delete.rs index b16930e83..c12d408bb 100644 --- a/crates/lib/src/bootc_composefs/delete.rs +++ b/crates/lib/src/bootc_composefs/delete.rs @@ -65,20 +65,20 @@ fn delete_type1_entry(depl: &DeploymentEntry, boot_dir: &Dir, deleting_staged: b let bls_config = parse_bls_config(&cfg)?; match &bls_config.cfg_type { - BLSConfigType::EFI { efi } => { - if !efi.as_str().contains(&depl.deployment.verity) { + BLSConfigType::UKI { uki } => { + if !uki.as_str().contains(&depl.deployment.verity) { continue; } // Boot dir in case of EFI will be the ESP - tracing::debug!("Deleting EFI .conf file: {}", file_name); + tracing::debug!("Deleting UKI .conf file: {}", file_name); entry.remove_file().context("Removing .conf file")?; delete_uki(&depl.deployment.verity, boot_dir)?; break; } - BLSConfigType::NonEFI { options, .. } => { + BLSConfigType::NonUKI { options, .. } => { let options = options .as_ref() .ok_or(anyhow::anyhow!("options not found in BLS config file"))?; @@ -87,7 +87,7 @@ fn delete_type1_entry(depl: &DeploymentEntry, boot_dir: &Dir, deleting_staged: b continue; } - tracing::debug!("Deleting non-EFI .conf file: {}", file_name); + tracing::debug!("Deleting non-UKI .conf file: {}", file_name); entry.remove_file().context("Removing .conf file")?; if should_del_kernel { @@ -116,8 +116,8 @@ fn delete_type1_entry(depl: &DeploymentEntry, boot_dir: &Dir, deleting_staged: b #[fn_error_context::context("Deleting kernel and initrd")] fn delete_kernel_initrd(bls_config: &BLSConfigType, boot_dir: &Dir) -> Result<()> { - let BLSConfigType::NonEFI { linux, initrd, .. } = bls_config else { - anyhow::bail!("Found EFI config") + let BLSConfigType::NonUKI { linux, initrd, .. } = bls_config else { + anyhow::bail!("Found UKI config") }; // "linux" and "initrd" are relative to the boot_dir in our config files diff --git a/crates/lib/src/bootc_composefs/state.rs b/crates/lib/src/bootc_composefs/state.rs index 723d6ed19..8491a627c 100644 --- a/crates/lib/src/bootc_composefs/state.rs +++ b/crates/lib/src/bootc_composefs/state.rs @@ -44,17 +44,17 @@ pub(crate) fn get_booted_bls(boot_dir: &Dir) -> Result { for entry in sorted_entries { match &entry.cfg_type { - BLSConfigType::EFI { efi } => { + BLSConfigType::UKI { uki } => { let composefs_param_value = booted.value().ok_or_else(|| { anyhow::anyhow!("Failed to get composefs kernel cmdline value") })?; - if efi.as_str().contains(composefs_param_value) { + if uki.as_str().contains(composefs_param_value) { return Ok(entry); } } - BLSConfigType::NonEFI { options, .. } => { + BLSConfigType::NonUKI { options, .. } => { let Some(opts) = options else { anyhow::bail!("options not found in bls config") }; diff --git a/crates/lib/src/bootc_composefs/status.rs b/crates/lib/src/bootc_composefs/status.rs index 29ae212ba..c09e13474 100644 --- a/crates/lib/src/bootc_composefs/status.rs +++ b/crates/lib/src/bootc_composefs/status.rs @@ -377,13 +377,13 @@ pub(crate) async fn composefs_deployment_status_from( .ok_or(anyhow::anyhow!("First boot entry not found"))?; match &bls_config.cfg_type { - BLSConfigType::NonEFI { options, .. } => !options + BLSConfigType::NonUKI { options, .. } => !options .as_ref() .ok_or(anyhow::anyhow!("options key not found in bls config"))? .contains(composefs_digest.as_ref()), - BLSConfigType::EFI { .. } => { - anyhow::bail!("Found 'efi' field in Type1 boot entry") + BLSConfigType::UKI { .. } => { + anyhow::bail!("Found 'uki' field in Type1 boot entry") } BLSConfigType::Unknown => anyhow::bail!("Unknown BLS Config Type"), } @@ -410,10 +410,10 @@ pub(crate) async fn composefs_deployment_status_from( match &bls_config.cfg_type { // For UKI boot - BLSConfigType::EFI { efi } => efi.as_str().contains(composefs_digest.as_ref()), + BLSConfigType::UKI { uki } => uki.as_str().contains(composefs_digest.as_ref()), // For boot entry Type1 - BLSConfigType::NonEFI { options, .. } => !options + BLSConfigType::NonUKI { options, .. } => !options .as_ref() .ok_or(anyhow::anyhow!("options key not found in bls config"))? .contains(composefs_digest.as_ref()), @@ -486,7 +486,7 @@ mod tests { let mut config1 = BLSConfig::default(); config1.title = Some("Fedora 42.20250623.3.1 (CoreOS)".into()); config1.sort_key = Some("1".into()); - config1.cfg_type = BLSConfigType::NonEFI { + config1.cfg_type = BLSConfigType::NonUKI { linux: "/boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/vmlinuz-5.14.10".into(), initrd: vec!["/boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/initramfs-5.14.10.img".into()], options: Some("root=UUID=abc123 rw composefs=7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6".into()), @@ -495,7 +495,7 @@ mod tests { let mut config2 = BLSConfig::default(); config2.title = Some("Fedora 41.20250214.2.0 (CoreOS)".into()); config2.sort_key = Some("2".into()); - config2.cfg_type = BLSConfigType::NonEFI { + config2.cfg_type = BLSConfigType::NonUKI { linux: "/boot/febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01/vmlinuz-5.14.10".into(), initrd: vec!["/boot/febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01/initramfs-5.14.10.img".into()], options: Some("root=UUID=abc123 rw composefs=febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01".into()) diff --git a/crates/lib/src/parsers/bls_config.rs b/crates/lib/src/parsers/bls_config.rs index 606b990c7..275baf24e 100644 --- a/crates/lib/src/parsers/bls_config.rs +++ b/crates/lib/src/parsers/bls_config.rs @@ -17,11 +17,11 @@ use crate::composefs_consts::COMPOSEFS_CMDLINE; #[derive(Debug, PartialEq, Eq, Default)] pub enum BLSConfigType { - EFI { - /// The path to the EFI binary, usually a UKI - efi: Utf8PathBuf, + UKI { + /// The path to the UKI + uki: Utf8PathBuf, }, - NonEFI { + NonUKI { /// The path to the linux kernel to boot. linux: Utf8PathBuf, /// The paths to the initrd images. @@ -102,11 +102,11 @@ impl Display for BLSConfig { writeln!(f, "version {}", self.version)?; match &self.cfg_type { - BLSConfigType::EFI { efi } => { - writeln!(f, "efi {}", efi)?; + BLSConfigType::UKI { uki } => { + writeln!(f, "uki {}", uki)?; } - BLSConfigType::NonEFI { + BLSConfigType::NonUKI { linux, initrd, options, @@ -173,16 +173,16 @@ impl BLSConfig { pub(crate) fn get_verity(&self) -> Result { match &self.cfg_type { - BLSConfigType::EFI { efi } => Ok(efi + BLSConfigType::UKI { uki } => Ok(uki .components() .last() - .ok_or(anyhow::anyhow!("Empty efi field"))? + .ok_or(anyhow::anyhow!("Empty uki field"))? .to_string() .strip_suffix(EFI_EXT) - .ok_or(anyhow::anyhow!("efi doesn't end with .efi"))? + .ok_or_else(|| anyhow::anyhow!("uki doesn't end with .efi"))? .to_string()), - BLSConfigType::NonEFI { options, .. } => { + BLSConfigType::NonUKI { options, .. } => { let options = options.as_ref().ok_or(anyhow::anyhow!("No options"))?; let cmdline = Cmdline::from(&options); @@ -209,7 +209,7 @@ pub(crate) fn parse_bls_config(input: &str) -> Result { let mut title = None; let mut version = None; let mut linux = None; - let mut efi = None; + let mut uki = None; let mut initrd = Vec::new(); let mut options = None; let mut machine_id = None; @@ -232,7 +232,7 @@ pub(crate) fn parse_bls_config(input: &str) -> Result { "options" => options = Some(CmdlineOwned::from(value)), "machine-id" => machine_id = Some(value), "sort-key" => sort_key = Some(value), - "efi" => efi = Some(Utf8PathBuf::from(value)), + "uki" => uki = Some(Utf8PathBuf::from(value)), _ => { extra.insert(key.to_string(), value); } @@ -242,10 +242,10 @@ pub(crate) fn parse_bls_config(input: &str) -> Result { let version = version.ok_or_else(|| anyhow!("Missing 'version' value"))?; - let cfg_type = match (linux, efi) { - (None, Some(efi)) => BLSConfigType::EFI { efi }, + let cfg_type = match (linux, uki) { + (None, Some(uki)) => BLSConfigType::UKI { uki }, - (Some(linux), None) => BLSConfigType::NonEFI { + (Some(linux), None) => BLSConfigType::NonUKI { linux, initrd, options, @@ -253,8 +253,8 @@ pub(crate) fn parse_bls_config(input: &str) -> Result { // The spec makes no mention of whether both can be present or not // Fow now, for us, we won't have both at the same time - (Some(_), Some(_)) => anyhow::bail!("'linux' and 'efi' values present"), - (None, None) => anyhow::bail!("Missing 'linux' or 'efi' value"), + (Some(_), Some(_)) => anyhow::bail!("'linux' and 'uki' values present"), + (None, None) => anyhow::bail!("Missing 'linux' or 'uki' value"), }; Ok(BLSConfig { @@ -285,13 +285,13 @@ mod tests { let config = parse_bls_config(input)?; - let BLSConfigType::NonEFI { + let BLSConfigType::NonUKI { linux, initrd, options, } = config.cfg_type else { - panic!("Expected non EFI variant"); + panic!("Expected non UKI variant"); }; assert_eq!( @@ -321,8 +321,8 @@ mod tests { let config = parse_bls_config(input)?; - let BLSConfigType::NonEFI { initrd, .. } = config.cfg_type else { - panic!("Expected non EFI variant"); + let BLSConfigType::NonUKI { initrd, .. } = config.cfg_type else { + panic!("Expected non UKI variant"); }; assert_eq!( From a7dc7edee9f18907dae4e3a8f8277cccf0c04210 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Tue, 18 Nov 2025 17:03:52 +0530 Subject: [PATCH 3/3] composefs/uki: Install all UKIs in EFI/Linux/bootc We were making a distinction based on the bootloader and installing UKIs in EFI/Linux for Grub and EFI/Linux/bootc for sd-boot. IMO it's better if we use the same directory for both bootloaders Signed-off-by: Pragyan Poudyal --- crates/lib/src/bootc_composefs/boot.rs | 12 +++--------- crates/lib/src/bootc_composefs/delete.rs | 4 ++-- crates/lib/src/parsers/grub_menuconfig.rs | 4 +++- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index ad4e9805e..644f082dc 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -73,7 +73,7 @@ const VMLINUZ: &str = "vmlinuz"; /// directory specified by the BLS spec. We do this because we want systemd-boot to only look at /// our config files and not show the actual UKIs in the bootloader menu /// This is relative to the ESP -pub(crate) const SYSTEMD_UKI_DIR: &str = "EFI/Linux/bootc"; +pub(crate) const BOOTC_UKI_DIR: &str = "EFI/Linux/bootc"; pub(crate) const GLOBAL_UKI_ADDON_DIR: &str = "loader/addons"; @@ -604,7 +604,6 @@ fn write_pe_to_esp( uki_id: &Sha512HashValue, is_insecure_from_opts: bool, mounted_efi: impl AsRef, - bootloader: &Bootloader, ) -> Result> { let efi_bin = read_file(file, &repo).context("Reading .efi binary")?; @@ -652,11 +651,7 @@ fn write_pe_to_esp( // Directory to write the PortableExecutable to let pe_install_dir = mounted_efi.as_ref().join(match pe_type { PEType::UkiAddon if INSTALL_ADDONS_AS_GLOBAL => GLOBAL_UKI_ADDON_DIR, - - PEType::Uki | PEType::UkiAddon => match bootloader { - Bootloader::Grub => EFI_LINUX, - Bootloader::Systemd => SYSTEMD_UKI_DIR, - }, + PEType::Uki | PEType::UkiAddon => BOOTC_UKI_DIR, }); create_dir_all(&pe_install_dir) @@ -811,7 +806,7 @@ fn write_systemd_uki_config( bls_conf .with_title(boot_label.boot_label) .with_cfg(BLSConfigType::UKI { - uki: format!("/{SYSTEMD_UKI_DIR}/{}{}", id.to_hex(), EFI_EXT).into(), + uki: format!("/{BOOTC_UKI_DIR}/{}{}", id.to_hex(), EFI_EXT).into(), }) .with_sort_key(default_sort_key.into()) .with_version(boot_label.version.unwrap_or(default_sort_key.into())); @@ -952,7 +947,6 @@ pub(crate) fn setup_composefs_uki_boot( &id, is_insecure_from_opts, esp_mount.dir.path(), - &bootloader, )?; if let Some(label) = ret { diff --git a/crates/lib/src/bootc_composefs/delete.rs b/crates/lib/src/bootc_composefs/delete.rs index c12d408bb..a45e3ee34 100644 --- a/crates/lib/src/bootc_composefs/delete.rs +++ b/crates/lib/src/bootc_composefs/delete.rs @@ -9,7 +9,7 @@ use crate::{ bootc_composefs::{ boot::{ find_vmlinuz_initrd_duplicates, get_efi_uuid_source, get_esp_partition, - get_sysroot_parent_dev, mount_esp, BootType, SYSTEMD_UKI_DIR, + get_sysroot_parent_dev, mount_esp, BootType, BOOTC_UKI_DIR, }, gc::composefs_gc, repo::open_composefs_repo, @@ -156,7 +156,7 @@ fn delete_kernel_initrd(bls_config: &BLSConfigType, boot_dir: &Dir) -> Result<() #[fn_error_context::context("Deleting UKI and UKI addons {uki_id}")] fn delete_uki(uki_id: &str, esp_mnt: &Dir) -> Result<()> { // TODO: We don't delete global addons here - let ukis = esp_mnt.open_dir(SYSTEMD_UKI_DIR)?; + let ukis = esp_mnt.open_dir(BOOTC_UKI_DIR)?; for entry in ukis.entries_utf8()? { let entry = entry?; diff --git a/crates/lib/src/parsers/grub_menuconfig.rs b/crates/lib/src/parsers/grub_menuconfig.rs index 41e25554c..d7f8ec5d0 100644 --- a/crates/lib/src/parsers/grub_menuconfig.rs +++ b/crates/lib/src/parsers/grub_menuconfig.rs @@ -15,6 +15,8 @@ use nom::{ Err, IResult, Parser, }; +use crate::bootc_composefs::boot::BOOTC_UKI_DIR; + /// Body content of a GRUB menuentry containing parsed commands. #[derive(Debug, PartialEq, Eq)] pub(crate) struct MenuentryBody<'a> { @@ -95,7 +97,7 @@ impl<'a> MenuEntry<'a> { title: format!("{boot_label}: ({uki_id})"), body: MenuentryBody { insmod: vec!["fat", "chain"], - chainloader: format!("/EFI/Linux/{uki_id}.efi"), + chainloader: format!("/{BOOTC_UKI_DIR}/{uki_id}.efi"), search: "--no-floppy --set=root --fs-uuid \"${EFI_PART_UUID}\"", version: 0, extra: vec![],