Restoring root and home Btrfs subvolumes to a new Fedora install

published on 2023-11-13 by hyperreal

Premise

Suppose we have a Fedora installation on a Btrfs filesystem, with two subvolumes called “root” and “home”. If something happened to our system and it could not boot properly, we might want to reinstall Fedora, but we may not want to reinstall all the packages and re-setup the configurations we had. In most cases, restoring the “home” subvolume is seamless, but the root subvolume relies on the presence and contents of certain files in order to be bootable. It’s possible to reinstall Fedora, restore the “root” and “home” subvolumes from snapshots, and boot back into the system as if nothing had happened since the time the snapshots were taken. I’ll describe this process below. We’ll have to do these tasks from a Fedora live environment.

Requirements

  • external HD with the capacity to hold both the “root” and “home” snapshots
  • Fedora live CD

For the purpose of this guide, we’ll assume the following:

  • internal Fedora disk: /dev/nvme0n1p3 to be mounted at /mnt/internal
  • external HD contains a Btrfs partition /dev/sda1 to be mounted at /mnt/external

Create read-only snapshots of “root” and “home”

If our disk is encrypted, we’ll need to run the following command as root to unlock it:

cryptsetup luksOpen /dev/nvme0n1p3 fedora

This will mount the decrypted volume at /dev/mapper/fedora.

The decrypted volume contains the top-level of the Btrfs volume originally created by the Anaconda installer. The top-level contains the “root” and “home” subvolumes. We’ll mount this top-level volume at /mnt/internal:

mount /dev/mapper/fedora /mnt/internal

Now create read-only snapshots of the “root” and “home” subvolumes. They must be read-only in order to send them to an external HD. The destination must also be on the same disk, or else we’ll get an “Invalid cross-device link” error.

btrfs subvolume snapshot -r /mnt/internal/root /mnt/internal/root-backup-ro
btrfs subvolume snapshot -r /mnt/internal/home /mnt/internal/home-backup-ro

Send the read-only snapshots to the external HD.

btrfs send /mnt/internal/root-backup-ro | btrfs receive /mnt/external/
btrfs send /mnt/internal/home-backup-ro | btrfs receive /mnt/external/

This might take a while depending on the size of the snapshots, the CPU, and the read/write performance of the disks.

After the snapshots are sent, unmount the devices.

umount -lR /mnt/internal
umount -lR /mnt/external

Run the Anaconda installer to install a fresh Fedora system

This is straight-forward. Just run the installer and continue to the next step. DO NOT REBOOT after the installation finishes.

Send the read-only snapshots back to the internal disk

After the installation, go back to the terminal and mount the internal and external disks again. If we’ve encrypted the internal disk, it should still be decrypted and mapped to /dev/mapper/luks-foo-bar-bas.

With encryption

mount /dev/mapper/luks-foo-bar-bas /mnt/internal

Unencrypted

mount /dev/nvme0n1p3 /mnt/internal

Mount the external HD again.

mount /dev/sda1 /mnt/external

Send the read-only snapshots back to the internal disk.

btrfs send /mnt/external/root-backup-ro | btrfs receive /mnt/internal/root-backup-ro
btrfs send /mnt/external/home-backup-ro | btrfs receive /mnt/internal/home-backup-ro

Create read-write subvolumes from the read-only snapshots

Create read-write snapshots from the read-only ones. These new read-write snapshots are regular subvolumes.

btrfs subvolume snapshot /mnt/internal/root-backup-ro /mnt/internal/root-backup-rw
btrfs subvolume snapshot /mnt/internal/home-backup-ro /mnt/internal/home-backup-rw

We can now delete the read-only snapshots from both the internal and external disks, if we want to.

btrfs subvolume delete /mnt/internal/root-backup-ro
btrfs subvolume delete /mnt/internal/home-backup-ro
btrfs subvolume delete /mnt/external/root-backup-ro
btrfs subvolume delete /mnt/external/home-backup-ro

The /mnt/internal directory should now contain the following subvolumes:

  • root
  • home
  • root-backup-rw
  • home-backup-rw

Copy important files from root to root-backup-rw

The following files need to be present on the root-backup-rw subvolume in order to boot into it:

  • /etc/crypttab : If we’ve encrypted our internal disk, this file contains the mount options for the encrypted partition. The Anaconda installer generates a unique UUID for the encrypted partition, so this file needs to be in place when it’s referenced during the boot process. The /etc/crypttab that currently exists on the root-backup-rw subvolume has the UUID that was generated in the previous installation, so the boot process will fail if it contains the old contents.
  • /etc/fstab : This file contains the unique UUIDs for each mount point. As with crypttab, the boot process will fail if the contents are different from what the Anaconda installer generated in the current installation.
  • /etc/kernel/cmdline : This file also contains the above information and will cause the boot process to fail if it doesn’t match the current installation.

    cp -v /mnt/internal/root/etc/crypttab /mnt/internal/root-backup-rw/etc/crypttab
    cp -v /mnt/internal/root/etc/fstab /mnt/internal/root-backup-rw/etc/fstab
    cp -v /mnt/internal/root/etc/kernel/cmdline /mnt/internal/root-backup-rw/etc/kernel/cmdline
    

Delete root and home subvolumes (optional)

Since all our data is on root-backup-rw and home-backup-rw, we no longer need the root and home subvolumes and can safely delete them.

btrfs subvolume delete /mnt/internal/root
btrfs subvolume delete /mnt/internal/home

Rename root-backup-rw and home-backup-rw

The subvolumes are referenced in /etc/fstab and elsewhere as root and home. If we don’t want to delete the root and home subvolumes then we have to rename them before using the mv command below.

mv root-backup-rw root
mv home-backup-rw home

Reinstall the kernel and bootloader

The kernel that’s installed by the Anaconda installer is likely older than the kernel on the snapshot root filesystem. We’ll have to chroot and reinstall the kernel and bootloader so that we can boot into the right kernel.

First, unmount the top-level Btrfs volume of the internal disk, then mount the root subvolume, and prepare the mountpoints for chroot.

umount -lR /mnt/internal

mount -o subvol=root,compress=zstd:1 /dev/mapper/luks-foo-bar-bas /mnt/internal

for dir in /dev /proc /run /sys; do mount --rbind $dir /mnt/$dir; done

mount -o subvol=home,compress=zstd:1 /dev/mapper/luks-foo-bar-bas /mnt/internal/home

mount /dev/nvme0n1p2 /mnt/internal/boot
mount /dev/nvme0n1p1 /mnt/internal/boot/efi

cp -v /etc/resolv.conf /mnt/internal/etc/

PS1='chroot# ' chroot /mnt/internal /bin/bash

While in the chroot, ensure we’re connected to the internet, then reinstall the kernel and grub.

ping -c 3 www.google.com

dnf reinstall -y kernel* grub*

exit

Our system should have no problems booting now.

That’s about it

Unmount the filesystems, reboot, and hope for the best.

umount -lR /mnt/internal
umount -lR /mnt/external

systemctl reboot

For more information on working with Btrfs subvolumes and snapshots, have a look at Working with Btrfs - Snapshots by Andreas Hartmann. It’s been super helpful to me throughout this adventure.