From 0361c12c07318585a5301ee19406aad38973513b Mon Sep 17 00:00:00 2001 From: Blake Ridgway Date: Fri, 21 Aug 2026 14:15:10 -0500 Subject: [PATCH] fix: resolve grub-pc / grub-efi-amd64 "held broken packages" conflict The edition package lists installed BOTH grub-pc and grub-efi-amd64 (+ shim-signed). Those provide the same bootloader role and conflict in apt, so every rootfs build failed with "unable to correct problems, you have held broken packages". A rootfs now carries exactly ONE bootloader, chosen by the BOOT variable (mirroring the existing --boot bios|efi deploy option): - versions.mk / common.sh: BOOT := bios (bios -> grub-pc, efi -> grub-efi-amd64 + shim-signed + mokutil), exported via the Makefile. - build-rootfs.sh validates BOOT early and injects the matching boot packages into the apt install; the static package lists no longer contain any grub package. - deploy-disk.sh / build-image.sh / install.sh default --boot from the same BOOT variable, so a rootfs and the artifact deployed from it can never disagree (BOOT=efi make image-cloud produces a UEFI image). - mokutil is now installed explicitly in the efi flavour (it was not pulled in because we install with --no-install-recommends). - docs updated (building.md knob + rationale, secureboot.md note). --- Makefile | 4 ++-- docs/building.md | 7 +++++++ docs/secureboot.md | 7 ++++--- editions/cloud/packages.list | 7 ++++--- editions/server/packages.list | 7 ++++--- editions/workstation/packages.list | 7 ++++--- scripts/build-image.sh | 2 +- scripts/build-rootfs.sh | 12 ++++++++++-- scripts/common.sh | 3 +++ scripts/deploy-disk.sh | 2 +- scripts/install.sh | 2 +- versions.mk | 6 ++++++ 12 files changed, 47 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index c6c0dc3..52f892d 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ include versions.mk # Pass edition + env through to the scripts. export DISTRO_NAME DISTRO_ID VERSION RELEASE_NAME export DEBIAN_SUITE DEBIAN_MIRROR SECURITY_MIRROR ARCH -export KERNEL_PACKAGE KERNEL_VERSION +export KERNEL_PACKAGE KERNEL_VERSION BOOT export BUILD_DIR ROOTFS_DIR IMAGE_DIR DEB_DIR LOG_DIR ARTIFACT_DIR export TOOLCHAIN_REPO @@ -55,7 +55,7 @@ help: @echo @echo "Configuration (see versions.mk):" @echo " VERSION=$(VERSION) DEBIAN_SUITE=$(DEBIAN_SUITE) ARCH=$(ARCH)" - @echo " ARCLINE_TOOLCHAIN=auto|skip|require (toolchain in image builds)" + @echo " BOOT=$(BOOT) ARCLINE_TOOLCHAIN=auto|skip|require (bootloader / toolchain in image builds)" # ── host deps ──────────────────────────────────────────────────────────────── deps: diff --git a/docs/building.md b/docs/building.md index 08ff27e..1d7c1c2 100644 --- a/docs/building.md +++ b/docs/building.md @@ -81,6 +81,13 @@ Set these as environment variables or edit `versions.mk`: | `ARCLINE_EXTRA_REPOS` | fetch grafana/loki upstream repos | `0` | | `ARCLINE_TOOLCHAIN` | toolchain in image builds | `auto` | | `ARCLINE_SIGN` | sign boot chain with the MOK (secure boot) | `0` | +| `BOOT` | bootloader in the image: `bios` → grub-pc, `efi` → grub-efi-amd64 + shim-signed | `bios` | + +> **Why one bootloader?** `grub-pc` and `grub-efi-amd64` conflict, so apt fails +> with *"held broken packages"* if both are in a package list. Arcline ships +> exactly the one matching `BOOT` (injected by `build-rootfs.sh`). For a UEFI + +> secure-boot build: `BOOT=efi make iso-server` (or `make image-cloud` with +> `BOOT=efi`). The deployed image/install uses the same variable by default. ## Building without the Arcline toolchain diff --git a/docs/secureboot.md b/docs/secureboot.md index 5f47ce0..55d803a 100644 --- a/docs/secureboot.md +++ b/docs/secureboot.md @@ -53,9 +53,10 @@ against your MOK. ## Notes -- Requires `sbsigntool` on the build host and `mokutil` in the image - (`mokutil` is pulled in by the `shim-signed` package already in the package - lists). +- Requires `sbsigntool` on the build host. `mokutil` (for enrollment) is + installed in the image as part of the EFI boot flavour: + `BOOT=efi make iso-server` (the `efi` flavour adds `grub-efi-amd64 + shim-signed mokutil`). - Losing `MOK.priv` means you cannot sign future updates — back it up. - Full vendor CA / Microsoft KEK signing is intentionally not used; revisit only if a commercial distribution is ever pursued. diff --git a/editions/cloud/packages.list b/editions/cloud/packages.list index a808b59..5217f9b 100644 --- a/editions/cloud/packages.list +++ b/editions/cloud/packages.list @@ -11,9 +11,10 @@ locales tzdata # ── boot ──────────────────────────────────────────────────────────────────── -grub-pc -grub-efi-amd64 -shim-signed +# The bootloader is chosen by the BOOT build variable and injected by +# build-rootfs.sh: bios → grub-pc, efi → grub-efi-amd64 + shim-signed. +# (Installing grub-pc and grub-efi-amd64 together makes apt fail with a +# "held broken packages" conflict — exactly one is ever installed.) # ── kernel (cloud variant) ────────────────────────────────────────────────── linux-image-cloud-amd64 diff --git a/editions/server/packages.list b/editions/server/packages.list index a33d300..5c997b7 100644 --- a/editions/server/packages.list +++ b/editions/server/packages.list @@ -13,9 +13,10 @@ locales tzdata # ── boot ──────────────────────────────────────────────────────────────────── -grub-pc -grub-efi-amd64 -shim-signed +# The bootloader is chosen by the BOOT build variable and injected by +# build-rootfs.sh: bios → grub-pc, efi → grub-efi-amd64 + shim-signed. +# (Installing grub-pc and grub-efi-amd64 together makes apt fail with a +# "held broken packages" conflict — exactly one is ever installed.) os-prober # ── kernel / firmware ─────────────────────────────────────────────────────── diff --git a/editions/workstation/packages.list b/editions/workstation/packages.list index 69ed45d..3050862 100644 --- a/editions/workstation/packages.list +++ b/editions/workstation/packages.list @@ -12,9 +12,10 @@ locales tzdata # ── boot ──────────────────────────────────────────────────────────────────── -grub-pc -grub-efi-amd64 -shim-signed +# The bootloader is chosen by the BOOT build variable and injected by +# build-rootfs.sh: bios → grub-pc, efi → grub-efi-amd64 + shim-signed. +# (Installing grub-pc and grub-efi-amd64 together makes apt fail with a +# "held broken packages" conflict — exactly one is ever installed.) os-prober # ── kernel / firmware ─────────────────────────────────────────────────────── diff --git a/scripts/build-image.sh b/scripts/build-image.sh index 9293c1a..b66b82d 100755 --- a/scripts/build-image.sh +++ b/scripts/build-image.sh @@ -21,7 +21,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/common.sh" EDITION="${1:?usage: build-image.sh [--format qcow2|raw] [--size 4G] [--boot bios|efi]}" FORMAT="qcow2" SIZE="4G" -BOOT="bios" +BOOT="${BOOT:-bios}" # default from the BOOT build variable (see versions.mk) shift || true while [[ $# -gt 0 ]]; do diff --git a/scripts/build-rootfs.sh b/scripts/build-rootfs.sh index d8b5a23..e13ffc6 100755 --- a/scripts/build-rootfs.sh +++ b/scripts/build-rootfs.sh @@ -29,6 +29,14 @@ case "$ARCLINE_TOOLCHAIN" in *) die "ARCLINE_TOOLCHAIN must be auto|skip|require (got '$ARCLINE_TOOLCHAIN')" ;; esac +# Fail fast on a bad boot flavour. grub-pc and grub-efi-amd64 conflict, so we +# install exactly the one matching BOOT (never both). +case "$BOOT" in + bios) BOOT_PKGS="grub-pc" ;; + efi) BOOT_PKGS="grub-efi-amd64 shim-signed mokutil" ;; + *) die "BOOT must be bios|efi (got '$BOOT')" ;; +esac + EDIR="$(edition_dir "$EDITION")" ROOTFS="$ROOTFS_DIR/$EDITION" ARTIFACT="$ARTIFACT_DIR/arcline-$EDITION-$VERSION-$ARCH.tar.xz" @@ -73,8 +81,8 @@ trap 'unmount_pseudo' EXIT chroot_run() { chroot "$ROOTFS" /bin/bash -c "$*"; } # ── 3. install edition packages ───────────────────────────────────────────── -log "[3/6] installing edition packages (${EDITION})" -PKGS="$(grep -vE '^\s*(#|$)' "$EDIR/packages.list" | tr '\n' ' ')" +log "[3/6] installing edition packages (${EDITION}, boot: $BOOT)" +PKGS="$(grep -vE '^\s*(#|$)' "$EDIR/packages.list" | tr '\n' ' ')$BOOT_PKGS" mount_pseudo chroot_run "export DEBIAN_FRONTEND=noninteractive; apt-get update -qq && apt-get install -y --no-install-recommends $PKGS" \ | tee "$LOG_DIR/packages-$EDITION.log" diff --git a/scripts/common.sh b/scripts/common.sh index 247e132..e3fa5af 100755 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -20,6 +20,9 @@ set -euo pipefail : "${KERNEL_PACKAGE:=linux-image-amd64}" : "${KERNEL_VERSION:=6.12}" +# Boot flavour (mirrors versions.mk): bios → grub-pc, efi → grub-efi-amd64. +: "${BOOT:=bios}" + # Toolchain policy for image builds: # auto (default) install the Arcline tools if build/debs/*.deb exist, # otherwise build without them (with a warning) diff --git a/scripts/deploy-disk.sh b/scripts/deploy-disk.sh index ddce866..f134f64 100755 --- a/scripts/deploy-disk.sh +++ b/scripts/deploy-disk.sh @@ -25,7 +25,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/common.sh" DEV="${1:?usage: deploy-disk.sh [--boot bios|efi]}" ROOTFS="${2:?usage: deploy-disk.sh [--boot bios|efi]}" EDITION="${3:?usage: deploy-disk.sh [--boot bios|efi]}" -BOOT="${4:-bios}" +BOOT="${4:-$BOOT}" # default from the BOOT build variable (see versions.mk) require_root "$0" "$@" validate_edition "$EDITION" diff --git a/scripts/install.sh b/scripts/install.sh index 7c0305e..6256e42 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -18,7 +18,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/common.sh" DEV="${1:?usage: install.sh [--edition server] [--boot bios|efi] [--rootfs |--image ]}" EDITION="server" -BOOT="bios" +BOOT="${BOOT:-bios}" # default from the BOOT build variable (see versions.mk) ROOTFS_SRC="" shift || true diff --git a/versions.mk b/versions.mk index 460bb8e..88c691a 100644 --- a/versions.mk +++ b/versions.mk @@ -25,6 +25,12 @@ ARCH := amd64 KERNEL_PACKAGE := linux-image-amd64 KERNEL_VERSION := 6.12 +# Boot flavour for installed systems — decides which GRUB lands in the image. +# bios → grub-pc (bare metal + most clouds; the default) +# efi → grub-efi-amd64 + shim-signed + mokutil (UEFI + secure boot) +# grub-pc and grub-efi-amd64 conflict, so exactly one is ever installed. +BOOT := bios + # Edition codenames (the "what do I install" flavours). EDITIONS := server workstation cloud