← Back to issue list

feat: zero-mount bootloader installation

View original Github issue

Metadata

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

Current evaluation

Merged to main (commit 0ca6f6e). Replaces privileged loop-device+chroot GRUB install (pack/grubutil.py) with a zero-mount bootloader package staging files into prime dirs; +3415/-1275 across 31 files.

Resolution: closed resolved

Reason: PR #437 by maintainer lengau was approved and merged into main as commit 0ca6f6e, deleting pack/grubutil.py and adding imagecraft/pack/bootloader/. The related earlier attempt #416 was closed unmerged and superseded by this merge.

Evaluated by: mr-cal-bot

No scores available.

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
stealth/space-bunny-alpha — closed resolved Merged to main (commit 0ca6f6e). Replaces privileged loop-device+chroot GRUB install (pack/grubutil.py) with a zero-mount bootloader package staging files into prime dirs; +3415/-1275 across 31 files.
qwen/qwen3.8-27b — closed resolved Merged into main. Replaces imagecraft's privileged loop-device + chroot GRUB install with a zero-mount bootloader installer (imagecraft/pack/bootloader/) that stages files into prime dirs, pre-generates fs IDs, and uses fuse2fs for BIOS. +3415/-1275, 31 files.
qwen/qwen3.8-27b
Impact: 75
Quick Win: 4
Complexity: 90
Confidence: 75
Actionability: 60
needs review Rewrites imagecraft's GRUB installation from loop-device+chroot to a zero-mount bootloader package (pre-format staging, pre-generated fs IDs, fuse2fs-based BIOS setup). Approved by 2 reviewers; 1 unresolved comment, OSV-scanner failing, spread boot checks pending; author just merged main and fixed lint.
qwen/qwen3.8-27b
Impact: 75
Quick Win: 4
Complexity: 90
Confidence: 70
Actionability: 60
needs review Replaces loop-device+chroot GRUB installation with a zero-mount bootloader package (prime-dir staging, pre-generated fs IDs, fuse2fs-based BIOS setup). Approved; conflicts resolved and lint fixed in d1860e2; OSV-scanner failing, snap builds pending.
qwen/qwen3.8-27b
Impact: 72
Quick Win: 5
Complexity: 85
Confidence: 75
Actionability: 45
keep open Replaces imagecraft's loop-device GRUB installer with an unprivileged zero-mount bootloader package (staging, pre-generated fs IDs, chrooted grub-mkconfig, fuse-based BIOS setup). +3412/-1269, 31 files. Approved; 4 CI checks failing, 3 pending.
qwen/qwen3.8-27b
Impact: 70
Quick Win: 6
Complexity: 85
Confidence: 75
Actionability: 55
needs review Replaces loop-device+chroot GRUB installation with a pre-format staging bootloader package (EFI/BIOS, shared/dedicated /boot). Approved, 2 unresolved comments; OSV-scanner failing, boot spread pending; conflicts fixed in d1860e2, awaiting re-review.
qwen/qwen3.8-27b
Impact: 65
Quick Win: 4
Complexity: 85
Confidence: 70
Actionability: 40
keep open Replaces privileged loop-device/chroot GRUB install (grubutil) with a zero-mount bootloader package staging files into prime dirs, pre-generating fs IDs, and running guest grub-mkconfig/mkimage via chroot/fuse. Approved, but 8 unresolved comments and spread boot CI suites failing; author just pushed conflict/lint fix.
stealth/space-bunny-alpha
Impact: 68
Quick Win: 5
Complexity: 85
Confidence: 64
Actionability: 45
needs review Replaces loop-device+chroot GRUB install with a zero-mount bootloader package that stages files into prime dirs pre-format. Approved by 1 reviewer, 5 unresolved comments, amd64 spread suite failing.
stealth/space-bunny-alpha
Impact: 82
Quick Win: 4
Complexity: 93
Confidence: 78
Actionability: 78
needs review Replaces Imagecraft’s privileged loop-device/chroot GRUB installation with pre-format staging and post-format BIOS setup using FUSE; 31 files and substantial unit/spread coverage are updated. All checks pass, but no reviewer has evaluated the large architectural change.
qwen/qwen3.6-35b-a3b
Impact: 75
Quick Win: 11
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
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: 8
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
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
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
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: 12
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
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
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: 18
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
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
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: 10
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
merged
updated
updated
updated
updated
updated
updated
updated
closed
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
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
updated
created

Related work

  • Superseded By: canonical/imagecraft#416 (confidence 78%)

    Earlier PR 'feat(grub): install BIOS GRUB without loop devices or chroot' closed unmerged after inactivity; #437 delivered the merged zero-mount approach.

Related issues

Issue Project State Summary Similarity
#434 feat(grub): loop-device-free EFI GRUB setup imagecraft closed Closed unmerged and superseded by #437, which merged the broader zero-mount bootloader installer in commit 0ca6f6e.
73%
#416 feat(grub): install BIOS GRUB without loop devices or chroot imagecraft closed Closed unmerged after inactivity; its zero-mount GRUB goal was superseded by merged #437 (commit 0ca6f6e), which replaced loop-device and chroot installation.
70%