#!/usr/bin/env bash # ───────────────────────────────────────────────────────────────────────────── # Arcline OS — deploy a rootfs to a disk # # scripts/deploy-disk.sh [--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 . 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 [--boot bios|efi]}" ROOTFS="${2:?usage: deploy-disk.sh [--boot bios|efi]}" EDITION="${3:?usage: deploy-disk.sh [--boot bios|efi]}" shift 3 # Optional --boot (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" 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"