Restoring root and home Btrfs subvolumes to a new Fedora install
2023-11-13
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 theroot-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.