Skip to content

Integration for GrubCC and SystemdBoot#1113

Merged
Johan-Liebert1 merged 21 commits into
coreos:mainfrom
Johan-Liebert1:grub-cc
Jul 13, 2026
Merged

Integration for GrubCC and SystemdBoot#1113
Johan-Liebert1 merged 21 commits into
coreos:mainfrom
Johan-Liebert1:grub-cc

Conversation

@Johan-Liebert1

@Johan-Liebert1 Johan-Liebert1 commented Jun 12, 2026

Copy link
Copy Markdown
Member

Add Bootloader enum

Now that we are going to support multiple bootloaders, we need a way to
keep track of which one we're operating on


For bootupd generate-update-metadata we now generate metadata for all
found bootloaders. The final json will look like

{
  "timestamp": "2026-06-10T09:52:58.107743026Z",
  "version": "grub-cc-1:2.12-59.fc45,grub2-1:2.12-58.fc44,shim-16.1-5",
  "versions": [
    { "name": "grub-cc", "rpm_evr": "1:2.12-59.fc45" },
    { "name": "grub2", "rpm_evr": "1:2.12-58.fc44" },
    { "name": "shim", "rpm_evr": "16.1-5" }
  ]
}

This allows us to have multiple bootloaders in a single image and
install/update based on preference.

During install/update, we check to bootloader and filter out any and all
entries from the update metadata that do not match or are not required
for the bootloader


For grub we store the statefile in /sysroot/boot/bootupd.json. For
grub-cc, and in future systemd-boot, we won't have a /boot, so we now
store the state pre bootloader.

Grub2: Statefile is still in /sysroot/boot/bootupd.json
GrubCC: Statefile is stored in all the ESPs

Store the sysroot_path in StateLockGuard which we require for mounting
the ESP. Also, mount the ESP at /boot which is what the BLS spec suggest


See: #1080

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for multiple bootloaders, specifically adding a new GrubCC option alongside the default Grub. It updates the saved state, component installation, update, and status mechanisms to dynamically detect and filter metadata based on the active bootloader, including handling state files stored in the ESP for GrubCC. The review feedback identifies a potential mount leak where the was_mounted flag is not reset upon unmounting, several instances of unnecessary double referencing, and opportunities to make the code more idiomatic by simplifying OnceLock usage and removing redundant iterator adapters.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/efi.rs
Comment thread src/efi.rs Outdated
Comment thread src/backend/statefile.rs Outdated
Comment thread src/backend/statefile.rs
Comment thread src/backend/statefile.rs
Comment thread src/bootloader.rs
Comment thread src/model.rs
@Johan-Liebert1
Johan-Liebert1 force-pushed the grub-cc branch 2 times, most recently from 2ffcc16 to e8da148 Compare June 12, 2026 08:56
@Johan-Liebert1
Johan-Liebert1 marked this pull request as draft June 12, 2026 09:31
@Johan-Liebert1
Johan-Liebert1 force-pushed the grub-cc branch 5 times, most recently from 77321d0 to fb028b8 Compare June 14, 2026 03:19
@Johan-Liebert1
Johan-Liebert1 marked this pull request as ready for review June 15, 2026 05:10
@Johan-Liebert1
Johan-Liebert1 force-pushed the grub-cc branch 2 times, most recently from aa72462 to e7bf29d Compare June 15, 2026 05:35
Comment thread src/cli/bootupd.rs Outdated
Comment thread src/backend/statefile.rs
Comment thread src/backend/statefile.rs Outdated
Comment thread src/backend/statefile.rs Outdated
Comment thread src/backend/statefile.rs Outdated
Comment thread src/backend/statefile.rs
Comment thread src/cli/bootupd.rs Outdated
Comment thread src/bootloader.rs
Now that we are going to support multiple bootloaders, we need a way to
keep track of which one we're operating on

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Generate metadata for only a particular bootloader, either Grub or
GrubCC

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Add `is_bootloader_supported` function to figure out if bootloader is
supported by the component or not. Skip installs and metadata generation
for the component if it does not support the bootloader

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
For grub we store the statefile in `/sysroot/boot/bootupd.json`. For
grub-cc, and in future systemd-boot, we won't have a `/boot`, so we now
store the state pre bootloader.

Grub2: Statefile is still in `/sysroot/boot/bootupd.json`
GrubCC: Statefile is stored in all the ESPs

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Comment thread src/backend/statefile.rs
@Johan-Liebert1

Copy link
Copy Markdown
Member Author

A lot of changes here. I'll test it out in fcos as an override

Comment thread Dockerfile.bls
grubName="grubaa64.efi"
fi

cp /usr/lib/systemd/boot/efi/systemd-boot*.efi "/usr/lib/efi/systemd-boot/${evr}/EFI/$ID/$grubName"

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.

Hmmm, is this really required? I guess it must be - it's us taking over bootctl install and bootctl update.

That feels...unnecessary?

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.

