feat(grub): install EFI GRUB without loop devices or chroot - #415
Draft
lengau wants to merge 1 commit into
Draft
Conversation
lengau
force-pushed
the
work/IMAGECRAFT-176/grub-efi
branch
2 times, most recently
from
August 13, 2026 12:15
7b70ea3 to
9d9f078
Compare
lengau
force-pushed
the
work/IMAGECRAFT-176/grub-efi
branch
from
August 13, 2026 17:03
9d9f078 to
a70c387
Compare
lengau
force-pushed
the
work/IMAGECRAFT-176/grub-efi
branch
from
August 13, 2026 19:43
a70c387 to
16dc20b
Compare
lengau
force-pushed
the
work/IMAGECRAFT-176/grub-efi
branch
2 times, most recently
from
August 13, 2026 23:18
f5ef26d to
5498d96
Compare
lengau
force-pushed
the
work/IMAGECRAFT-176/grub-efi
branch
from
August 14, 2026 02:49
5498d96 to
d6e07d5
Compare
lengau
force-pushed
the
work/IMAGECRAFT-176/grub-efi
branch
from
August 20, 2026 16:45
d6e07d5 to
608616f
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR reworks imagecraft’s EFI/GPT GRUB installation to operate directly on raw disk images (using host-side grub-mkimage, mtools, and debugfs) rather than relying on privileged loop-device mounts and a chroot, enabling unprivileged/container builds. It also updates boot validation to exercise the new EFI path (including Secure Boot).
Changes:
- Implement EFI GRUB installation in
imagecraft.pack.grubutilvia direct disk image manipulation (no mount/loop/chroot), with optional deployment of signed shim/GRUB when present. - Add/expand unit + end-to-end integration tests that build small real disk images and verify GRUB artifacts/config placement without privileged operations.
- Update spread boot tasks/workflow and snap packaging to support the new tooling (
grub-mkimage, EFI boot tests, Secure Boot test).
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
imagecraft/pack/grubutil.py |
Core EFI/GPT GRUB install path rewritten to operate on raw images with debugfs/mtools/grub-mkimage; BIOS/MBR path retained via chroot for now. |
tests/unit/pack/test_grubutil.py |
New/expanded unit + integration coverage for EFI GRUB installation and config generation, including signed shim/GRUB handling and separate /boot cases. |
tests/spread/boot/classic-efi/task.yaml |
Spread boot test adjusted for EFI boot under QEMU with serial capture via script. |
tests/spread/boot/classic-efi/imagecraft.yaml |
Test image tweaked to ensure reliable serial console output and GRUB defaults compatible with the new config generation. |
tests/spread/boot/classic-efi-secureboot/task.yaml |
New Secure Boot-enabled QEMU boot validation using OVMF secboot firmware + Microsoft CA vars template. |
tests/spread/boot/classic-efi-secureboot/imagecraft.yaml |
New Secure Boot test image definition (installs signed GRUB + shim packages, configures GRUB defaults). |
snap/snapcraft.yaml |
Add grub-common and stage grub-mkimage into the snap’s tool path. |
.github/workflows/spread-test.yaml |
Update ARM classic boot spread job to run the new classic-efi spread test. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| ) | ||
|
|
||
|
|
||
| def test_setup_grub_efi_unsigned(new_dir, fake_kernel_files): |
Comment on lines
+832
to
+834
| @pytest.mark.slow | ||
| @pytest.mark.usefixtures("new_dir") | ||
| def _build_efi_disk(tmp_path: Path, root_content: Path, esp_content: Path): |
lengau
force-pushed
the
work/IMAGECRAFT-176/grub-efi
branch
from
August 20, 2026 18:16
608616f to
fc58ff0
Compare
Replace the chroot-over-loop-device GRUB installation for EFI systems with direct manipulation of the disk image: - The root (and optional separate boot) ext4 partitions are exposed through the unprivileged FUSE mounts from imagecraft.utils.mount. - GRUB's EFI binary is built by running the image's own grub-mkimage inside a chroot of the mounted rootfs (via chroot(1)), guaranteeing the builder matches the modules it consumes — no host/image version skew and no module dumping. - ESP files are written in place with mtools' image@@offset addressing; fusefat is deliberately not used here as it proves unreliable when stacked over fusefile. - grub.cfg is hand-generated (kernel enumeration newest-first, /etc/default/grub cmdline handling, UEFI firmware entry) because grub-mkconfig/grub-probe require a real block device. This removes the need for loop devices when building EFI images, which is what allows imagecraft to run inside unprivileged LXD containers. The BIOS path still uses the chroot implementation and is converted in a follow-up, so setup_grub dispatches to the new EFI implementation for GPT volumes and to the retained chroot implementation for MBR volumes. The spread test boot/classic is renamed to boot/classic-efi to make room for an MBR counterpart, and boot/classic-efi-secureboot is added to cover installing the distribution's signed shim and GRUB binaries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
lengau
force-pushed
the
work/IMAGECRAFT-176/grub-efi
branch
from
August 25, 2026 21:15
fc58ff0 to
ae8088a
Compare
lengau
force-pushed
the
work/IMAGECRAFT-176/grub-efi
branch
from
August 27, 2026 13:44
7b20ed5 to
ae8088a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 5 of a stack that makes GRUB installation work without root privileges.
This is the core of the change.
The problem
Installing GRUB used to mean: attach a loop device to the image, mount the
partitions, bind-mount
/dev,/procand/sys,chrootin, and run thedistribution's
grub-install. All of that needs root and privileged access tothe host's loop devices, which is why imagecraft could not build images in an
unprivileged container.
The approach
Build the bootloader on the host instead of inside the image:
grub-mkimageassembles a GRUB EFI binary with the modules we need, with aprefix pointing at the ESP.
mtools(via
imgfsfrom the previous PR) -- no mount.debugfs, and agrub.cfgis generated for them.instead so the image boots with Secure Boot enabled.
Nothing is mounted, no loop device is attached, and no
chroothappens.Scope
Only the EFI/GPT path is converted here. BIOS still uses the old chroot
implementation, and
setup_grubdispatches to whichever is appropriate, so thetree is working and testable at this commit. BIOS is converted in the next PR.
Testing
tests/spread/boot/classicbecomesboot/classic-efi(to make room for the MBRcounterpart), and
boot/classic-efi-securebootis added. Both build an imageand boot it under QEMU, asserting the guest reaches userspace.
Replacing
update-grubupdate-grubrendered/etc/default/grubinto the config. Since it is nolonger run, the generated config honours
GRUB_CMDLINE_LINUX,GRUB_CMDLINE_LINUX_DEFAULTandGRUB_TIMEOUTdirectly, and orders menuentries newest kernel first so
default=0boots the newest one.