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).
This commit is contained in:
Blake Ridgway
2026-08-21 14:15:10 -05:00
parent bb031ca92f
commit 0361c12c07
12 changed files with 47 additions and 19 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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 ───────────────────────────────────────────────────────

View File

@@ -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 ───────────────────────────────────────────────────────

View File

@@ -21,7 +21,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
EDITION="${1:?usage: build-image.sh <edition> [--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

View File

@@ -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"

View File

@@ -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)

View File

@@ -25,7 +25,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
DEV="${1:?usage: deploy-disk.sh <device> <rootfs> <edition> [--boot bios|efi]}"
ROOTFS="${2:?usage: deploy-disk.sh <device> <rootfs> <edition> [--boot bios|efi]}"
EDITION="${3:?usage: deploy-disk.sh <device> <rootfs> <edition> [--boot bios|efi]}"
BOOT="${4:-bios}"
BOOT="${4:-$BOOT}" # default from the BOOT build variable (see versions.mk)
require_root "$0" "$@"
validate_edition "$EDITION"

View File

@@ -18,7 +18,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
DEV="${1:?usage: install.sh <device> [--edition server] [--boot bios|efi] [--rootfs <dir>|--image <tar.xz>]}"
EDITION="server"
BOOT="bios"
BOOT="${BOOT:-bios}" # default from the BOOT build variable (see versions.mk)
ROOTFS_SRC=""
shift || true

View File

@@ -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