Now that I spend a bit of time on this, is this case actually "install systemd-boot pretending to be grub" so it gets chainloaded by shim? That looks like what this is.

That feels like a clearly distinct case of things here and really worth clearly documenting.

OK right, and we're doing the same with grub-cc?

Hmmm, I guess it's kind of an assumption woven into things here that we're using shim, which is probably the right one by default, but I think we do ultimately need to handle non-shim cases too.

I guess this isn't blocking per se, but we should have a note about this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Now that I spend a bit of time on this, is this case actually "install systemd-boot pretending to be grub" so it gets chainloaded by shim? That looks like what this is.

yes, that's what it is. We will probably hardlink the actual systemd-boot binary at /usr/lib/efi/systemd-boot/** so that all bootloader live in the same place

OK right, and we're doing the same with grub-cc?

yes

but I think we do ultimately need to handle non-shim cases too

We do. On a call with @travier we decided to handle that case later

@travier travier Jul 9, 2026

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.

The name of the EFI binary loaded by shim is hardcoded in shim: https://src.fedoraproject.org/rpms/shim-unsigned-x64/blob/rawhide/f/shim-unsigned-x64.spec#_154

There are options to change it but they have a lot of downsides: #1080 (comment)

Pretending to be grub$arch.efi is apparently the simplest option.

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.

We settled (at least for now) on leaving standalone systemd-boot (or potentially standalone grub-cc) support to systemd's bootctl as we don't need all the complexity from bootupd for those cases: #1080 (comment)

@Rolv-Apneseth Rolv-Apneseth left a comment

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.

Couple of questions and suggestions but wow, this is cool

Comment thread scripts/test-bootloader.sh
Comment thread src/backend/statefile.rs
Comment thread src/backend/statefile.rs Outdated
Comment thread src/backend/statefile.rs
Comment thread src/backend/statefile.rs Outdated
Comment thread src/bootloader.rs Outdated
Comment thread src/bootupd.rs Outdated
Comment thread src/model.rs Outdated
Comment thread src/model.rs
Comment thread Dockerfile.bls Outdated
Store the sysroot_path in StateLockGuard which we require for mounting
the ESP. Also, prevent mounting ESP at a tempdir, instead just mount it
at `/boot` which is what the BLS spec suggest

Fix issue with composefs systems where lsblk would fail to find the
backing device for "/" due to it being mounted as a virtual filesystem.
Instead we now use `/sysroot` or `/boot` to find the baking device

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
For `bootupd generate-update-metadata` we now generate metadata for all
found bootloaders. The final json will look like

```json
{
  "timestamp": "2026-06-10T09:52:58.107743026Z",
  "version": "grub-cc-1:2.12-59.fc45,grub2-1:2.12-58.fc44,shim-16.1-5",
  "versions": [
    { "name": "grub-cc", "rpm_evr": "1:2.12-59.fc45" },
    { "name": "grub2", "rpm_evr": "1:2.12-58.fc44" },
    { "name": "shim", "rpm_evr": "16.1-5" }
  ]
}
```

This allows us to have multiple bootloaders in a single image and
install/update based on preference.

During install/update, we check to bootloader and filter out any and all
entries from the update metadata that do not match or are not required
for the bootloader

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Only allow Grub as the bootloader for powerpc and s390x

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Move functions `get_component_update` and `component_update_data_name`
inside Component trait with default implementations. This reduces use of
`dyn Component` which is much cleaner and allows us to have default
implementations on the trait

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Add a cmd to set default bootloader after generating content metadata.
This is the bootloader we'll use at install time if no bootloader option
is provided.

ContentMetadata now has an `defautl_bootloader: Option<Bootloader>`
field

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Now that we can set default bootloader option, we don't require a
bootloader to be passed in at install time as we can just get it from
the set default

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Installation is failing with

```
12:16:20  >>> Non-critical error in %posttrans scriptlet: grub2-common-1:2.12-61.fc44.noarch
12:16:20  >>> Scriptlet output:
12:16:20  >>> /var/tmp/rpm-tmp.JMDpbL: line 7: grub2-mkconfig: command not found
12:16:20  >>>
12:16:20  >>> [RPM] %posttrans(grub2-common-1:2.12-61.fc44.noarch) scriptlet failed, exit status 127
12:16:20  Transaction failed: Rpm transaction failed.
```

and I don't think we're using grub tools in the stage

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
While searching for backing device for root, we were simply assuming
error thrown by lsblk would be because of composefs and implicity
looking for mountpoints for /sysroot and /boot underneath.

We still have the same logic, but insted of relying on some unknown
error, we explicitly check for a composefs mount

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Create a new Dockerfile with base "fedora-bootc:44" for testing images
with both grub and grub-cc present. Currently we don't have a bootc
release that allows us to test with grub-cc, so we only test grub
installations.

This mainly tests backwards compatibility for the following cases

- Only one bootloader present and no "default" in metadata
- Two bootloaders present with grub as the "default"

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
This works exactly like GrubCC
We expect systemd-boot efi to be in

