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).
172 lines
8.0 KiB
Bash
Executable File
172 lines
8.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Arcline OS — rootfs builder
|
|
#
|
|
# scripts/build-rootfs.sh <edition>
|
|
#
|
|
# Pipeline:
|
|
# 1. debootstrap a minimal Debian base
|
|
# 2. configure apt sources (main + security)
|
|
# 3. install the edition package set
|
|
# 4. apply overlays (hardening, configs, service units)
|
|
# 5. run configure-system.sh inside the chroot
|
|
# 6. clean and tar the result → build/artifacts/arcline-<edition>-<version>.tar.xz
|
|
#
|
|
# Root is required (re-execs under sudo when needed).
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
set -euo pipefail
|
|
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
|
|
|
EDITION="${1:?usage: build-rootfs.sh <edition>}"
|
|
validate_edition "$EDITION"
|
|
|
|
# Re-exec under sudo if we aren't root (debootstrap/chroot/mount need it).
|
|
require_root "$0" "$@"
|
|
|
|
# Fail fast on a bad toolchain policy before debootstrap does any work.
|
|
case "$ARCLINE_TOOLCHAIN" in
|
|
auto|skip|require) ;;
|
|
*) 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"
|
|
|
|
mkdir -p "$ROOTFS_DIR" "$LOG_DIR" "$ARTIFACT_DIR"
|
|
|
|
# ── staleness check ─────────────────────────────────────────────────────────
|
|
# Incremental builds: skip rebuilding when no input changed since the last
|
|
# artifact. This is what makes `make iso-<edition>` fast when nothing changed,
|
|
# AND it catches stale archives during development (the bug class that shipped
|
|
# a rootfs with no grub-install). Force with FORCE=1 or `make clean`.
|
|
newer_input() { # <artifact> → 0 if any input is newer than the artifact
|
|
local art="$1"
|
|
[[ -f "$art" ]] || return 0
|
|
find "$ROOT/scripts" "$ROOT/btrfs" "$ROOT/overlays" "$EDIR" \
|
|
-type f -newer "$art" -print -quit 2>/dev/null | grep -q . && return 0
|
|
[[ -d "$DEB_DIR" ]] && find "$DEB_DIR" -name '*.deb' -newer "$art" -print -quit 2>/dev/null | grep -q . && return 0
|
|
[[ "$ROOT/versions.mk" -nt "$art" ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
if [[ "${FORCE:-0}" != "1" && -d "$ROOTFS" && -f "$ARTIFACT" ]] && ! newer_input "$ARTIFACT"; then
|
|
log "rootfs '$EDITION' is up to date — skipping rebuild ($ARTIFACT)"
|
|
log " (FORCE=1 rebuilds anyway)"
|
|
exit 0
|
|
fi
|
|
rm -rf "$ROOTFS" "$ARTIFACT"
|
|
|
|
log "═══ building Arcline $EDITION rootfs ($DISTRO_NAME $VERSION) ═══"
|
|
|
|
# ── 1. bootstrap ────────────────────────────────────────────────────────────
|
|
log "[1/6] debootstrap $DEBIAN_SUITE ($ARCH)"
|
|
debootstrap \
|
|
--arch="$ARCH" \
|
|
--variant=minbase \
|
|
--include=apt-transport-https,ca-certificates,gnupg,curl \
|
|
"$DEBIAN_SUITE" "$ROOTFS" "$DEBIAN_MIRROR" \
|
|
| tee "$LOG_DIR/bootstrap-$EDITION.log"
|
|
|
|
# ── 2. apt sources ──────────────────────────────────────────────────────────
|
|
log "[2/6] configuring apt sources"
|
|
cat > "$ROOTFS/etc/apt/sources.list" <<EOF
|
|
deb $DEBIAN_MIRROR $DEBIAN_SUITE main contrib non-free-firmware
|
|
deb $DEBIAN_MIRROR $DEBIAN_SUITE-updates main contrib non-free-firmware
|
|
deb $SECURITY_MIRROR $DEBIAN_SUITE-security main contrib non-free-firmware
|
|
EOF
|
|
|
|
# ── chroot helpers (bind-mount pseudo-fs, run in chroot, unmount on exit) ──
|
|
mount_pseudo() {
|
|
mount --bind /dev "$ROOTFS/dev"
|
|
mount --bind /proc "$ROOTFS/proc"
|
|
mount --bind /sys "$ROOTFS/sys"
|
|
mount --bind /dev/pts "$ROOTFS/dev/pts" 2>/dev/null || true
|
|
}
|
|
unmount_pseudo() {
|
|
umount -l "$ROOTFS/dev/pts" 2>/dev/null || true
|
|
umount -l "$ROOTFS/sys" 2>/dev/null || true
|
|
umount -l "$ROOTFS/proc" 2>/dev/null || true
|
|
umount -l "$ROOTFS/dev" 2>/dev/null || true
|
|
}
|
|
trap 'unmount_pseudo' EXIT
|
|
chroot_run() { chroot "$ROOTFS" /bin/bash -c "$*"; }
|
|
|
|
# ── 3. install edition packages ─────────────────────────────────────────────
|
|
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"
|
|
unmount_pseudo
|
|
|
|
# ── 4. overlays ─────────────────────────────────────────────────────────────
|
|
log "[4/6] applying overlays"
|
|
"$ROOT/scripts/apply-overlays.sh" "$ROOTFS" "$EDITION"
|
|
|
|
# copy btrfs snapshot/rollback tooling into the image
|
|
install -Dm0755 "$ROOT/btrfs/snapshot.sh" "$ROOTFS/usr/local/sbin/arcline-snapshot"
|
|
install -Dm0755 "$ROOT/btrfs/rollback.sh" "$ROOTFS/usr/local/sbin/arcline-rollback"
|
|
|
|
# ── 5. configure system in chroot ───────────────────────────────────────────
|
|
log "[5/6] configuring system in chroot"
|
|
install -m0755 "$ROOT/scripts/configure-system.sh" "$ROOTFS/root/configure-system.sh"
|
|
# Stage the edition manifest INSIDE the chroot (the hook runs in there and
|
|
# cannot see host paths).
|
|
mkdir -p "$ROOTFS/root/arcline-edition"
|
|
cp "$EDIR/kernel.cmdline" "$EDIR/metadata.yaml" "$ROOTFS/root/arcline-edition/"
|
|
|
|
# Toolchain policy — the Arcline tools are optional in an image build. See
|
|
# ARCLINE_TOOLCHAIN in scripts/common.sh (auto | skip | require).
|
|
HAVE_DEBS=0
|
|
if [[ -d "$DEB_DIR" ]] && ls "$DEB_DIR"/*.deb >/dev/null 2>&1; then
|
|
HAVE_DEBS=1
|
|
fi
|
|
case "$ARCLINE_TOOLCHAIN" in
|
|
require)
|
|
[[ $HAVE_DEBS -eq 1 ]] || die "ARCLINE_TOOLCHAIN=require but no .deb files in $DEB_DIR (run: make toolchain)"
|
|
mkdir -p "$ROOTFS/arcline-debs"
|
|
cp "$DEB_DIR"/*.deb "$ROOTFS/arcline-debs/"
|
|
log "toolchain: staging $(ls "$DEB_DIR"/*.deb | wc -l) packages (required)"
|
|
;;
|
|
skip)
|
|
log "toolchain: skipped (ARCLINE_TOOLCHAIN=skip) — building without Arcline tools"
|
|
;;
|
|
auto)
|
|
if [[ $HAVE_DEBS -eq 1 ]]; then
|
|
mkdir -p "$ROOTFS/arcline-debs"
|
|
cp "$DEB_DIR"/*.deb "$ROOTFS/arcline-debs/"
|
|
log "toolchain: staging $(ls "$DEB_DIR"/*.deb | wc -l) packages"
|
|
else
|
|
warn "no toolchain .debs in $DEB_DIR — building WITHOUT Arcline tools (run: make toolchain)"
|
|
fi
|
|
;;
|
|
*)
|
|
die "ARCLINE_TOOLCHAIN must be auto|skip|require (got '$ARCLINE_TOOLCHAIN')"
|
|
;;
|
|
esac
|
|
|
|
mount_pseudo
|
|
chroot_run "ARCLINE_EDITION_DIR='/root/arcline-edition' ARCLINE_LOCK_ROOT='${ARCLINE_LOCK_ROOT:-0}' ARCLINE_EXTRA_REPOS='${ARCLINE_EXTRA_REPOS:-0}' /root/configure-system.sh '$EDITION'"
|
|
rm -f "$ROOTFS/root/configure-system.sh"
|
|
rm -rf "$ROOTFS/root/arcline-edition" "$ROOTFS/arcline-debs"
|
|
unmount_pseudo
|
|
|
|
# ── 6. clean + archive ──────────────────────────────────────────────────────
|
|
log "[6/6] cleaning and archiving"
|
|
chroot_run "apt-get clean 2>/dev/null; rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/* /root/.bash_history"
|
|
rm -f "$ROOTFS/etc/machine-id"
|
|
: > "$ROOTFS/etc/machine-id"
|
|
|
|
tar -C "$ROOTFS" -cJf "$ARTIFACT" .
|
|
log "rootfs artifact: $ARTIFACT"
|
|
sha256sum "$ARTIFACT" | tee "$ARTIFACT.sha256"
|