From 613c09c6d4afd4d9c9ea858578ccce29d635941c Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 24 Jan 2026 14:28:51 -0600 Subject: Phase 3: Add multi-disk btrfs support (RAID0/RAID1) - RAID1 (mirror) and RAID0 (stripe) for 2+ disks - Multi-disk LUKS with single passphrase prompt - EFI redundancy: GRUB installed on all disks - Pacman hook syncs GRUB updates across EFI partitions - btrfs initramfs hook for multi-device assembly at boot --- custom/archangel | 83 +++++++++++----- custom/lib/btrfs.sh | 276 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 322 insertions(+), 37 deletions(-) (limited to 'custom') diff --git a/custom/archangel b/custom/archangel index ba0c94e..14679cb 100755 --- a/custom/archangel +++ b/custom/archangel @@ -1596,32 +1596,57 @@ install_zfs() { ############################# install_btrfs() { - # Get partition references - local root_part=$(get_root_partition "${SELECTED_DISKS[0]}") - local efi_part=$(get_efi_partition "${SELECTED_DISKS[0]}") - local btrfs_device="$root_part" + local num_disks=${#SELECTED_DISKS[@]} + local btrfs_devices=() + local efi_parts=() + local root_parts=() - # Partition and format - partition_disk "${SELECTED_DISKS[0]}" - format_efi "$efi_part" + # Collect partition references for all disks + for disk in "${SELECTED_DISKS[@]}"; do + root_parts+=("$(get_root_partition "$disk")") + efi_parts+=("$(get_efi_partition "$disk")") + done + + # Partition all disks + for disk in "${SELECTED_DISKS[@]}"; do + partition_disk "$disk" + done + + # Format all EFI partitions + format_efi_partitions "${SELECTED_DISKS[@]}" # LUKS encryption (if enabled) if [[ "$NO_ENCRYPT" != "yes" ]]; then - create_luks_container "$root_part" "$LUKS_PASSPHRASE" - open_luks_container "$root_part" "$LUKS_PASSPHRASE" - btrfs_device="/dev/mapper/$LUKS_MAPPER_NAME" + if [[ $num_disks -eq 1 ]]; then + # Single disk LUKS + create_luks_container "${root_parts[0]}" "$LUKS_PASSPHRASE" + open_luks_container "${root_parts[0]}" "$LUKS_PASSPHRASE" + btrfs_devices=("/dev/mapper/$LUKS_MAPPER_NAME") + else + # Multi-disk LUKS - encrypt each partition + create_luks_containers "$LUKS_PASSPHRASE" "${root_parts[@]}" + open_luks_containers "$LUKS_PASSPHRASE" "${root_parts[@]}" + btrfs_devices=($(get_luks_devices $num_disks)) + fi + else + # No encryption - use raw partitions + btrfs_devices=("${root_parts[@]}") fi - # Create btrfs on the device (raw partition or LUKS mapper) - create_btrfs_volume "$btrfs_device" + # Create btrfs filesystem + if [[ $num_disks -eq 1 ]]; then + create_btrfs_volume "${btrfs_devices[0]}" + else + create_btrfs_volume "${btrfs_devices[@]}" --raid-level "$RAID_LEVEL" + fi - # Create and mount subvolumes - create_btrfs_subvolumes "$btrfs_device" - mount_btrfs_subvolumes "$btrfs_device" + # Create and mount subvolumes (use first device for mount) + create_btrfs_subvolumes "${btrfs_devices[0]}" + mount_btrfs_subvolumes "${btrfs_devices[0]}" - # Mount EFI + # Mount primary EFI mkdir -p /mnt/efi - mount "$efi_part" /mnt/efi + mount "${efi_parts[0]}" /mnt/efi # Install base system install_base_btrfs @@ -1633,14 +1658,24 @@ install_btrfs() { # Configure encryption if enabled if [[ "$NO_ENCRYPT" != "yes" ]]; then - configure_crypttab "$root_part" - configure_luks_grub "$root_part" + configure_crypttab "${root_parts[@]}" + configure_luks_grub "${root_parts[0]}" configure_luks_initramfs fi - generate_btrfs_fstab "$btrfs_device" "$efi_part" + generate_btrfs_fstab "${btrfs_devices[0]}" "${efi_parts[0]}" configure_btrfs_initramfs - configure_grub "$efi_part" + + # GRUB installation + if [[ $num_disks -eq 1 ]]; then + configure_grub "${efi_parts[0]}" + else + # Multi-disk: install GRUB to all EFI partitions + configure_grub "${efi_parts[0]}" + install_grub_all_efi "${efi_parts[@]}" + create_grub_sync_hook "${efi_parts[@]}" + fi + configure_snapper configure_btrfs_services configure_btrfs_pacman_hook @@ -1652,7 +1687,11 @@ install_btrfs() { # Cleanup btrfs_cleanup if [[ "$NO_ENCRYPT" != "yes" ]]; then - close_luks_container + if [[ $num_disks -eq 1 ]]; then + close_luks_container + else + close_luks_containers $num_disks + fi fi print_btrfs_summary } diff --git a/custom/lib/btrfs.sh b/custom/lib/btrfs.sh index 90c5e6d..279897e 100644 --- a/custom/lib/btrfs.sh +++ b/custom/lib/btrfs.sh @@ -68,19 +68,92 @@ close_luks_container() { cryptsetup close "$name" 2>/dev/null || true } +# Multi-disk LUKS functions +create_luks_containers() { + local passphrase="$1" + shift + local partitions=("$@") + + step "Creating LUKS Encrypted Containers" + + local i=0 + for partition in "${partitions[@]}"; do + info "Setting up LUKS encryption on $partition..." + echo -n "$passphrase" | cryptsetup luksFormat --type luks2 \ + --cipher aes-xts-plain64 --key-size 512 --hash sha512 \ + --iter-time 2000 --pbkdf argon2id \ + "$partition" - \ + || error "Failed to create LUKS container on $partition" + ((i++)) + done + + info "Created $i LUKS containers." +} + +open_luks_containers() { + local passphrase="$1" + shift + local partitions=("$@") + + step "Opening LUKS Containers" + + local i=0 + for partition in "${partitions[@]}"; do + local name="${LUKS_MAPPER_NAME}${i}" + [[ $i -eq 0 ]] && name="$LUKS_MAPPER_NAME" # First one has no suffix + info "Opening LUKS container: $partition -> /dev/mapper/$name" + echo -n "$passphrase" | cryptsetup open "$partition" "$name" - \ + || error "Failed to open LUKS container: $partition" + ((i++)) + done + + info "Opened ${#partitions[@]} LUKS containers." +} + +close_luks_containers() { + local count="${1:-1}" + + for ((i=0; i/dev/null || true + done +} + +# Get list of opened LUKS mapper devices +get_luks_devices() { + local count="$1" + local devices=() + + for ((i=0; i /mnt/etc/crypttab - # Create crypttab entry - echo "# LUKS encrypted root" > /mnt/etc/crypttab - echo "$LUKS_MAPPER_NAME UUID=$uuid none luks,discard" >> /mnt/etc/crypttab + local i=0 + for partition in "${partitions[@]}"; do + local uuid + uuid=$(blkid -s UUID -o value "$partition") + local name="${LUKS_MAPPER_NAME}${i}" + [[ $i -eq 0 ]] && name="$LUKS_MAPPER_NAME" - info "crypttab configured for $LUKS_MAPPER_NAME" + echo "$name UUID=$uuid none luks,discard" >> /mnt/etc/crypttab + info "crypttab: $name -> UUID=$uuid" + ((i++)) + done + + info "crypttab configured for $i partition(s)" } configure_luks_initramfs() { @@ -139,15 +212,66 @@ btrfs_preflight() { # Btrfs Volume Creation ############################# +# Create btrfs filesystem (single or multi-device) +# Usage: create_btrfs_volume device1 [device2 ...] [--raid-level level] create_btrfs_volume() { - local partition="$1" + local devices=() + local raid_level="" + + # Parse arguments + while [[ $# -gt 0 ]]; do + case "$1" in + --raid-level) + raid_level="$2" + shift 2 + ;; + *) + devices+=("$1") + shift + ;; + esac + done step "Creating Btrfs Filesystem" - info "Formatting $partition as btrfs..." - mkfs.btrfs -f -L "archroot" "$partition" || error "Failed to create btrfs filesystem" - - info "Btrfs filesystem created on $partition" + local num_devices=${#devices[@]} + + if [[ $num_devices -eq 1 ]]; then + # Single device + info "Formatting ${devices[0]} as btrfs..." + mkfs.btrfs -f -L "archroot" "${devices[0]}" || error "Failed to create btrfs filesystem" + info "Btrfs filesystem created on ${devices[0]}" + else + # Multi-device RAID + local data_profile="raid1" + local meta_profile="raid1" + + case "$raid_level" in + stripe) + data_profile="raid0" + meta_profile="raid1" # Always mirror metadata for safety + info "Creating striped btrfs (RAID0 data, RAID1 metadata) with $num_devices devices..." + ;; + mirror) + data_profile="raid1" + meta_profile="raid1" + info "Creating mirrored btrfs (RAID1) with $num_devices devices..." + ;; + *) + # Default to mirror for safety + data_profile="raid1" + meta_profile="raid1" + info "Creating mirrored btrfs (RAID1) with $num_devices devices..." + ;; + esac + + mkfs.btrfs -f -L "archroot" \ + -d "$data_profile" \ + -m "$meta_profile" \ + "${devices[@]}" || error "Failed to create btrfs filesystem" + + info "Btrfs $raid_level filesystem created on ${devices[*]}" + fi } ############################# @@ -443,6 +567,121 @@ EOF info "GRUB configured with btrfs snapshot support." } +############################# +# EFI Redundancy (Multi-disk) +############################# + +# Install GRUB to all EFI partitions for redundancy +install_grub_all_efi() { + local efi_partitions=("$@") + + step "Installing GRUB to All EFI Partitions" + + local i=1 + for efi_part in "${efi_partitions[@]}"; do + # First EFI at /efi (already mounted), subsequent at /efi2, /efi3, etc. + local chroot_efi_dir="/efi" + local mount_point="/mnt/efi" + local bootloader_id="GRUB" + + if [[ $i -gt 1 ]]; then + chroot_efi_dir="/efi${i}" + mount_point="/mnt/efi${i}" + bootloader_id="GRUB-disk${i}" + + # Mount secondary EFI partitions + if ! mountpoint -q "$mount_point" 2>/dev/null; then + mkdir -p "$mount_point" + mount "$efi_part" "$mount_point" || { warn "Failed to mount $efi_part"; ((i++)); continue; } + # Also create the directory in chroot for grub-install + mkdir -p "/mnt${chroot_efi_dir}" + mount --bind "$mount_point" "/mnt${chroot_efi_dir}" + fi + fi + + info "Installing GRUB to $efi_part ($bootloader_id)..." + arch-chroot /mnt grub-install --target=x86_64-efi \ + --efi-directory="$chroot_efi_dir" \ + --bootloader-id="$bootloader_id" \ + --boot-directory=/boot \ + || warn "GRUB install to $efi_part may have failed (continuing)" + + ((i++)) + done + + info "GRUB installed to ${#efi_partitions[@]} EFI partition(s)." +} + +# Create pacman hook to sync GRUB across all EFI partitions +create_grub_sync_hook() { + local efi_partitions=("$@") + + step "Creating GRUB Sync Hook" + + # Only needed for multi-disk + if [[ ${#efi_partitions[@]} -lt 2 ]]; then + info "Single disk - no sync hook needed." + return + fi + + # Create sync script + local script_content='#!/bin/bash +# Sync GRUB to all EFI partitions after grub package update +# Generated by archangel installer + +set -e + +EFI_PARTITIONS=(' + for part in "${efi_partitions[@]}"; do + script_content+="\"$part\" " + done + script_content+=') + +PRIMARY_EFI="/efi" + +sync_grub() { + local i=0 + for part in "${EFI_PARTITIONS[@]}"; do + if [[ $i -eq 0 ]]; then + # Primary - just reinstall GRUB + grub-install --target=x86_64-efi --efi-directory="$PRIMARY_EFI" \ + --bootloader-id=GRUB --boot-directory=/boot 2>/dev/null || true + else + # Secondary - mount, install, unmount + local mount_point="/tmp/efi-sync-$i" + mkdir -p "$mount_point" + mount "$part" "$mount_point" 2>/dev/null || continue + grub-install --target=x86_64-efi --efi-directory="$mount_point" \ + --bootloader-id="GRUB-disk$((i+1))" --boot-directory=/boot 2>/dev/null || true + umount "$mount_point" 2>/dev/null || true + rmdir "$mount_point" 2>/dev/null || true + fi + ((i++)) + done +} + +sync_grub +' + echo "$script_content" > /mnt/usr/local/bin/grub-sync-efi + chmod +x /mnt/usr/local/bin/grub-sync-efi + + # Create pacman hook + mkdir -p /mnt/etc/pacman.d/hooks + cat > /mnt/etc/pacman.d/hooks/99-grub-sync-efi.hook << 'HOOKEOF' +[Trigger] +Type = Package +Operation = Upgrade +Target = grub + +[Action] +Description = Syncing GRUB to all EFI partitions... +When = PostTransaction +Exec = /usr/local/bin/grub-sync-efi +HOOKEOF + + info "GRUB sync hook created for ${#efi_partitions[@]} EFI partitions." +} + ############################# # Pacman Snapshot Hook ############################# @@ -520,9 +759,16 @@ fallback_options="-S autodetect" EOF # Configure hooks for btrfs - # btrfs module is built into kernel, but we need the btrfs hook for multi-device - sed -i 's/^HOOKS=.*/HOOKS=(base udev microcode modconf kms keyboard keymap consolefont block filesystems fsck)/' \ - /mnt/etc/mkinitcpio.conf + # For multi-device btrfs, we need the btrfs hook for device assembly at boot + local num_disks=${#SELECTED_DISKS[@]} + if [[ $num_disks -gt 1 ]]; then + info "Multi-device btrfs: adding btrfs hook for device assembly" + sed -i 's/^HOOKS=.*/HOOKS=(base udev microcode modconf kms keyboard keymap consolefont block btrfs filesystems fsck)/' \ + /mnt/etc/mkinitcpio.conf + else + sed -i 's/^HOOKS=.*/HOOKS=(base udev microcode modconf kms keyboard keymap consolefont block filesystems fsck)/' \ + /mnt/etc/mkinitcpio.conf + fi # Regenerate initramfs info "Regenerating initramfs..." -- cgit v1.2.3