diff options
| author | Craig Jennings <c@cjennings.net> | 2026-01-23 22:57:21 -0600 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-01-23 22:57:21 -0600 |
| commit | e4ee55f9706d5567f45b6b4f6f007c09709fdfea (patch) | |
| tree | 93b017f7d3b8c76c752e25b2e7c380019bc33355 /custom/lib | |
| parent | 4560cbdc55e6bd5abe643806e4115d67313db564 (diff) | |
| download | archangel-e4ee55f9706d5567f45b6b4f6f007c09709fdfea.tar.gz archangel-e4ee55f9706d5567f45b6b4f6f007c09709fdfea.zip | |
Phase 2.1: Implement btrfs support
- Create lib/btrfs.sh with full btrfs installation functions
- Subvolume layout matching ZFS datasets (10 subvols)
- Snapper configuration with timeline policy
- GRUB + grub-btrfs for snapshot boot menu
- Update disk.sh for filesystem-aware partition type
- Update archangel with install_btrfs() path
- Update build.sh to include lib/btrfs.sh
- Update plan with Phase 2.7 (test) and 2.8 (LUKS)
Diffstat (limited to 'custom/lib')
| -rw-r--r-- | custom/lib/btrfs.sh | 423 | ||||
| -rw-r--r-- | custom/lib/disk.sh | 11 |
2 files changed, 432 insertions, 2 deletions
diff --git a/custom/lib/btrfs.sh b/custom/lib/btrfs.sh new file mode 100644 index 0000000..c264fce --- /dev/null +++ b/custom/lib/btrfs.sh @@ -0,0 +1,423 @@ +#!/usr/bin/env bash +# btrfs.sh - Btrfs-specific functions for archangel installer +# Source this file after common.sh, config.sh, disk.sh + +############################# +# Btrfs Constants +############################# + +# Mount options for btrfs subvolumes +BTRFS_OPTS="noatime,compress=zstd,space_cache=v2,discard=async" + +# Subvolume layout (matches ZFS dataset structure) +# Format: "name:mountpoint:extra_opts" +BTRFS_SUBVOLS=( + "@:/::" + "@home:/home::" + "@snapshots:/.snapshots::" + "@var_log:/var/log::" + "@var_cache:/var/cache::" + "@tmp:/tmp::nosuid,nodev" + "@var_tmp:/var/tmp::nosuid,nodev" + "@media:/media::compress=no" + "@vms:/vms::nodatacow,compress=no" + "@var_lib_docker:/var/lib/docker::" +) + +############################# +# Btrfs Pre-flight +############################# + +btrfs_preflight() { + step "Checking Btrfs Requirements" + + # Check for btrfs-progs + if ! command_exists mkfs.btrfs; then + error "btrfs-progs not installed. Cannot create btrfs filesystem." + fi + info "btrfs-progs available." + + # Check for required tools + require_command btrfs + require_command grub-install + + info "Btrfs preflight checks passed." +} + +############################# +# Btrfs Volume Creation +############################# + +create_btrfs_volume() { + local partition="$1" + + 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" +} + +############################# +# Subvolume Creation +############################# + +create_btrfs_subvolumes() { + local partition="$1" + + step "Creating Btrfs Subvolumes" + + # Mount the raw btrfs volume temporarily + mount "$partition" /mnt || error "Failed to mount btrfs volume" + + # Create each subvolume + for subvol_spec in "${BTRFS_SUBVOLS[@]}"; do + IFS=':' read -r name mountpoint extra <<< "$subvol_spec" + info "Creating subvolume: $name -> $mountpoint" + btrfs subvolume create "/mnt/$name" || error "Failed to create subvolume $name" + done + + # Unmount raw volume + umount /mnt + + info "Created ${#BTRFS_SUBVOLS[@]} subvolumes." +} + +############################# +# Btrfs Mount Functions +############################# + +mount_btrfs_subvolumes() { + local partition="$1" + + step "Mounting Btrfs Subvolumes" + + # Mount root subvolume first + info "Mounting @ -> /mnt" + mount -o "subvol=@,$BTRFS_OPTS" "$partition" /mnt || error "Failed to mount root subvolume" + + # Create mount points and mount remaining subvolumes + for subvol_spec in "${BTRFS_SUBVOLS[@]}"; do + IFS=':' read -r name mountpoint extra <<< "$subvol_spec" + + # Skip root, already mounted + [[ "$name" == "@" ]] && continue + + # Build mount options + local opts="subvol=$name,$BTRFS_OPTS" + + # Apply extra options (override defaults where specified) + if [[ -n "$extra" ]]; then + # Handle compress=no by removing compress from opts and not adding it + if [[ "$extra" == *"compress=no"* ]]; then + opts=$(echo "$opts" | sed 's/,compress=zstd//') + fi + # Handle nodatacow + if [[ "$extra" == *"nodatacow"* ]]; then + opts="$opts,nodatacow" + opts=$(echo "$opts" | sed 's/,compress=zstd//') + fi + # Handle nosuid,nodev for tmp + if [[ "$extra" == *"nosuid"* ]]; then + opts="$opts,nosuid,nodev" + fi + fi + + info "Mounting $name -> /mnt$mountpoint" + mkdir -p "/mnt$mountpoint" + mount -o "$opts" "$partition" "/mnt$mountpoint" || error "Failed to mount $name" + done + + # Set permissions on tmp directories + chmod 1777 /mnt/tmp /mnt/var/tmp + + info "All subvolumes mounted." +} + +############################# +# Fstab Generation +############################# + +generate_btrfs_fstab() { + local partition="$1" + local efi_partition="$2" + + step "Generating fstab" + + local uuid + uuid=$(blkid -s UUID -o value "$partition") + + # Start with header + cat > /mnt/etc/fstab << EOF +# /etc/fstab - Btrfs subvolume mounts +# IMPORTANT: Using subvol= NOT subvolid= for snapshot compatibility +# Generated by archangel installer + +EOF + + # Add each subvolume + for subvol_spec in "${BTRFS_SUBVOLS[@]}"; do + IFS=':' read -r name mountpoint extra <<< "$subvol_spec" + + # Build mount options + local opts="subvol=$name,$BTRFS_OPTS" + + # Apply extra options + if [[ -n "$extra" ]]; then + if [[ "$extra" == *"compress=no"* ]]; then + opts=$(echo "$opts" | sed 's/,compress=zstd//') + fi + if [[ "$extra" == *"nodatacow"* ]]; then + opts="$opts,nodatacow" + opts=$(echo "$opts" | sed 's/,compress=zstd//') + fi + if [[ "$extra" == *"nosuid"* ]]; then + opts="$opts,nosuid,nodev" + fi + fi + + echo "UUID=$uuid $mountpoint btrfs $opts 0 0" >> /mnt/etc/fstab + done + + # Add EFI partition + local efi_uuid + efi_uuid=$(blkid -s UUID -o value "$efi_partition") + echo "" >> /mnt/etc/fstab + echo "# EFI System Partition" >> /mnt/etc/fstab + echo "UUID=$efi_uuid /efi vfat defaults,noatime 0 2" >> /mnt/etc/fstab + + info "fstab generated with ${#BTRFS_SUBVOLS[@]} btrfs mounts + EFI" +} + +############################# +# Snapper Configuration +############################# + +configure_snapper() { + step "Configuring Snapper" + + # Snapper config for root + # Note: snapper expects /.snapshots to exist and be a subvolume + info "Creating snapper config for root..." + + arch-chroot /mnt snapper -c root create-config / || error "Failed to create snapper config" + + # Snapper creates its own .snapshots subvolume, but we already have @snapshots + # Delete snapper's and use ours + arch-chroot /mnt btrfs subvolume delete /.snapshots 2>/dev/null || true + mkdir -p /mnt/.snapshots + + # Set snapper timeline settings + # Keep: 6 hourly, 7 daily, 2 weekly, 1 monthly + info "Configuring snapshot retention policy..." + cat > /mnt/etc/snapper/configs/root << 'EOF' +# Snapper config for root filesystem +SUBVOLUME="/" +FSTYPE="btrfs" +QGROUP="" + +# Automatic timeline snapshots +TIMELINE_CREATE="yes" +TIMELINE_CLEANUP="yes" + +# Retention policy +TIMELINE_MIN_AGE="1800" +TIMELINE_LIMIT_HOURLY="6" +TIMELINE_LIMIT_DAILY="7" +TIMELINE_LIMIT_WEEKLY="2" +TIMELINE_LIMIT_MONTHLY="1" +TIMELINE_LIMIT_YEARLY="0" + +# Cleanup settings +NUMBER_CLEANUP="yes" +NUMBER_MIN_AGE="1800" +NUMBER_LIMIT="50" +NUMBER_LIMIT_IMPORTANT="10" + +# User access +ALLOW_USERS="" +ALLOW_GROUPS="" + +# Sync ACL +SYNC_ACL="no" + +# Background comparison +BACKGROUND_COMPARISON="yes" + +# Empty pre-post cleanup +EMPTY_PRE_POST_CLEANUP="yes" +EMPTY_PRE_POST_MIN_AGE="1800" +EOF + + # Enable snapper timers + arch-chroot /mnt systemctl enable snapper-timeline.timer + arch-chroot /mnt systemctl enable snapper-cleanup.timer + + info "Snapper configured with timeline snapshots enabled." +} + +############################# +# GRUB Configuration +############################# + +configure_grub() { + local efi_partition="$1" + + step "Configuring GRUB Bootloader" + + # Mount EFI partition + mkdir -p /mnt/efi + mount "$efi_partition" /mnt/efi + + # Configure GRUB defaults + info "Setting GRUB configuration..." + cat > /mnt/etc/default/grub << 'EOF' +# GRUB configuration for btrfs root with snapshots +GRUB_DEFAULT=0 +GRUB_TIMEOUT=5 +GRUB_DISTRIBUTOR="Arch" +GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet" +GRUB_CMDLINE_LINUX="" + +# Btrfs snapshot boot support +GRUB_BTRFS_GRUB_DIRNAME="/efi/grub" + +# Disable os-prober (single-boot system) +GRUB_DISABLE_OS_PROBER=true +EOF + + # Install GRUB to EFI + info "Installing GRUB to EFI partition..." + arch-chroot /mnt grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB \ + || error "GRUB installation failed" + + # Generate GRUB config + info "Generating GRUB configuration..." + arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg \ + || error "Failed to generate GRUB config" + + # Enable grub-btrfsd for automatic snapshot menu updates + info "Enabling grub-btrfs daemon..." + arch-chroot /mnt systemctl enable grub-btrfsd + + info "GRUB configured with btrfs snapshot support." +} + +############################# +# Pacman Snapshot Hook +############################# + +configure_btrfs_pacman_hook() { + step "Configuring Pacman Snapshot Hook" + + # snap-pac handles this automatically when installed + # Just verify it's set up + info "snap-pac will create pre/post snapshots for pacman transactions." + info "Snapshots visible in GRUB menu via grub-btrfs." +} + +############################# +# Genesis Snapshot +############################# + +create_btrfs_genesis_snapshot() { + step "Creating Genesis Snapshot" + + # Use snapper to create the genesis snapshot + arch-chroot /mnt snapper -c root create --description "genesis" \ + || error "Failed to create genesis snapshot" + + info "Genesis snapshot created." + info "Restore with: snapper -c root rollback <number>" + + # Show the snapshot + arch-chroot /mnt snapper -c root list +} + +############################# +# Btrfs Services +############################# + +configure_btrfs_services() { + step "Configuring System Services" + + # Enable standard services + arch-chroot /mnt systemctl enable NetworkManager + arch-chroot /mnt systemctl enable avahi-daemon + + # Snapper timers (already enabled in configure_snapper) + + # grub-btrfsd (already enabled in configure_grub) + + info "System services configured." +} + +############################# +# Btrfs Initramfs +############################# + +configure_btrfs_initramfs() { + step "Configuring Initramfs for Btrfs" + + # Backup original + cp /mnt/etc/mkinitcpio.conf /mnt/etc/mkinitcpio.conf.bak + + # Remove archiso drop-in if present + if [[ -f /mnt/etc/mkinitcpio.conf.d/archiso.conf ]]; then + info "Removing archiso drop-in config..." + rm -f /mnt/etc/mkinitcpio.conf.d/archiso.conf + fi + + # Create proper linux-lts preset + info "Creating linux-lts preset..." + cat > /mnt/etc/mkinitcpio.d/linux-lts.preset << 'EOF' +# mkinitcpio preset file for linux-lts + +PRESETS=(default fallback) + +ALL_kver="/boot/vmlinuz-linux-lts" + +default_image="/boot/initramfs-linux-lts.img" + +fallback_image="/boot/initramfs-linux-lts-fallback.img" +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 + + # Regenerate initramfs + info "Regenerating initramfs..." + arch-chroot /mnt mkinitcpio -P + + info "Initramfs configured for btrfs." +} + +############################# +# Btrfs Cleanup +############################# + +btrfs_cleanup() { + step "Cleaning Up Btrfs" + + # Unmount in reverse order + info "Unmounting subvolumes..." + + # Unmount EFI first + umount /mnt/efi 2>/dev/null || true + + # Unmount all btrfs subvolumes (reverse order) + for ((i=${#BTRFS_SUBVOLS[@]}-1; i>=0; i--)); do + IFS=':' read -r name mountpoint extra <<< "${BTRFS_SUBVOLS[$i]}" + [[ "$name" == "@" ]] && continue + umount "/mnt$mountpoint" 2>/dev/null || true + done + + # Unmount root last + umount /mnt 2>/dev/null || true + + info "Btrfs cleanup complete." +} diff --git a/custom/lib/disk.sh b/custom/lib/disk.sh index fa3dbd2..38c6dd9 100644 --- a/custom/lib/disk.sh +++ b/custom/lib/disk.sh @@ -8,10 +8,17 @@ # Partition a single disk for ZFS/Btrfs installation # Creates: EFI partition (512M) + root partition (rest) +# Uses global FILESYSTEM variable to determine partition type partition_disk() { local disk="$1" local efi_size="${2:-512M}" + # Determine root partition type based on filesystem + local root_type="BF00" # ZFS (Solaris root) + if [[ "$FILESYSTEM" == "btrfs" ]]; then + root_type="8300" # Linux filesystem + fi + info "Partitioning $disk..." # Wipe existing partition table @@ -20,8 +27,8 @@ partition_disk() { # Create EFI partition (512M, type EF00) sgdisk -n 1:0:+${efi_size} -t 1:EF00 -c 1:"EFI" "$disk" || error "Failed to create EFI partition on $disk" - # Create root partition (rest of disk, type BF00 for ZFS or 8300 for Linux) - sgdisk -n 2:0:0 -t 2:BF00 -c 2:"ROOT" "$disk" || error "Failed to create root partition on $disk" + # Create root partition (rest of disk) + sgdisk -n 2:0:0 -t 2:$root_type -c 2:"ROOT" "$disk" || error "Failed to create root partition on $disk" # Notify kernel of partition changes partprobe "$disk" 2>/dev/null || true |
