Restoring root and home Btrfs subvolumes to a new Fedora install
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.
- 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/nvme0n1p3to be mounted at
- external HD contains a Btrfs partition
/dev/sda1to be mounted at
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
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
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
mount /dev/mapper/luks-foo-bar-bas /mnt/internal
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
/mnt/internal directory should now contain the following subvolumes:
Copy important files from
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/crypttabthat currently exists on the
root-backup-rwsubvolume 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
home subvolumes (optional)
Since all our data is on
home-backup-rw, we no longer need the
home subvolumes and can safely delete them.
btrfs subvolume delete /mnt/internal/root btrfs subvolume delete /mnt/internal/home
The subvolumes are referenced in
/etc/fstab and elsewhere as
home. If we don't want to delete the
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.