Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions crates/lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2351,6 +2351,35 @@ fn remove_all_except_loader_dirs(bootdir: &Dir, is_ostree: bool) -> Result<()> {
Ok(())
}

/// Subdirectories of the EFI System Partition which hold the bootloader
/// installed by bootc (via bootupd or systemd-boot).
const ESP_BOOTLOADER_DIRS: &[&str] = &["EFI", "loader"];

/// Remove the bootloader content from the ESP, preserving everything else.
///
/// The ESP is commonly shared with earlier boot stages or platform firmware
/// which bootc does not manage and cannot recreate. For example, on Apple
/// Silicon the Asahi installer stores the stage 2 bootloader (`m1n1/boot.bin`),
/// the device firmware payload (`vendorfw/`) and the U-Boot EFI variable store
/// (`ubootefi.var`) on the ESP; wiping them leaves the machine unbootable.
#[context("Removing bootloader content from EFI system partition")]
fn clean_esp_bootloader_dirs(efidir: &Dir) -> Result<()> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

People might also have custom bootloader config files in ESP/loader. I think we should read the config files and only remove the ones we own and the boot binaries referenced by them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe but I wouldn't mind a second opinion on that... For example... Would the pre-existing boot entries be completely broken after this? I'm open to it, but a second opinion could be interesting

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more or less related to #2243. I don't think it's a blocker per se, but if we're going the distance to do this, then maybe we should just handle all the cases.

Would the pre-existing boot entries be completely broken after this?

we're deleting the pre-existing boot entries... so, yes.

@ericcurtin ericcurtin Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, maybe I didn't explain myself well enough... Even if we left the pre-bootc boot entries around... Would they even boot at that point? Would they even be useful after the conversion to a bootc system? (genuine question, haven't checked how destructive "bootc install to-existing-root" is to userspace, initramfs, kernel, etc. This is useful in Asahi because of the files required above needed to boot, not because of rollback to pre-bootc case)

for name in ESP_BOOTLOADER_DIRS {
let Some(subdir) = efidir
.open_dir_optional(name)
.with_context(|| format!("Opening {name}"))?
else {
continue;
};
remove_all_in_dir_no_xdev(&subdir, false)
.with_context(|| format!("Removing directory contents: {name}"))?;
efidir
.remove_dir(name)
.with_context(|| format!("Removing directory: {name}"))?;
}
Ok(())
}

#[context("Removing boot directory content")]
fn clean_boot_directories(rootfs: &Dir, rootfs_path: &Utf8Path, is_ostree: bool) -> Result<()> {
let bootdir =
Expand All @@ -2365,13 +2394,14 @@ fn clean_boot_directories(rootfs: &Dir, rootfs_path: &Utf8Path, is_ostree: bool)
// This should not remove /boot/efi note.
remove_all_except_loader_dirs(&bootdir, is_ostree).context("Emptying /boot")?;

// TODO: we should also support not wiping the ESP.
// Only remove the bootloader content we are about to reinstall; the rest
// of the ESP may hold platform firmware we must not touch.
if ARCH_USES_EFI {
if let Some(efidir) = bootdir
.open_dir_optional(crate::bootloader::EFI_DIR)
.context("Opening /boot/efi")?
{
remove_all_in_dir_no_xdev(&efidir, false).context("Emptying EFI system partition")?;
clean_esp_bootloader_dirs(&efidir)?;
}
}

Expand Down Expand Up @@ -3050,6 +3080,43 @@ mod tests {
Ok(())
}

// Verify that only the bootloader directories are removed from the ESP
// and that unrelated platform content is preserved.
#[test]
fn test_clean_esp_bootloader_dirs() -> Result<()> {
let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;

// Owned by bootc (bootupd/grub and systemd-boot)
td.create_dir_all("EFI/BOOT")?;
td.write("EFI/BOOT/BOOTAA64.EFI", b"shim")?;
td.create_dir_all("EFI/fedora")?;
td.write("EFI/fedora/grub.cfg", b"cfg")?;
td.create_dir_all("loader/entries")?;
td.write("loader/entries/foo.conf", b"entry")?;
td.write("loader/loader.conf", b"loader")?;

// Not owned by bootc: Apple Silicon (Asahi) boot chain and firmware
td.create_dir_all("m1n1")?;
td.write("m1n1/boot.bin", b"m1n1+u-boot")?;
td.create_dir_all("vendorfw")?;
td.write("vendorfw/firmware.cpio", b"firmware")?;
td.write("ubootefi.var", b"efivars")?;

clean_esp_bootloader_dirs(&td).unwrap();

assert!(!td.exists("EFI"));
assert!(!td.exists("loader"));
assert_eq!(td.read("m1n1/boot.bin")?, b"m1n1+u-boot");
assert_eq!(td.read("vendorfw/firmware.cpio")?, b"firmware");
assert_eq!(td.read("ubootefi.var")?, b"efivars");

// Running against an ESP without bootloader content is a no-op
clean_esp_bootloader_dirs(&td).unwrap();
assert_eq!(td.entries()?.count(), 3);

Ok(())
}

#[test]
fn test_read_boot_fstab_entry() -> Result<()> {
let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
Expand Down
8 changes: 6 additions & 2 deletions docs/src/bootc-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,12 @@ Set the environment variable `BOOTC_DIRECT_IO=on` to create the loopback device

This is a variant of `install to-filesystem`, which maximizes convenience for using
an existing Linux system, converting it into the target container image. Note that
the `/boot` (and `/boot/efi`) partitions *will be reinitialized* - so this is a
somewhat destructive operation for the existing Linux installation.
the `/boot` partition *will be reinitialized*, and the bootloader content on the
EFI System Partition (the `EFI/` and `loader/` directories) *will be replaced* - so
this is a somewhat destructive operation for the existing Linux installation.
Other content on the EFI System Partition (for example firmware or earlier boot
stages placed there by the platform, such as `m1n1/` and `vendorfw/` on Apple
Silicon systems installed with the Asahi installer) is preserved.

Also, because the filesystem is reused, it's required that the target system kernel
support the root storage setup already initialized.
Expand Down
Loading