The deploy failed with "grub-install: command not found" in the chroot — the installed rootfs archive was built BEFORE the BOOT/grub injection, so it had no bootloader at all. build-iso.sh / build-image.sh only rebuilt the rootfs when the archive was missing, never when sources changed, so development fixes were silently absent from deployed images. - build-rootfs.sh: incremental staleness check — skips a rebuild only when no input (scripts/, btrfs/, overlays/, editions/<e>/, versions.mk, toolchain debs) is newer than the artifact; FORCE=1 rebuilds anyway. - build-iso.sh / build-image.sh: always delegate freshness to build-rootfs.sh instead of gating on file existence. - deploy-disk.sh: defensive check that grub-install exists in the deployed rootfs, with a clear "stale archive — rebuild" message instead of a bare "command not found". - docs/building.md: incremental-build note (FORCE=1 / make clean).
123 lines
6.6 KiB
Bash
Executable File
123 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Arcline OS — live ISO builder (with graphical installer)
|
|
#
|
|
# scripts/build-iso.sh <edition>
|
|
#
|
|
# Produces a hybrid (BIOS+UEFI) live ISO that boots straight into the Arcline
|
|
# graphical installer:
|
|
# 1. ensure the CLEAN rootfs exists (what the installer deploys)
|
|
# 2. build-live.sh → build/rootfs/<edition>-live (live-boot + X + installer)
|
|
# 3. stage kernel + initramfs + squashfs in isofiles/live, and the clean
|
|
# rootfs archive in isofiles/install (the installer's install source)
|
|
# 4. write the grub boot config (live-boot: boot=live)
|
|
# 5. grub-mkrescue → build/artifacts/arcline-<edition>-<version>-<arch>.iso
|
|
#
|
|
# Requires root for the rootfs stage; the ISO assembly itself runs unprivileged.
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
set -euo pipefail
|
|
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
|
|
|
EDITION="${1:?usage: build-iso.sh <edition>}"
|
|
validate_edition "$EDITION"
|
|
|
|
CLEAN="$ROOTFS_DIR/$EDITION"
|
|
LIVE_ROOT="$ROOTFS_DIR/$EDITION-live"
|
|
INSTALL_ARC="$ARTIFACT_DIR/arcline-$EDITION-$VERSION-$ARCH.tar.xz"
|
|
ISOFILES="$IMAGE_DIR/$EDITION/isofiles"
|
|
ARTIFACT="$ARTIFACT_DIR/arcline-$EDITION-$VERSION-$ARCH.iso"
|
|
|
|
# ── 1. rootfs(es) ───────────────────────────────────────────────────────────
|
|
# The ISO is a live session (built on top of the clean rootfs) and carries the
|
|
# CLEAN rootfs archive as the install source for the graphical installer.
|
|
# build-rootfs is incremental (skips when inputs are unchanged) and rebuilds
|
|
# when any input is newer — so a stale archive can never be deployed again.
|
|
"$ROOT/scripts/build-rootfs.sh" "$EDITION"
|
|
"$ROOT/scripts/build-live.sh" "$EDITION"
|
|
|
|
# ── 2. stage files ──────────────────────────────────────────────────────────
|
|
log "staging ISO files for edition '$EDITION'"
|
|
rm -rf "$ISOFILES"
|
|
mkdir -p "$ISOFILES/live" "$ISOFILES/boot/grub" "$ISOFILES/install"
|
|
|
|
KERNEL="$(find "$LIVE_ROOT/boot" -maxdepth 1 -name 'vmlinuz-*' | sort -V | tail -1)"
|
|
INITRD="$(find "$LIVE_ROOT/boot" -maxdepth 1 -name 'initrd.img-*' | sort -V | tail -1)"
|
|
[[ -n "$KERNEL" && -n "$INITRD" ]] || die "kernel or initramfs not found in live rootfs"
|
|
cp -L "$KERNEL" "$ISOFILES/live/vmlinuz"
|
|
cp -L "$INITRD" "$ISOFILES/live/initrd.img"
|
|
|
|
# the clean (installed-system) rootfs is what the installer deploys
|
|
cp "$INSTALL_ARC" "$ISOFILES/install/arcline-$EDITION.tar.xz"
|
|
|
|
# ── 2b. secure boot (optional: ARCLINE_SIGN=1 + a MOK keypair) ──────────────
|
|
if [[ "${ARCLINE_SIGN:-0}" == "1" ]]; then
|
|
log "secure boot: signing boot chain and shipping MOK in the live image"
|
|
[[ -f "$BUILD_DIR/keys/MOK.der" ]] || die "ARCLINE_SIGN=1 but no MOK keypair — run: scripts/secureboot/gen-keys.sh"
|
|
mkdir -p "$LIVE_ROOT/etc/arcline"
|
|
cp "$BUILD_DIR/keys/MOK.der" "$LIVE_ROOT/etc/arcline/MOK.der"
|
|
"$ROOT/scripts/secureboot/sign-image.sh" "$LIVE_ROOT" --keydir "$BUILD_DIR/keys"
|
|
"$ROOT/scripts/secureboot/sign-image.sh" "$ISOFILES" --keydir "$BUILD_DIR/keys"
|
|
fi
|
|
|
|
log "compressing live rootfs → squashfs (this takes a while)"
|
|
# The squashfs is the live session root. Keep it complete — offline man pages
|
|
# and docs are a product promise (see the landing page), so nothing is excluded.
|
|
mksquashfs "$LIVE_ROOT" "$ISOFILES/live/arcline.squashfs" -noappend -comp zstd -Xcompression-level 15 2>/dev/null || \
|
|
mksquashfs "$LIVE_ROOT" "$ISOFILES/live/arcline.squashfs" -noappend -comp xz
|
|
|
|
# ── 3. grub boot config ─────────────────────────────────────────────────────
|
|
log "writing grub config"
|
|
KCMD="$(tr '\n' ' ' < "$(edition_dir "$EDITION")/kernel.cmdline" | sed 's/ */ /g')"
|
|
cat > "$ISOFILES/boot/grub/grub.cfg" <<EOF
|
|
set timeout=5
|
|
set default=0
|
|
|
|
menuentry "Arcline $EDITION ($VERSION)" {
|
|
linux /live/vmlinuz boot=live config quiet $KCMD
|
|
initrd /live/initrd.img
|
|
}
|
|
|
|
menuentry "Arcline $EDITION ($VERSION) — safe mode (no mitigations)" {
|
|
linux /live/vmlinuz boot=live config quiet nomodeset
|
|
initrd /live/initrd.img
|
|
}
|
|
EOF
|
|
|
|
# ── 4. assemble ─────────────────────────────────────────────────────────────
|
|
log "assembling ISO with grub-mkrescue"
|
|
command -v grub-mkrescue >/dev/null || die "grub-mkrescue not found (run scripts/check-host-deps.sh --install)"
|
|
mkdir -p "$ARTIFACT_DIR"
|
|
grub-mkrescue -o "$ARTIFACT" "$ISOFILES" -- \
|
|
-volume-label "ARCLINE_${EDITION^^}" 2>/dev/null || \
|
|
grub-mkrescue -o "$ARTIFACT" "$ISOFILES"
|
|
|
|
# ── 5. verify hybrid boot entries ───────────────────────────────────────────
|
|
# grub-mkrescue builds the ISO bootloader from the HOST's GRUB modules. If the
|
|
# host lacks the x86_64-efi modules or mtools, the ISO has no UEFI boot entry
|
|
# and UEFI firmware shows "make sure there is a bootable uefi x64 image".
|
|
# Check the finished ISO instead of trusting the host.
|
|
if command -v xorriso >/dev/null 2>&1; then
|
|
log "verifying ISO boot entries"
|
|
ET="$(xorriso -indev "$ARTIFACT" -report_el_torito cmd 2>/dev/null || true)"
|
|
|
|
HAS_EFI=0
|
|
echo "$ET" | grep -qiE 'boot_image efi|efi\.img|BOOTX64\.EFI' && HAS_EFI=1
|
|
if [[ $HAS_EFI -eq 0 ]]; then
|
|
# fallback: some xorriso versions name it differently — look in the fs
|
|
xorriso -indev "$ARTIFACT" -find / -name 'efi.img' 2>/dev/null | grep -qi 'efi\.img' && HAS_EFI=1
|
|
fi
|
|
|
|
HAS_BIOS=0
|
|
echo "$ET" | grep -qiE 'boot_image isolinux|boot_image grub' && HAS_BIOS=1
|
|
|
|
[[ $HAS_EFI -eq 1 ]] && log " ✓ EFI boot entry present" || warn " ISO has NO EFI boot entry (UEFI will refuse it)"
|
|
[[ $HAS_BIOS -eq 1 ]] && log " ✓ BIOS boot entry present" || warn " ISO has NO BIOS boot entry (legacy BIOS will refuse it)"
|
|
|
|
if [[ "$BOOT" == "efi" && $HAS_EFI -eq 0 ]]; then
|
|
die "BOOT=efi but the ISO has no EFI boot entry — install grub-efi-amd64-bin + mtools on the build host (scripts/check-host-deps.sh --install), then rebuild"
|
|
fi
|
|
fi
|
|
|
|
log "ISO artifact: $ARTIFACT"
|
|
sha256sum "$ARTIFACT" | tee "$ARTIFACT.sha256"
|