`/usr/lib/efi/systemd-boot/<evr>/fedora/grubx64.cc`

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
We don't have e2e upgrade tests for now, but we can atleast test if the
installations are working well

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
We generate the final filetree from `/usr/lib/efi` which worked well
with only one bootloader, but now that we have three which are stored in
the following places

Grub2
- /usr/lib/efi/grub2/<evr>/EFI/fedora/grubx64.efi

GrubCC
- /usr/lib/efi/grub-cc/<evr>/EFI/fedora/grubx64.efi

SystemdBoot
- /usr/lib/efi/systemd-boot/<evr>/EFI/fedora/grubx64.efi

The problem arises as all the distinct efi binaries have the same name
(grubx64.efi - due to the particular name being baked into shim), we
cannot blindly create a filesystem tree out of this as later values will
override the previous ones.

To mitigate this, introduce a param `skip_dirs` which will filter
directories not required for the bootloader being installed

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
A utility cfg macro defined in build.rs so that we can deny EFI only
bootloaders - GrubCC and SystemdBoot, on non-EFI systems at compile time

Also, only allow setting default bootloaders for EFI systems

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
@Johan-Liebert1

Copy link
Copy Markdown
Member Author

A lot of changes here. I'll test it out in fcos as an override

Tested locally overriding rootfs. There's one test failure. Looking into it

systemctl status kola-runext.service:
× kola-runext.service
     Loaded: loaded (/etc/systemd/system/kola-runext.service; static)
    Drop-In: /usr/lib/systemd/system/service.d
             └─10-timeout-abort.conf
     Active: failed (Result: exit-code) since Mon 2026-07-13 05:42:18 UTC; 916ms ago
   Duration: 190ms
 Invocation: 760c18b9ef0b4f7681b94504d3ac32cb
    Process: 2350 ExecStart=/usr/local/bin/kola-runext-install-bootloader-multipath (code=exited, status=1/FAILURE)
   Main PID: 2350 (code=exited, status=1/FAILURE)
   Mem peak: 3.6M
        CPU: 101ms

Jul 13 05:42:18 qemu0 systemd[1]: Started kola-runext.service.
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: + . /var/opt/kola/extdata/commonlib.sh
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: ++ IFS=' '
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: ++ read -r -a cmdline
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: + case "${AUTOPKGTEST_REBOOT_MARK:-}" in
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: + ok 'first boot'
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: + echo ok 'first boot'
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: ok first boot
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2352]: + cat /proc/cmdline
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2353]: + grep -q rd.multipath=default
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2354]: ++ findmnt -nvr -o SOURCE /boot
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: + boot_src=/dev/mapper/0x21017c2d5487beb8p3
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2355]: ++ multipath -l -v 1
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: + parent_device=/dev/mapper/0x21017c2d5487beb8
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2356]: + udevadm info --query=property /dev/mapper/0x21017c2d5487beb8
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2357]: + grep -q MPATH_DEVICE_READY=1
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: + mount -o remount,rw /boot
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: + rm -f /boot/bootupd-state.json
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2350]: + bootupctl backend install --auto --write-uuid --device /dev/mapper/0x21017c2d5487beb8 --update-firmware /
Jul 13 05:42:18 qemu0 kola-runext-install-bootloader-multipath[2361]: error: boot data installation failed: Could not determine bootloader. Default bootloader not set
Jul 13 05:42:18 qemu0 systemd[1]: kola-runext.service: Main process exited, code=exited, status=1/FAILURE
Jul 13 05:42:18 qemu0 systemd[1]: kola-runext.service: Failed with result 'exit-code'.
--- FAIL: ext.config.boot.install-bootloader-multipath (27.58s)
        harness.go:1259: kolet failed: : kolet run-test-unit failed:  Error: Unit kola-runext.service exited with code 1
2026-07-13T05:42:19Z cli: Unit kola-runext.service exited with code 1: Process exited with status 1

Use this enum in match statements so that if we add a new component in
future, we'll get compile time errors in places where we need to handle
the new component. This also gets rid of some hardcoded strings

Also remove `#[default]` from Bootloader enum. Having a "default" in the
enum doesn't make sense as we never construct this enum

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Renaming some functions, using match instead of if-else for cleaner
code. Slightly major change here is the `was_mounted` field was not
being accounted for in `update_state` and the path was being unmounted
regardless. Updated to call `drop` on `Efi` which handles it well

Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
@Johan-Liebert1

Copy link
Copy Markdown
Member Author

Fixed. Issue was to do with BIOS having no default bootloader. Explicitly set default to Grub

@Rolv-Apneseth Rolv-Apneseth left a comment

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.

LGTM

@Johan-Liebert1
Johan-Liebert1 merged commit 5d7cd81 into coreos:main Jul 13, 2026
14 checks passed
krolmiki2011 added a commit to ImmutableLinux/bootupd that referenced this pull request Jul 14, 2026
Signed-off-by: krolmiki2011 <mikolajziolkowski504@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants