diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 127b67ca7..9f9e87ea5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -217,9 +217,11 @@ jobs: test_os: ${{ fromJson(needs.compute-ci-level.outputs.integration_os_matrix) }} variant: [ostree, composefs] filesystem: ["ext4", "xfs"] - bootloader: ["grub", "systemd"] + # TODO: Remove "grub" once "grub-cc" is stable + bootloader: ["grub", "grub-cc", "systemd"] boot_type: ["bls", "uki"] seal_state: ["sealed", "unsealed"] + exclude: # https://github.com/bootc-dev/bootc/issues/1812 - test_os: centos-9 @@ -245,6 +247,22 @@ jobs: - variant: ostree bootloader: systemd + # For now only have grub-cc tests in F44 + - test_os: fedora-45 + bootloader: grub-cc + - test_os: fedora-43 + bootloader: grub-cc + - test_os: centos-9 + bootloader: grub-cc + - test_os: centos-10 + bootloader: grub-cc + # Not in ostree + - variant: ostree + bootloader: grub-cc + # Not yet "sealed" + - bootloader: grub-cc + seal_state: sealed + runs-on: ubuntu-24.04 steps: diff --git a/Dockerfile b/Dockerfile index 31f515800..8c07bd102 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,6 +59,7 @@ COPY --from=target-base /target-rootfs/ / ARG SKIP_CONFIGS ARG boot_type ARG seal_state +ARG bootloader # All network-fetching operations: package installs from distro repos, Copr, Koji. # Separated so `just build-fetch --target=fetch` can be retried independently on # transient network failures without re-running the configuration phase. @@ -89,6 +90,20 @@ RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ if [[ ${#pkgs_to_install[@]} -gt 0 ]]; then dnf install -y "${pkgs_to_install[@]}" fi + + if [[ "$bootloader" == "grub-cc" ]]; then + # We have this until we get grub-cc support in bootupd + arch=$(uname -m) + curl -L -o /var/grub-cc.rpm "https://kojipkgs.fedoraproject.org/packages/grub2/2.12/59.eln156/x86_64/grub2-efi-x64-cc-2.12-59.eln156.${arch}.rpm" + mkdir /var/grub-cc + rpm2archive /var/grub-cc.rpm | tar -xvz -C /var/grub-cc + file=$(find /var/grub-cc -name '*.efi') + mkdir /usr/lib/grub-cc + cp $file /usr/lib/grub-cc + rm -rvf /var/grub-cc + rm -rvf /var/grub-cc.rpm + fi + EOF # Note we don't do any customization here yet diff --git a/crates/lib/src/bootc_composefs/backwards_compat/bcompat_boot.rs b/crates/lib/src/bootc_composefs/backwards_compat/bcompat_boot.rs index f624bbead..9f3e9be36 100644 --- a/crates/lib/src/bootc_composefs/backwards_compat/bcompat_boot.rs +++ b/crates/lib/src/bootc_composefs/backwards_compat/bcompat_boot.rs @@ -17,7 +17,7 @@ use crate::{ TYPE1_ENT_PATH_STAGED, UKI_NAME_PREFIX, USER_CFG_STAGED, }, parsers::bls_config::{BLSConfig, BLSConfigType}, - spec::Bootloader, + spec::BootloaderKind, store::Storage, }; use anyhow::{Context, Result}; @@ -323,8 +323,8 @@ pub(crate) async fn prepend_custom_prefix( handle_bls_conf(storage, cfs_cmdline, boot_dir, false)?; } - BootType::Uki => match bootloader { - Bootloader::Grub => { + BootType::Uki => match bootloader.kind()? { + BootloaderKind::GRUBClassic => { let esp = storage.require_esp()?; let mut buf = String::new(); @@ -384,11 +384,9 @@ pub(crate) async fn prepend_custom_prefix( rename_exchange_user_cfg(&grub_dir)?; } - Bootloader::Systemd => { + BootloaderKind::BLSCompatible => { handle_bls_conf(storage, cfs_cmdline, boot_dir, true)?; } - - Bootloader::None => unreachable!("Checked at install time"), }, }; diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index f4a020c5e..72f00828d 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -96,6 +96,7 @@ use crate::bootc_composefs::status::ComposefsCmdline; use crate::bootc_kargs::compute_new_kargs; use crate::composefs_consts::{TYPE1_BOOT_DIR_PREFIX, TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED}; use crate::parsers::bls_config::{BLSConfig, BLSConfigType}; +use crate::spec::BootloaderKind; use crate::task::Task; use crate::{bootc_composefs::repo::open_composefs_repo, store::Storage}; use crate::{bootc_composefs::status::get_sorted_grub_uki_boot_entries, install::PostFetchState}; @@ -582,8 +583,8 @@ pub(crate) fn setup_composefs_bls_boot( compute_new_kargs(mounted_erofs, current_root, &mut cmdline_refs)?; - let (entry_paths, _tmpdir_guard) = match bootloader { - Bootloader::Grub => { + let (entry_paths, _tmpdir_guard) = match bootloader.kind()? { + BootloaderKind::GRUBClassic => { let root = Dir::open_ambient_dir(&root_path, ambient_authority()) .context("Opening root path")?; @@ -607,7 +608,7 @@ pub(crate) fn setup_composefs_bls_boot( ) } - Bootloader::Systemd => { + BootloaderKind::BLSCompatible => { let efi_mount = mount_esp(&esp_device).context("Mounting ESP")?; let mounted_efi = Utf8PathBuf::from(efi_mount.dir.path().as_str()?); @@ -622,8 +623,6 @@ pub(crate) fn setup_composefs_bls_boot( Some(efi_mount), ) } - - Bootloader::None => unreachable!("Checked at install time"), }; let (bls_config, boot_digest, os_id) = match &entry { @@ -1164,14 +1163,14 @@ pub(crate) fn setup_composefs_uki_boot( let boot_digest = uki_info.boot_digest.clone(); - match bootloader { - Bootloader::Grub => { + match bootloader.kind()? { + BootloaderKind::GRUBClassic => { write_grub_uki_menuentry(root_path, &setup_type, uki_info.boot_label, id, &esp_device)? } - Bootloader::Systemd => write_systemd_uki_config(&esp_mount.fd, &setup_type, uki_info, id)?, - - Bootloader::None => unreachable!("Checked at install time"), + BootloaderKind::BLSCompatible => { + write_systemd_uki_config(&esp_mount.fd, &setup_type, uki_info, id)? + } }; Ok(boot_digest) @@ -1369,13 +1368,48 @@ pub(crate) async fn setup_composefs_boot( &root_setup.device_info.require_single_root()?, boot_uuid, )?; - } else if postfetch.detected_bootloader == Bootloader::Grub { + } else if matches!( + postfetch.detected_bootloader, + Bootloader::Grub | Bootloader::GrubCC + ) { crate::bootloader::install_via_bootupd( &root_setup.device_info, &root_setup.physical_root_path, &state.config_opts, None, )?; + + // FIXME: Remove this hack once we have support in bootupd + if matches!(postfetch.detected_bootloader, Bootloader::GrubCC) { + root_setup + .physical_root + .remove_dir_all("boot/grub2") + .context("removing grub2")?; + + let (os_id, ..) = parse_os_release(mounted_root.dir())? + .ok_or_else(|| anyhow::anyhow!("Failed to parse os-release"))?; + + let dir = format!("EFI/{os_id}"); + + // Files are in EFI// + let efis_dir = mounted_root + .open_esp_dir() + .context("opening esp")? + .open_dir(&dir) + .with_context(|| format!("Opening {dir}"))?; + + efis_dir + .remove_file_optional("bootuuid.cfg") + .context("Removing bootuuid.cfg")?; + efis_dir + .remove_file_optional("grub.cfg") + .context("Removing grub.cfg")?; + + mounted_root + .dir() + .copy("usr/lib/grub-cc/grubx64-cc.efi", &efis_dir, "grubx64.efi") + .context("Copying grub-cc binary")?; + } } else { crate::bootloader::install_systemd_boot( &mounted_root, diff --git a/crates/lib/src/bootc_composefs/delete.rs b/crates/lib/src/bootc_composefs/delete.rs index da4b61ecb..999afaa7c 100644 --- a/crates/lib/src/bootc_composefs/delete.rs +++ b/crates/lib/src/bootc_composefs/delete.rs @@ -15,7 +15,7 @@ use crate::{ TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED, USER_CFG_STAGED, }, parsers::bls_config::{BLSConfigType, parse_bls_config}, - spec::{BootEntry, Bootloader, DeploymentEntry}, + spec::{BootEntry, BootloaderKind, DeploymentEntry}, status::Slot, store::{BootedComposefs, Storage}, }; @@ -145,20 +145,18 @@ fn delete_depl_boot_entries( ) -> Result<()> { let boot_dir = storage.require_boot_dir()?; - match deployment.deployment.bootloader { - Bootloader::Grub => match deployment.deployment.boot_type { + match deployment.deployment.bootloader.kind()? { + BootloaderKind::GRUBClassic => match deployment.deployment.boot_type { BootType::Bls => delete_type1_conf_file(deployment, boot_dir, deleting_staged), BootType::Uki => { remove_grub_menucfg_entry(&deployment.deployment.verity, boot_dir, deleting_staged) } }, - Bootloader::Systemd => { + BootloaderKind::BLSCompatible => { // For Systemd UKI as well, we use .conf files delete_type1_conf_file(deployment, boot_dir, deleting_staged) } - - Bootloader::None => unreachable!("Checked at install time"), } } diff --git a/crates/lib/src/bootc_composefs/finalize.rs b/crates/lib/src/bootc_composefs/finalize.rs index e1d67d99f..9e0f5e3d4 100644 --- a/crates/lib/src/bootc_composefs/finalize.rs +++ b/crates/lib/src/bootc_composefs/finalize.rs @@ -5,7 +5,7 @@ use crate::bootc_composefs::gc::{GCOpts, composefs_gc}; use crate::bootc_composefs::rollback::{rename_exchange_bls_entries, rename_exchange_user_cfg}; use crate::bootc_composefs::status::get_composefs_status; use crate::composefs_consts::STATE_DIR_ABS; -use crate::spec::Bootloader; +use crate::spec::BootloaderKind; use crate::store::{BootedComposefs, Storage}; use anyhow::{Context, Result}; use bootc_initramfs_setup::mount_composefs_image; @@ -131,8 +131,8 @@ pub(crate) async fn composefs_backend_finalize( let boot_dir = storage.require_boot_dir()?; - match booted_composefs.bootloader { - Bootloader::Grub => match staged_composefs.boot_type { + match booted_composefs.bootloader.kind()? { + BootloaderKind::GRUBClassic => match staged_composefs.boot_type { BootType::Bls => { let entries_dir = boot_dir.open_dir("loader")?; rename_exchange_bls_entries(&entries_dir)?; @@ -140,12 +140,10 @@ pub(crate) async fn composefs_backend_finalize( BootType::Uki => finalize_staged_grub_uki(boot_dir)?, }, - Bootloader::Systemd => { + BootloaderKind::BLSCompatible => { let entries_dir = boot_dir.open_dir("loader")?; rename_exchange_bls_entries(&entries_dir)?; } - - Bootloader::None => unreachable!("Checked at install time"), }; // Now that we have successfully updated bootloader entires, we can GC the unreferenced ones diff --git a/crates/lib/src/bootc_composefs/rollback.rs b/crates/lib/src/bootc_composefs/rollback.rs index 106919f04..b61d6ac15 100644 --- a/crates/lib/src/bootc_composefs/rollback.rs +++ b/crates/lib/src/bootc_composefs/rollback.rs @@ -12,7 +12,7 @@ use crate::bootc_composefs::boot::{ }; use crate::bootc_composefs::status::{get_composefs_status, get_sorted_type1_boot_entries}; use crate::composefs_consts::TYPE1_ENT_PATH_STAGED; -use crate::spec::Bootloader; +use crate::spec::{Bootloader, BootloaderKind}; use crate::store::{BootedComposefs, Storage}; use crate::{ bootc_composefs::{boot::get_efi_uuid_source, status::get_sorted_grub_uki_boot_entries}, @@ -224,8 +224,8 @@ pub(crate) async fn composefs_rollback( let boot_dir = storage.require_boot_dir()?; - match &rollback_entry.bootloader { - Bootloader::Grub => match rollback_entry.boot_type { + match &rollback_entry.bootloader.kind()? { + BootloaderKind::GRUBClassic => match rollback_entry.boot_type { BootType::Bls => { rollback_composefs_entries(boot_dir, rollback_entry.bootloader.clone())?; } @@ -234,12 +234,10 @@ pub(crate) async fn composefs_rollback( } }, - Bootloader::Systemd => { + BootloaderKind::BLSCompatible => { // We use BLS entries for systemd UKI as well rollback_composefs_entries(boot_dir, rollback_entry.bootloader.clone())?; } - - Bootloader::None => unreachable!("Checked at install time"), } if reverting { diff --git a/crates/lib/src/bootc_composefs/status.rs b/crates/lib/src/bootc_composefs/status.rs index ac1b261dd..f1fca53f6 100644 --- a/crates/lib/src/bootc_composefs/status.rs +++ b/crates/lib/src/bootc_composefs/status.rs @@ -25,7 +25,7 @@ use crate::{ bls_config::{BLSConfig, BLSConfigType, parse_bls_config}, grub_menuconfig::{MenuEntry, parse_grub_menuentry_file}, }, - spec::{BootEntry, BootOrder, Host, HostSpec, ImageStatus}, + spec::{BootEntry, BootOrder, BootloaderKind, Host, HostSpec, ImageStatus}, store::Storage, utils::{EfiError, read_uefi_var}, }; @@ -339,8 +339,8 @@ pub(crate) fn list_bootloader_entries(storage: &Storage) -> Result { + let entries = match bootloader.kind()? { + BootloaderKind::GRUBClassic => { // Grub entries are always in boot let grub_dir = boot_dir.open_dir("grub2").context("Opening grub dir")?; @@ -368,9 +368,7 @@ pub(crate) fn list_bootloader_entries(storage: &Storage) -> Result list_type1_entries(boot_dir)?, - - Bootloader::None => unreachable!("Checked at install time"), + BootloaderKind::BLSCompatible => list_type1_entries(boot_dir)?, }; Ok(entries) @@ -414,10 +412,14 @@ pub(crate) fn get_bootloader() -> Result { let bootloader = match read_uefi_var(EFI_LOADER_INFO) { Ok(loader) => { if loader.to_lowercase().contains("systemd-boot") { - Bootloader::Systemd - } else { - Bootloader::Grub + return Ok(Bootloader::Systemd); + } + + if loader.to_lowercase().contains("grub cc") { + return Ok(Bootloader::GrubCC); } + + return Ok(Bootloader::Grub); } Err(efi_error) => match efi_error { @@ -869,8 +871,11 @@ async fn composefs_deployment_status_from( let booted_cfs = host.require_composefs_booted()?; let mut grub_menu_string = String::new(); - let (is_rollback_queued, sorted_bls_config, grub_menu_entries) = match booted_cfs.bootloader { - Bootloader::Grub => match boot_type { + let (is_rollback_queued, sorted_bls_config, grub_menu_entries) = match booted_cfs + .bootloader + .kind()? + { + BootloaderKind::GRUBClassic => match boot_type { BootType::Bls => { let bls_configs = get_sorted_type1_boot_entries(boot_dir, false)?; let bls_config = bls_configs @@ -911,7 +916,7 @@ async fn composefs_deployment_status_from( }, // We will have BLS stuff and the UKI stuff in the same DIR - Bootloader::Systemd => { + BootloaderKind::BLSCompatible => { let bls_configs = get_sorted_type1_boot_entries(boot_dir, true)?; let bls_config = bls_configs .first() @@ -934,8 +939,6 @@ async fn composefs_deployment_status_from( (is_rollback_queued, Some(bls_configs), None) } - - Bootloader::None => unreachable!("Checked at install time"), }; // Determine rollback deployment by matching extra deployment boot entries against entires read from /boot diff --git a/crates/lib/src/bootc_composefs/update.rs b/crates/lib/src/bootc_composefs/update.rs index a1fb2722f..13cad88b6 100644 --- a/crates/lib/src/bootc_composefs/update.rs +++ b/crates/lib/src/bootc_composefs/update.rs @@ -12,6 +12,7 @@ use ocidir::cap_std::ambient_authority; use ostree_ext::container::ManifestDiff; use crate::bootc_composefs::gc::GCOpts; +use crate::spec::BootloaderKind; use crate::{ bootc_composefs::{ boot::{BootSetupType, BootType, setup_composefs_bls_boot, setup_composefs_uki_boot}, @@ -30,7 +31,7 @@ use crate::{ COMPOSEFS_STAGED_DEPLOYMENT_FNAME, COMPOSEFS_TRANSIENT_STATE_DIR, STATE_DIR_RELATIVE, TYPE1_ENT_PATH_STAGED, USER_CFG_STAGED, }, - spec::{Bootloader, Host, ImageReference}, + spec::{Host, ImageReference}, store::{BootedComposefs, ComposefsRepository, Storage}, }; @@ -170,8 +171,8 @@ pub(crate) fn validate_update( // Remove staged bootloader entries, if any // GC should take care of the UKI PEs and other binaries - match get_bootloader()? { - Bootloader::Grub => match booted.boot_type { + match get_bootloader()?.kind()? { + BootloaderKind::GRUBClassic => match booted.boot_type { BootType::Bls => rm_staged_type1_ent(boot_dir)?, BootType::Uki => { @@ -184,9 +185,7 @@ pub(crate) fn validate_update( } }, - Bootloader::Systemd => rm_staged_type1_ent(boot_dir)?, - - Bootloader::None => unreachable!("Checked at install time"), + BootloaderKind::BLSCompatible => rm_staged_type1_ent(boot_dir)?, } // Remove state directory diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index c9fcaf88c..9fdb8c37b 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -1291,6 +1291,7 @@ pub(crate) fn exec_in_host_mountns(args: &[std::ffi::OsString]) -> Result<()> { Err(Command::new(cmd).args(args).arg0(bootc_utils::NAME).exec()).context("exec")? } +#[derive(Debug)] pub(crate) struct RootSetup { #[cfg(feature = "install-to-disk")] luks_device: Option, @@ -1874,7 +1875,7 @@ async fn install_with_sysroot( Some(&deployment_path.as_str()), )?; } - Bootloader::Systemd => { + Bootloader::Systemd | Bootloader::GrubCC => { anyhow::bail!("bootupd is required for ostree-based installs"); } Bootloader::None => { diff --git a/crates/lib/src/install/baseline.rs b/crates/lib/src/install/baseline.rs index cfd8878e1..25e1b1a65 100644 --- a/crates/lib/src/install/baseline.rs +++ b/crates/lib/src/install/baseline.rs @@ -55,7 +55,7 @@ fn use_discoverable_partitions(state: &State) -> bool { // systemd-boot always supports BLI matches!( state.config_opts.bootloader, - Some(crate::spec::Bootloader::Systemd) + Some(crate::spec::Bootloader::Systemd) | Some(crate::spec::Bootloader::GrubCC) ) } diff --git a/crates/lib/src/spec.rs b/crates/lib/src/spec.rs index ea2c5a77c..9fa8d7a25 100644 --- a/crates/lib/src/spec.rs +++ b/crates/lib/src/spec.rs @@ -237,16 +237,29 @@ pub enum Bootloader { /// Use Grub as the bootloader #[default] Grub, + /// Use Grub for confidential clusters as the bootloader + #[serde(rename = "grub-cc")] + GrubCC, /// Use SystemdBoot as the bootloader Systemd, /// Don't use a bootloader managed by bootc None, } +#[derive(Debug, PartialEq, Eq)] +pub enum BootloaderKind { + /// Bootloader that support Bootloader Specification + /// GrubCC and SystemdBoot + BLSCompatible, + /// Classic Grub + GRUBClassic, +} + impl Display for Bootloader { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let string = match self { Bootloader::Grub => "grub", + Bootloader::GrubCC => "grub-cc", Bootloader::Systemd => "systemd", Bootloader::None => "none", }; @@ -261,6 +274,7 @@ impl FromStr for Bootloader { fn from_str(value: &str) -> Result { match value { "grub" => Ok(Self::Grub), + "grub-cc" => Ok(Self::GrubCC), "systemd" => Ok(Self::Systemd), "none" => Ok(Self::None), unrecognized => Err(anyhow::anyhow!("Unrecognized bootloader: '{unrecognized}'")), @@ -268,6 +282,18 @@ impl FromStr for Bootloader { } } +impl Bootloader { + /// Returns whether the Bootloader is BLSCompatible + /// Throws and error if Bootloader is None + pub(crate) fn kind(&self) -> Result { + match self { + Bootloader::Grub => Ok(BootloaderKind::GRUBClassic), + Bootloader::Systemd | Bootloader::GrubCC => Ok(BootloaderKind::BLSCompatible), + Bootloader::None => anyhow::bail!("Bootloader was None"), + } + } +} + /// A bootable entry #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "camelCase")] diff --git a/crates/lib/src/store/mod.rs b/crates/lib/src/store/mod.rs index 653fd2a25..563594f43 100644 --- a/crates/lib/src/store/mod.rs +++ b/crates/lib/src/store/mod.rs @@ -118,7 +118,7 @@ use crate::bootc_composefs::boot::{EFI_LINUX, mount_esp}; use crate::bootc_composefs::status::{ComposefsCmdline, composefs_booted, get_bootloader}; use crate::lsm; use crate::podstorage::CStorage; -use crate::spec::{Bootloader, ImageStatus}; +use crate::spec::{BootloaderKind, ImageStatus}; use crate::utils::{deployment_fd, open_dir_remount_rw}; /// See @@ -332,11 +332,14 @@ impl BootedStorage { let esp_dev = root_dev.find_first_colocated_esp()?; let esp_mount = mount_esp(&esp_dev.path())?; - let boot_dir = match get_bootloader()? { - Bootloader::Grub => physical_root.open_dir("boot").context("Opening boot")?, + let boot_dir = match get_bootloader()?.kind()? { + BootloaderKind::GRUBClassic => { + physical_root.open_dir("boot").context("Opening boot")? + } // NOTE: Handle XBOOTLDR partitions here if and when we use it - Bootloader::Systemd => esp_mount.fd.try_clone().context("Cloning fd")?, - Bootloader::None => unreachable!("Checked at install time"), + BootloaderKind::BLSCompatible => { + esp_mount.fd.try_clone().context("Cloning fd")? + } }; let storage = Storage { @@ -524,16 +527,15 @@ impl Storage { // boot dir in case of systemd-boot points to the ESP, but we store // the actual binaries inside ESP/EFI/Linux - let boot_dir = match get_bootloader()? { - Bootloader::Grub => boot_dir.try_clone()?, - Bootloader::Systemd => { + let boot_dir = match get_bootloader()?.kind()? { + BootloaderKind::GRUBClassic => boot_dir.try_clone()?, + BootloaderKind::BLSCompatible => { let boot_dir = boot_dir .open_dir(EFI_LINUX) .with_context(|| format!("Opening {EFI_LINUX}"))?; boot_dir } - Bootloader::None => anyhow::bail!("Unknown bootloader"), }; Ok(boot_dir) diff --git a/crates/xtask/src/bcvk.rs b/crates/xtask/src/bcvk.rs index 7d883d205..a7508f6cf 100644 --- a/crates/xtask/src/bcvk.rs +++ b/crates/xtask/src/bcvk.rs @@ -110,7 +110,10 @@ impl BcvkInstallOpts { Run 'just generate-secureboot-keys' to generate them." ); } - } else if matches!(self.bootloader, Some(Bootloader::Systemd)) { + } else if matches!( + self.bootloader, + Some(Bootloader::Systemd) | Some(Bootloader::GrubCC) + ) { Ok(vec!["--firmware=uefi-insecure".into()]) } else { Ok(Vec::new()) diff --git a/crates/xtask/src/xtask.rs b/crates/xtask/src/xtask.rs index 79f4ffff6..8e54b5772 100644 --- a/crates/xtask/src/xtask.rs +++ b/crates/xtask/src/xtask.rs @@ -151,6 +151,8 @@ pub(crate) struct LocalRustDepsArgs { pub enum Bootloader { /// grub as bootloader Grub, + /// grub cc as bootloader + GrubCC, /// systemd-boot as bootloader Systemd, } @@ -159,6 +161,7 @@ impl Display for Bootloader { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Bootloader::Grub => f.write_str("grub"), + Bootloader::GrubCC => f.write_str("grub-cc"), Bootloader::Systemd => f.write_str("systemd"), } } diff --git a/docs/src/host-v1.schema.json b/docs/src/host-v1.schema.json index db716c3f1..0503068a7 100644 --- a/docs/src/host-v1.schema.json +++ b/docs/src/host-v1.schema.json @@ -214,6 +214,11 @@ "type": "string", "const": "grub" }, + { + "description": "Use Grub for confidential clusters as the bootloader", + "type": "string", + "const": "grub-cc" + }, { "description": "Use SystemdBoot as the bootloader", "type": "string", diff --git a/docs/src/man/bootc-install-to-disk.8.md b/docs/src/man/bootc-install-to-disk.8.md index 5b4a7bd39..6850d0ba9 100644 --- a/docs/src/man/bootc-install-to-disk.8.md +++ b/docs/src/man/bootc-install-to-disk.8.md @@ -166,6 +166,7 @@ set `discoverable-partitions = true` in their install configuration Possible values: - grub + - grub-cc - systemd - none diff --git a/docs/src/man/bootc-install-to-existing-root.8.md b/docs/src/man/bootc-install-to-existing-root.8.md index 4b4f8925d..9cfaec6ef 100644 --- a/docs/src/man/bootc-install-to-existing-root.8.md +++ b/docs/src/man/bootc-install-to-existing-root.8.md @@ -212,6 +212,7 @@ of migrating the fstab entries. See the "Injecting kernel arguments" section abo Possible values: - grub + - grub-cc - systemd - none diff --git a/docs/src/man/bootc-install-to-filesystem.8.md b/docs/src/man/bootc-install-to-filesystem.8.md index f46892de3..a48ecc50a 100644 --- a/docs/src/man/bootc-install-to-filesystem.8.md +++ b/docs/src/man/bootc-install-to-filesystem.8.md @@ -120,6 +120,7 @@ is currently expected to be empty by default. Possible values: - grub + - grub-cc - systemd - none diff --git a/tmt/tests/booted/readonly/030-test-composefs.nu b/tmt/tests/booted/readonly/030-test-composefs.nu index 58bbd94c5..801c5a7ee 100644 --- a/tmt/tests/booted/readonly/030-test-composefs.nu +++ b/tmt/tests/booted/readonly/030-test-composefs.nu @@ -7,6 +7,12 @@ def parse_cmdline [] { open /proc/cmdline | str trim | split row " " } +def find_root_eq_in_cmdline [bootloader: string] { + let cmdline = parse_cmdline + let has_root_param = ($cmdline | any { |param| $param | str starts-with 'root=' }) + assert (not $has_root_param) $"($bootloader) image should not have root= in kernel cmdline; systemd-gpt-auto-generator should discover the root partition via DPS" +} + # Detect composefs by checking if composefs field is present let st = bootc status --json | from json let is_composefs = (tap is_composefs) @@ -24,11 +30,14 @@ if $expecting_composefs { 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" + find_root_eq_in_cmdline "systemd-boot" } } + + # GrubCC also supports BLS and shouldn't need root= + if $bootloader == "grub-cc" { + find_root_eq_in_cmdline "grub-cc" + } } if $is_composefs { diff --git a/tmt/tests/booted/test-composefs-gc.nu b/tmt/tests/booted/test-composefs-gc.nu index d19a01efe..4262b583b 100644 --- a/tmt/tests/booted/test-composefs-gc.nu +++ b/tmt/tests/booted/test-composefs-gc.nu @@ -88,9 +88,9 @@ def third_boot [] { # Also assert we have two different kernel + initrd pairs let booted_verity = (bootc status --json | from json).status.booted.composefs.verity - let bootloader = (bootc status --json | from json).status.booted.composefs.bootloader + let bootloader = ((bootc status --json | from json).status.booted.composefs.bootloader | str downcase) - let boot_dir = if ($bootloader | str downcase) == "systemd" { + let boot_dir = if $bootloader == "systemd" or $bootloader == "grub-cc" { # TODO: Some concrete API for this would be great mkdir /var/tmp/efi mount /dev/disk/by-partlabel/EFI-SYSTEM /var/tmp/efi @@ -123,9 +123,9 @@ def third_boot [] { } def fourth_boot [] { - let bootloader = (bootc status --json | from json).status.booted.composefs.bootloader + let bootloader = ((bootc status --json | from json).status.booted.composefs.bootloader | str downcase) - if ($bootloader | str downcase) == "systemd" { + if $bootloader == "systemd" or $bootloader == "grub-cc" { # TODO: Some concrete API for this would be great mkdir /var/tmp/efi mount /dev/disk/by-partlabel/EFI-SYSTEM /var/tmp/efi 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 5025b0a76..8d588dd48 100644 --- a/tmt/tests/booted/test-install-to-filesystem-var-mount.sh +++ b/tmt/tests/booted/test-install-to-filesystem-var-mount.sh @@ -141,11 +141,15 @@ echo "Filesystem layout:" mount | grep /var/mnt/target || true df -h /var/mnt/target /var/mnt/target/boot /var/mnt/target/boot/efi /var/mnt/target/var -COMPOSEFS_BACKEND=() +bootloader=$(bootc status --json | jq -r '.status.booted.composefs.bootloader' | tr '[:upper:]' '[:lower:]') + +COMPOSEFS_BACKEND_PARAMS=() KARGS=("--karg=root=UUID=$ROOT_UUID") if [[ $is_composefs != "null" ]]; then - COMPOSEFS_BACKEND+=("--composefs-backend") + COMPOSEFS_BACKEND_PARAMS+=("--composefs-backend") + COMPOSEFS_BACKEND_PARAMS+=("--bootloader" "${bootloader}") + tune2fs -O verity /dev/BL/var02 tune2fs -O verity /dev/BL/root02 @@ -164,7 +168,7 @@ podman run \ "$TARGET_IMAGE" \ bootc install to-filesystem \ --disable-selinux \ - "${COMPOSEFS_BACKEND[@]}" \ + "${COMPOSEFS_BACKEND_PARAMS[@]}" \ "${KARGS[@]}" \ --root-mount-spec=UUID="$ROOT_UUID" \ --boot-mount-spec=UUID="$BOOT_UUID" \ @@ -186,16 +190,12 @@ else # It works for now as the CI runs separately for each bootloader, but we need to get the # bootloader from the installed systemd if we wish to run the tests locally without rebuilding the images # This probably also happens in other tests, one instance is install-outside-container - bootloader=$(bootc status --json | jq '.status.booted.composefs.bootloader' | tr '[:upper:]' '[:lower:]') - bootloader=${bootloader//\"/} - if [[ $bootloader == "grub" ]]; then test -d /var/mnt/target/boot/grub2 || test -d /var/mnt/target/boot/loader else test -d /var/mnt/target/boot/efi/EFI test -d /var/mnt/target/boot/efi/loader/entries fi - fi diff --git a/tmt/tests/booted/test-multi-device-esp.nu b/tmt/tests/booted/test-multi-device-esp.nu index b3f69fcf3..40fd4acb9 100644 --- a/tmt/tests/booted/test-multi-device-esp.nu +++ b/tmt/tests/booted/test-multi-device-esp.nu @@ -437,8 +437,9 @@ def main [] { # supports GRUB today. Skip when the image uses systemd-boot. if (tap is_composefs) { let st = bootc status --json | from json - if ($st.status.booted.composefs.bootloader | str downcase) == "systemd" { - print "SKIP: multi-device ESP test not supported with systemd-boot" + let bootloader = $st.status.booted.composefs.bootloader | str downcase + if $bootloader == "systemd" or $bootloader == "grub-cc" { + print $"SKIP: multi-device ESP test not supported with ($bootloader)" tap ok return }