Files
os-build/scripts/deploy-disk.sh
Blake Ridgway 51d87daa7b feat: add disk-deploy core, disk image builder, and installer
- deploy-disk.sh: the shared "write a finished system to a disk" step —
  partition (GPT bios/efi) -> btrfs layout via btrfs/init.sh -> rsync
  rootfs -> chroot (real fstab, GRUB, hostname). Carries the optional
  ARCLINE_SIGN hook; the signing tooling itself lands in a later commit.
- apply-fstab.sh: renders the edition fstab template with real root/efi
  UUIDs and drops the swap line.
- build-image.sh: rootfs -> bootable qcow2/raw disk image (sparse file +
  loop device + deploy), the cloud edition's primary output.
- install.sh: scripted installer for a real disk, confirmation-gated.
- Makefile: image-<edition> targets (+ minimal variants); build-edition.sh
  learns the "image" stage; cloud metadata now ships a disk image.
- check-host-deps.sh / GitLab CI: add gdisk, parted, rsync, dosfstools,
  qemu-utils, dpkg, sbsigntool to the build image.
2026-08-21 13:33:17 -05:00

140 lines
6.3 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]}"
BOOT="${4:-bios}"
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"
for tool in sgdisk mkfs.btrfs rsync; do
command -v "$tool" >/dev/null || die "missing host tool '$tool' (run: scripts/check-host-deps.sh --install)"
done
[[ "$BOOT" == "efi" ]] && { command -v mkfs.vfat >/dev/null || die "missing 'mkfs.vfat' (package: dosfstools)"; }
# 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"
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"