← Back to issue list

feat: zero-mount bootloader installation

View original Github issue

Metadata

Project
imagecraft
Number
#437
Type
pull request
State
open
Author
lengau
Labels
Created
Updated
Closed

Current evaluation

Replaces privileged loop-device GRUB installation with pre-format-staging bootloader installer, removing loop device requirements for EFI/BIOS boot code installation. All CI passing, 0 reviewers.

Suggested action: needs review

Reason: This is a fresh PR (2 days old) with a major architectural refactoring of bootloader installation (+2722/-1262 lines across 29 files) that replaces the entire loop-device + chroot GRUB approach with a pre-format-staging mechanism. All CI checks pass, but there are 0 reviewers and 0 comments, so it needs maintainer review before merging. The detailed review instructions in the PR body indicate the author expects careful examination of the integration flow, filesystem ID consistency, and chroot environment boundaries.

Impact: 75 Quick Win: 11.25 Staleness: 5 Complexity: 85 Confidence: 75

Issue body

So this ended up having way more than I intended but it sort of fit all the puzzle pieces together. AI-generated summary is below, including an AI-generated walkthrough of the code and suggestion of how to review. IMAGECRAFT-176 ## Summary Replaces imagecraft's privileged loop-device + chroot GRUB installation (`imagecraft.pack.grubutil`) with a new pre-format-staging bootloader installer under `imagecraft/pack/bootloader/`. Instead of attaching loop devices and mounting partitions, this: 1. Stages bootloader files (`/etc/fstab`, `/boot/grub/grub.cfg`, EFI binaries) directly into partition **prime directories** before formatting, so `mke2fs -d`/`mcopy` embed them automatically. 2. Pre-generates filesystem IDs and passes them to the formatters (`mke2fs -U` for ext, `mkfs.fat -i` for FAT — FAT volume IDs use the `XXXX-XXXX` serial form GRUB probes) so they match what's baked into `fstab`/`grub.cfg`. 3. Generates `grub.cfg` by running the guest rootfs's own `grub-mkconfig` in a prime-dir chroot, with a `grub-probe` shim bind-mounted over the guest binary (the chroot root is a plain directory that the real `grub-probe` cannot resolve to a device in unprivileged containers) answering with the pre-generated IDs. 4. For BIOS targets, mounts the formatted image's root partition read-write via `fuse2fs` (using the existing `imagecraft.utils.mount.ExtFuseMount`) and runs the guest's own `grub-mkimage` + `grub-bios-setup` chrooted into that mount, with the image bind-mounted at its host path and a generated `device.map`. Inside the chroot, `/proc/self/mountinfo` shows the fuse mount as `/`, so `grub-bios-setup`'s mountinfo-based device resolution works unmodified; `grub-bios-setup` writes Sector 0 and embeds `core.img` (post-MBR gap or BIOS Boot partition) itself. No loop devices, no byte-level patching. For native-arch builds this removes the old approach's loop-device requirements (no `losetup`, no partition mounts). Cross-architecture builds still need binfmt/QEMU for the chrooted guest tools — unchanged from the previous `grubutil` flow, which also chrooted and executed guest-architecture binaries. This is intended to be **non-breaking**: treating `imagecraft` as a CLI black box, any `imagecraft.yaml` that packed successfully before this change should still pack successfully after it. ## Reviewing this PR Start with the execution flow rather than reading the new package alphabetically. The key boundary is **before versus after filesystem formatting**: files written to prime directories must be staged before formatting; BIOS boot sectors are installed into the finalized image afterwards. **Scope clarification:** the bootloader no longer needs kernel-mounted image partitions, but the wider `ImagecraftPackService.pack()` flow still calls `ImageService.attach_images()` and formats loop-device partitions. This PR does not make the entire pack operation loopless. Chrooted guest tools still require binfmt/QEMU for foreign-architecture execution. 1. **Read the integration first:** `imagecraft/services/pack.py`, then `bootloader/installer.py`. Follow one EFI image and one BIOS image through staging, formatting, detachment, and final installation. 2. **Trace a separate `/boot` layout:** follow its prime directory and filesystem ID through `installer.py`, `mkconfig.py`, `efi.py` or `bios.py`, and `diskutil.py`. The formatter, GRUB search configuration, and fstab must agree. Kernel `root=UUID=` must still identify the root filesystem, not `/boot`. 3. **Inspect the environment boundaries:** `mkconfig.py`, `bios.py`, `chrootenv.py`, and `pack/chroot.py`. Pay particular attention to temporary files, bind mounts, cleanup on failure, and the distinction between missing tooling (a reported skip) and command failures (errors). 4. **Read the regression cases alongside the implementation:** `tests/unit/pack/bootloader/`, `tests/unit/pack/test_diskutil.py`, and `tests/unit/services/test_pack.py`. The four GRUB pack scenarios under `tests/spread/pack/` cover EFI/BIOS with shared or separate `/boot`; their log expectations change with the new flow. Unit-level command assertions are not a substitute for checking that an image actually boots. ## Code walkthrough ### 1. Coordinate the two installation phases `ImagecraftPackService.pack()` creates and attaches the image, constructs `BootloaderInstaller`, and obtains its `partition_uuids` mapping. It passes the lifecycle's prime-directory resolver and the project's filesystem mount mapping into `prepare_rootfs()`. After staging, it formats each partition with that partition's assigned ID, verifies the images, and detaches loop devices in a `finally` block. It then finalizes the image paths and calls `install_image_boot_code()`; that last phase only acts on a successfully prepared BIOS target. ### 2. Select partitions, IDs, and boot method `imagecraft/models/volume.py` provides the root, ESP, dedicated boot, and BIOS Boot partition lookups. Hybrid type strings are normalized for GPT-type comparisons; ESP and raw BIOS Boot partitions are excluded from the dedicated boot lookup. `bootloader/installer.py` uses those lookups and the architecture specifications in `const.py` to choose EFI, BIOS, or no installation. An ESP selects EFI; supported PC architectures can use BIOS on MBR or with a GPT BIOS Boot partition. The installer assigns conventional UUIDs to ext filesystems and `XXXX-XXXX` volume IDs to FAT filesystems. `configure_fstab()` updates the root entry and, when the mount mapping associates `/boot` with the selected boot partition, its boot entry. Existing entry fields are retained while the source is updated. `diskutil.format_device()` applies the same IDs through `mke2fs -U` or `mkfs.fat -i`. ### 3. Generate the main GRUB configuration `bootloader/mkconfig.py` runs the guest's `grub-mkconfig` in a chroot rooted at the root prime directory. A dedicated boot prime is bind-mounted at `/boot`. Because the real `grub-probe` cannot discover the future image filesystem from these ordinary directories, a shell shim supplies device, UUID, and partition-map answers. A transient defaults snippet and device placeholders support configuration generation; the snippet's previous content is restored when present. For separate `/boot`, `_fs_internal_path()` and `_strip_boot_prefix()` translate generated file operands to boot-filesystem-relative paths without rewriting kernel arguments. These helpers deserve close attention because build-host paths must not leak into boot-time paths. ### 4. Stage the EFI bootloader `bootloader/efi.py` tries signed shim/GRUB binaries first, then an unsigned prebuilt GRUB binary, then an in-chroot `grub-mkimage` build. It places the selected files in the ESP's `EFI/BOOT` and `EFI/ubuntu` directories. The early configuration rendered by `const.py` searches for the filesystem containing the main GRUB configuration. Its prefix is `/boot/grub` for shared root/boot, or `/grub` for a dedicated boot partition. `chrootenv.stage_grub_modules()` copies runtime modules into the appropriate prime directory where needed, before formatting embeds them. ### 5. Install BIOS boot code BIOS preparation stages runtime modules before formatting. Afterwards, `PCBiosInstaller` in `bootloader/bios.py` locates the root partition's byte offset, including the extended-container numbering adjustment for MBR layouts with more than four entries. It mounts that filesystem through `ExtFuseMount`, builds a chroot on the mount, and bind-mounts the image at its host pathname inside the chroot. The guest's `grub-mkimage` builds `core.img`, and `grub-bios-setup` performs the boot-sector and embedding writes using a generated device map. This leaves GRUB responsible for the disk-level installation rather than introducing Python sector-patching code. FAT boot layouts require `fat.mod` in the core image. ### 6. Check supporting helpers and removed paths `bootloader/chrootenv.py` centralizes guest-tool discovery, the restricted command PATH, error conversion, and module staging. `pack/chroot.py` retains the existing chroot machinery and adds `build_prime_chroot()` to assemble the required mounts. Finally, compare the deleted `pack/grubutil.py` and `pack/image.py` with the coordinator and service flow. Their responsibilities move into staging, the existing image service, and the BIOS installer; the old unit tests are replaced by coverage at those boundaries.

Evaluation history

Date Model Scores Action Summary
qwen/qwen3.6-35b-a3b
Impact: 75
Quick Win: 11.25
Staleness: 5
Complexity: 85
Confidence: 75
needs review Replaces privileged loop-device GRUB installation with pre-format-staging bootloader installer, removing loop device requirements for EFI/BIOS boot code installation. All CI passing, 0 reviewers.
qwen/qwen3.6-35b-a3b
Impact: 65
Quick Win: 14.3
Staleness: 5
Complexity: 78
Confidence: 75
needs review Replaces privileged loop-device grubutil with new bootloader installer staging files in prime dirs before formatting. Fresh PR, all CI passing, no reviewers yet.
qwen/qwen3.6-35b-a3b
Impact: 75
Quick Win: 7.5
Staleness: 0
Complexity: 90
Confidence: 75
needs review Large refactoring replacing privileged loop-device GRUB installation with a new zero-mount bootloader installer. Brand new PR, 0 days old, 0 reviewers, 6 unresolved review comments, CI passing.
qwen/qwen3.6-35b-a3b
Impact: 75
Quick Win: 11.25
Staleness: 0
Complexity: 85
Confidence: 75
needs review Large architectural change replacing privileged loop-device GRUB installation with a new zero-mount bootloader installer. Fresh PR (0 days old) with 6 unresolved review comments and mostly passing CI.
qwen/qwen3.6-35b-a3b
Impact: 70
Quick Win: 14.0
Staleness: 0
Complexity: 80
Confidence: 75
needs review Replaces privileged loop-device + chroot GRUB installation with a new zero-mount bootloader installer. Fresh PR with 6 unresolved review comments, all CI checks passing.
qwen/qwen3.6-35b-a3b
Impact: 75
Quick Win: 9.0
Staleness: 5
Complexity: 88
Confidence: 72
needs review Major refactor replacing privileged loop-device + chroot GRUB installation with a zero-mount bootloader installer. Fresh PR from maintainer, no reviews yet, CI partially pending with one failing build check.
qwen/qwen3.6-35b-a3b
Impact: 65
Quick Win: 11.7
Staleness: 0
Complexity: 82
Confidence: 65
needs review Large refactoring replacing privileged loop-device + chroot GRUB installation with a zero-mount bootloader installer. Brand new PR from maintainer, 0 reviewers, CI passing, no comments yet.
qwen/qwen3.6-35b-a3b
Impact: 75
Quick Win: 11.25
Staleness: 0
Complexity: 85
Confidence: 75
needs review Replaces privileged loop-device + chroot GRUB installation with zero-mount bootloader installer. Brand new PR under review with 3 unresolved comments and pending CI checks.
qwen/qwen3.6-35b-a3b
Impact: 75
Quick Win: 11.25
Staleness: 10
Complexity: 85
Confidence: 65
needs review Replaces privileged loop-device + chroot GRUB installation with a new zero-mount bootloader installer. Large refactoring (+1725/-1383 lines, 27 files) adding imagecraft/pack/bootloader/ package. Fresh PR with 5 unresolved review comments, 0 reviewers, passing CI.
qwen/qwen3.6-35b-a3b
Impact: 70
Quick Win: 17.5
Staleness: 5
Complexity: 75
Confidence: 55
needs review Replaces privileged loop-device + chroot GRUB installation with a new zero-mount bootloader installer under imagecraft/pack/bootloader/, removing host-privilege requirements and adding riscv64 EFI support.
qwen/qwen3.6-35b-a3b
Impact: 75
Quick Win: 11.25
Staleness: 0
Complexity: 85
Confidence: 70
needs review Replaces privileged loop-device + chroot GRUB installation with a new zero-mount bootloader installer that stages files before formatting, eliminating host-privilege requirements. Fresh PR awaiting review.
qwen/qwen3.6-35b-a3b
Impact: 75
Quick Win: 11.25
Staleness: 0
Complexity: 85
Confidence: 70
needs review Major refactor replacing privileged loop-device + chroot GRUB installation with a zero-mount bootloader installer. New package structure, breaking change. Fresh PR with 0 reviewers and 2 unresolved review comments, CI passing.
qwen/qwen3.6-35b-a3b
Impact: 65
Quick Win: 9.75
Staleness: 0
Complexity: 85
Confidence: 75
needs review Replaces privileged loop-device + chroot GRUB installation with a new zero-mount bootloader installer. Fresh PR from maintainer, all CI passing, awaiting review.

Update history

Date Change
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
created

Related issues

Issue Project State Summary Similarity
#415 feat(grub): install EFI GRUB without loop devices imagecraft open PR converts EFI/GPT GRUB installation to use FUSE partition mounts instead of kernel loop devices, enabling unprivileged image builds. Has 11 unresolved review comments and some failing CI slow tests.
71%