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).
171 lines
7.7 KiB
Bash
Executable File
171 lines
7.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Arcline OS — deploy a rootfs to a disk
|
|
#
|
|
# scripts/deploy-disk.sh <device> <rootfs> <edition> [--boot bios|efi]
|
|
#
|
|
# The shared "write a finished Arcline system to a block device" step, used by
|
|
# both the disk image builder (scripts/build-image.sh) and the scripted
|
|
# installer (scripts/install.sh):
|
|
#
|
|
# 1. partition the device (GPT):
|
|
# bios: [1M BIOS boot] + [btrfs root]
|
|
# efi: [512M EFI] + [btrfs root]
|
|
# 2. mkfs.btrfs + btrfs/init.sh → @, @home, @log, @snapshots
|
|
# 3. rsync the rootfs into @
|
|
# 4. chroot: write real /etc/fstab (real UUIDs), install GRUB, update-grub
|
|
#
|
|
# DANGER: this wipes <device>. There is no confirmation — the callers are
|
|
# responsible for safety (build-image.sh always uses a throwaway loop device;
|
|
# install.sh asks for explicit confirmation).
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
set -euo pipefail
|
|
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]}"
|
|
shift 3
|
|
|
|
# Optional --boot <bios|efi> (a bare 4th positional is accepted too). Defaults
|
|
# to the BOOT build variable (versions.mk / common.sh).
|
|
BOOT="${BOOT:-bios}"
|
|
if [[ $# -ge 2 && "$1" == "--boot" ]]; then
|
|
BOOT="$2"
|
|
elif [[ $# -ge 1 ]]; then
|
|
BOOT="$1"
|
|
fi
|
|
|
|
require_root "$0" "$@"
|
|
validate_edition "$EDITION"
|
|
[[ "$BOOT" == "bios" || "$BOOT" == "efi" ]] || die "--boot must be bios|efi (got '$BOOT')"
|
|
[[ -d "$ROOTFS" ]] || die "rootfs '$ROOTFS' does not exist (build it first: make rootfs-$EDITION)"
|
|
[[ -e "$DEV" ]] || die "device '$DEV' does not exist"
|
|
|
|
# ── deploy tool pre-flight ─────────────────────────────────────────────────
|
|
# Report EVERY missing tool at once (no one-at-a-time whack-a-mole), with the
|
|
# exact install command. Runs on the build host for image/install builds and
|
|
# inside the live image for the graphical installer.
|
|
declare -A TOOL_PKG=(
|
|
[sgdisk]=gdisk [partprobe]=parted
|
|
[mkfs.btrfs]=btrfs-progs [btrfs]=btrfs-progs
|
|
[rsync]=rsync [wipefs]=util-linux [truncate]=coreutils
|
|
[mkfs.vfat]=dosfstools
|
|
)
|
|
MISSING_TOOLS=()
|
|
for tool in sgdisk partprobe mkfs.btrfs btrfs rsync wipefs truncate mkfs.vfat; do
|
|
[[ "$tool" == "mkfs.vfat" && "$BOOT" != "efi" ]] && continue
|
|
command -v "$tool" >/dev/null 2>&1 || MISSING_TOOLS+=("$tool")
|
|
done
|
|
if [[ ${#MISSING_TOOLS[@]} -gt 0 ]]; then
|
|
pkgs=""
|
|
for t in "${MISSING_TOOLS[@]}"; do pkgs+=" ${TOOL_PKG[$t]}"; done
|
|
die "missing deploy tools: ${MISSING_TOOLS[*]} — install: apt-get install -y${pkgs} (or: scripts/check-host-deps.sh --install)"
|
|
fi
|
|
|
|
# device + partition naming: /dev/sda → /dev/sda1, /dev/nvme0n1 → /dev/nvme0n1p1, /dev/loop0 → /dev/loop0p1
|
|
part_of() {
|
|
if [[ "$1" =~ [0-9]$ ]]; then printf '%sp%s' "$1" "$2"; else printf '%s%s' "$1" "$2"; fi
|
|
}
|
|
|
|
MNT="$(mktemp -d /run/arcline-deploy.XXXXXX)"
|
|
cleanup() {
|
|
umount -R "$MNT" 2>/dev/null || true
|
|
rmdir "$MNT" 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# ── 1. partition ────────────────────────────────────────────────────────────
|
|
log "wiping and partitioning $DEV ($BOOT boot)"
|
|
wipefs -a "$DEV" >/dev/null 2>&1 || true
|
|
sgdisk --zap-all "$DEV" >/dev/null
|
|
case "$BOOT" in
|
|
bios)
|
|
sgdisk -n 1:0:+1M -t 1:ef02 -c 1:"BIOS boot" "$DEV" >/dev/null
|
|
sgdisk -n 2:0:0 -t 2:8300 -c 2:"Arcline root" "$DEV" >/dev/null
|
|
ROOT_PART="$(part_of "$DEV" 2)"
|
|
EFI_PART=""
|
|
;;
|
|
efi)
|
|
sgdisk -n 1:0:+512M -t 1:ef00 -c 1:"EFI System" "$DEV" >/dev/null
|
|
sgdisk -n 2:0:0 -t 2:8300 -c 2:"Arcline root" "$DEV" >/dev/null
|
|
ROOT_PART="$(part_of "$DEV" 2)"
|
|
EFI_PART="$(part_of "$DEV" 1)"
|
|
;;
|
|
esac
|
|
partprobe "$DEV" 2>/dev/null || udevadm settle
|
|
sleep 1
|
|
[[ -b "$ROOT_PART" ]] || die "root partition $ROOT_PART did not appear"
|
|
|
|
# ── 2. filesystems + btrfs layout ───────────────────────────────────────────
|
|
log "formatting root partition $ROOT_PART (btrfs)"
|
|
mkfs.btrfs -f -L arcline-root "$ROOT_PART" >/dev/null
|
|
"$ROOT/btrfs/init.sh" "$ROOT_PART"
|
|
if [[ -n "$EFI_PART" ]]; then
|
|
log "formatting EFI partition $EFI_PART (vfat)"
|
|
mkfs.vfat -F32 -n ARCLINE-EFI "$EFI_PART" >/dev/null
|
|
fi
|
|
|
|
ROOT_UUID="$(blkid -s UUID -o value "$ROOT_PART")"
|
|
EFI_UUID=""
|
|
[[ -n "$EFI_PART" ]] && EFI_UUID="$(blkid -s UUID -o value "$EFI_PART")"
|
|
|
|
# ── 3. mount subvolumes + copy rootfs ───────────────────────────────────────
|
|
log "mounting subvolumes and copying rootfs (this takes a while)"
|
|
mkdir -p "$MNT/root"
|
|
mount -o subvol=@,compress=zstd:3 "$ROOT_PART" "$MNT/root"
|
|
mkdir -p "$MNT/root/home" "$MNT/root/var/log" "$MNT/root/.snapshots"
|
|
mount -o subvol=@home,compress=zstd:3 "$ROOT_PART" "$MNT/root/home"
|
|
mount -o subvol=@log,compress=zstd:3 "$ROOT_PART" "$MNT/root/var/log"
|
|
mount -o subvol=@snapshots,compress=zstd:3 "$ROOT_PART" "$MNT/root/.snapshots"
|
|
if [[ -n "$EFI_PART" ]]; then
|
|
mkdir -p "$MNT/root/boot/efi"
|
|
mount "$EFI_PART" "$MNT/root/boot/efi"
|
|
fi
|
|
|
|
rsync -aHAX --numeric-ids \
|
|
--exclude='/proc/*' --exclude='/sys/*' --exclude='/dev/*' \
|
|
--exclude='/run/*' --exclude='/tmp/*' --exclude='/boot/grub/grub.cfg' \
|
|
"$ROOTFS/" "$MNT/root/"
|
|
|
|
# ── 4. chroot: fstab + grub + hostname ──────────────────────────────────────
|
|
log "configuring system in chroot"
|
|
mount --bind /dev "$MNT/root/dev"
|
|
mount --bind /proc "$MNT/root/proc"
|
|
mount --bind /sys "$MNT/root/sys"
|
|
|
|
# /etc/fstab from the edition template, with real UUIDs (swap line dropped;
|
|
# add a swapfile later if you want one — btrfs swapfiles need nocow).
|
|
"$ROOT/scripts/apply-fstab.sh" "$MNT/root" "$EDITION" "$ROOT_UUID" "$EFI_UUID"
|
|
|
|
# defensive: the deployed rootfs must carry the bootloader. A stale archive
|
|
# from before the BOOT/grub injection has no grub-install at all.
|
|
if ! chroot "$MNT/root" /bin/bash -c 'command -v grub-install >/dev/null 2>&1'; then
|
|
die "grub-install not found in the deployed rootfs — the rootfs archive is stale; rebuild it (FORCE=1 make iso-$EDITION or make clean)"
|
|
fi
|
|
|
|
chroot "$MNT/root" /bin/bash -c '
|
|
set -e
|
|
case "$1" in
|
|
bios) grub-install --target=i386-pc --recheck "$0" ;;
|
|
efi) grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=arcline --recheck ;;
|
|
esac
|
|
update-grub
|
|
' "$DEV" "$BOOT"
|
|
|
|
# ── secure boot (optional: ARCLINE_SIGN=1 + a MOK keypair) ──────────────────
|
|
if [[ "${ARCLINE_SIGN:-0}" == "1" ]]; then
|
|
log "secure boot: shipping MOK and signing installed boot chain"
|
|
[[ -f "$BUILD_DIR/keys/MOK.der" ]] || die "ARCLINE_SIGN=1 but no MOK keypair — run: scripts/secureboot/gen-keys.sh"
|
|
mkdir -p "$MNT/root/etc/arcline"
|
|
cp "$BUILD_DIR/keys/MOK.der" "$MNT/root/etc/arcline/MOK.der"
|
|
"$ROOT/scripts/secureboot/sign-image.sh" "$MNT/root" --keydir "$BUILD_DIR/keys"
|
|
fi
|
|
|
|
if [[ "$EDITION" != "cloud" ]]; then
|
|
echo "$RELEASE_NAME" > "$MNT/root/etc/hostname"
|
|
fi
|
|
|
|
log "deploy complete: $DEV is a bootable $EDITION ($BOOT) system"
|
|
log "root UUID: $ROOT_UUID"
|