- Update (27th June 2022): Since I wrote this I've switched to silverblue, which led me to forget a bit about btrfs snapshotting in Fedora because you get that for free with rpm-ostree. But if anybody reaches this I've found a more complete article describing the whole process for Fedora 36 and adding disk encryption that might be worth reading before mine.
Why Fedora and Btrfs?
Distrohopping has been a major pain when using GNU/Linux. Debian Stable has been my main choice for many years but it's not perfect when dealing with modern hardware. I came back to Fedora after many years not using it with Fedora 31 and found that the system has become way smoother and stable than I remembered. Even the upgrades have become an almost painless process.
As great as Fedora is right now, I still have that feeling that something might fail someday and I wanted to have a recovery system configured. Fedora Silverblue looks terrific and I want to be ready to use it, but I'm not yet unfortunately (I still have to get my head around the one purpose containers and podman).
Until then, btrfs and snapshots seem to be the best option for me.
Fedora 33 base install
The default Fedora 33 installation creates a disk structure like this:
- One 1Gb ext4 partition for /boot
- One Btrfs volume for the rest of the space
- One Btrfs subvolume called "root" mounted at /
- One Btrfs subvolume called "home" mounted at /home
[franute@localhost ~]$ df -h | grep vda
/dev/vda2 35G 7.1G 28G 21% /
/dev/vda2 35G 7.1G 28G 21% /home
/dev/vda1 976M 183M 727M 21% /boot
[franute@localhost ~]$ sudo btrfs sub list /
ID 256 gen 175 top level 5 path home
ID 258 gen 179 top level 5 path root
ID 265 gen 28 top level 258 path var/lib/machines
My main volume is called /dev/vda2 and the root btrfs subvolume has ID 258. Write down your volume path and ID as we'll need them later.
Install & configure snapper
Next I will install snapper and the dnf plugin to generate pre and post transactions every time something is installed on the system and create a configuration for root.
[franute@localhost ~]$ sudo dnf install -y snapper python3-dnf-plugin-snapper
...
Complete!
[franute@localhost ~]$ sudo snapper --config=root create-config /
This will create a subvolume called .snapshots in /, but we would rather have it at a different level to be able to mount it manually if needed and not nested under the "root" subvolume.
[franute@localhost ~]$ sudo btrfs sub list /
ID 256 gen 965 top level 5 path home
ID 258 gen 1038 top level 5 path root
ID 265 gen 966 top level 258 path var/lib/machines
ID 267 gen 1038 top level 258 path .snapshots
Delete that subvolume and create the new one.
franute@localhost ~]$ sudo btrfs sub delete /.snapshots
Delete subvolume (no-commit): '//.snapshots'
[franute@localhost ~]$ sudo mkdir /.snapshots
[franute@localhost ~]$ sudo mkdir /mnt/btrfs
[franute@localhost ~]$ sudo mount /dev/vda2 /mnt/btrfs
[franute@localhost ~]$ cd /mnt/btrfs/
[franute@localhost btrfs]$ ls
home root
[franute@localhost btrfs]$ sudo btrfs sub create snapshots
Create subvolume './snapshots'
[franute@localhost btrfs]$ ls
home root snapshots
[franute@localhost btrfs]$ cd ..
[franute@localhost mnt]$ sudo umount /mnt/btrfs
[franute@localhost mnt]$ sudo rmdir /mnt/btrfs/
Now, let's modify /etc/fstab to mount the snapshots subvolume at boot.
Change it to look similar to this (do not change your UUID, just focus on the options after ext4 or btrfs):
UUID=279ddd2e-e753-483f-8ea9-3a8eab37f5e6 / btrfs defaults 0 0
UUID=1d095325-9db9-4ac9-af8f-54d698535a55 /boot ext4 defaults 1 2
UUID=279ddd2e-e753-483f-8ea9-3a8eab37f5e6 /home btrfs subvol=home 0 0
UUID=279ddd2e-e753-483f-8ea9-3a8eab37f5e6 /.snapshots btrfs subvol=snapshots 0 0
I have added a new entry at the end and changed the subvol=root from the first entry to just defaults. The next change is important. Do you remember that subvolume ID from before (258)? We need to tell the system that we want that to be considered the default when mounting /dev/vda2 if no subvolume is specified. There's also a problem with how Fedora configures the bootloader options by default as it will try to boot from this very specific subvol, making the rollbacks useless.
Now, we will set that subvol as the default and remove a specific arg from the bootloader:
[franute@localhost ~]$ sudo btrfs sub set-default 258 /
[franute@localhost ~]$ sudo grubby --info=ALL
index=0
kernel="/boot/vmlinuz-5.8.15-301.fc33.x86_64"
args="ro rootflags=subvol=root rhgb quiet"
root="UUID=279ddd2e-e753-483f-8ea9-3a8eab37f5e6"
initrd="/boot/initramfs-5.8.15-301.fc33.x86_64.img"
title="Fedora (5.8.15-301.fc33.x86_64) 33 (Workstation Edition)"
id="c6bb0926115440f9b9f721746626df37-5.8.15-301.fc33.x86_64"
index=1
kernel="/boot/vmlinuz-0-rescue-c6bb0926115440f9b9f721746626df37"
args="ro rootflags=subvol=root rhgb quiet"
root="UUID=279ddd2e-e753-483f-8ea9-3a8eab37f5e6"
initrd="/boot/initramfs-0-rescue-c6bb0926115440f9b9f721746626df37.img"
title="Fedora (0-rescue-c6bb0926115440f9b9f721746626df37) 33 (Workstation Edition)"
id="c6bb0926115440f9b9f721746626df37-0-rescue"
[franute@localhost ~]$ sudo grubby --update-kernel=ALL --remove-args="rootflags=subvol=root"
[franute@localhost ~]$ sudo grubby --info=ALL
index=0
kernel="/boot/vmlinuz-5.8.15-301.fc33.x86_64"
args="ro rhgb quiet"
root="UUID=279ddd2e-e753-483f-8ea9-3a8eab37f5e6"
initrd="/boot/initramfs-5.8.15-301.fc33.x86_64.img"
title="Fedora (5.8.15-301.fc33.x86_64) 33 (Workstation Edition)"
id="c6bb0926115440f9b9f721746626df37-5.8.15-301.fc33.x86_64"
index=1
kernel="/boot/vmlinuz-0-rescue-c6bb0926115440f9b9f721746626df37"
args="ro rhgb quiet"
root="UUID=279ddd2e-e753-483f-8ea9-3a8eab37f5e6"
initrd="/boot/initramfs-0-rescue-c6bb0926115440f9b9f721746626df37.img"
title="Fedora (0-rescue-c6bb0926115440f9b9f721746626df37) 33 (Workstation Edition)"
id="c6bb0926115440f9b9f721746626df37-0-rescue"
Now restart, if you now check the default subvolume for / you should see 258:
[franute@localhost ~]$ sudo btrfs sub get-default /
ID 258 gen 1093 top level 5 path root
Snapshots
By default there will be no snapshots in the system, but as soon as you use dnf to install something from the command line, it will generate 2 snapshots. Let's see what happens installing htop for example:
[franute@localhost ~]$ sudo snapper ls
# | Type | Pre # | Date | User | Cleanup | Description | Userdata
---+--------+-------+------+------+---------+-------------+---------
0 | single | | | root | | current |
[franute@localhost ~]$ sudo dnf install -y htop
...
Complete!
[franute@localhost ~]$ sudo snapper ls
# | Type | Pre # | Date | User | Cleanup | Description | Userdata
---+--------+-------+------------------------------+------+---------+------------------------------+---------
0 | single | | | root | | current |
1 | pre | | Sun 24 Jan 2021 18:58:37 WET | root | number | /usr/bin/dnf install -y htop |
2 | post | 1 | Sun 24 Jan 2021 18:58:38 WET | root | number | /usr/bin/dnf install -y htop |
You can see there are 2 new snapshots now: 1 is a snapshot with the state of the system before htop was installed and 2 has the current state (right after it is installed).
Rollback
Let's say that for some reason we want to rollback our system to right before installing htop. We can do it with the rollback operation from snapper:
[franute@localhost ~]$ sudo snapper rollback 1
Creating read-only snapshot of current system. (Snapshot 3.)
Creating read-write snapshot of snapshot 1. (Snapshot 4.)
Setting default subvolume to snapshot 4.
After restarting the system, you'll see that the default subvolume has changed now:
[franute@localhost ~]$ sudo btrfs sub list /
ID 256 gen 1212 top level 5 path home
ID 258 gen 1174 top level 5 path root
ID 265 gen 1060 top level 258 path root/var/lib/machines
ID 268 gen 1167 top level 5 path snapshots
ID 269 gen 1166 top level 268 path snapshots/1/snapshot
ID 270 gen 1110 top level 268 path snapshots/2/snapshot
ID 271 gen 1166 top level 268 path snapshots/3/snapshot
ID 272 gen 1212 top level 268 path snapshots/4/snapshot
[franute@localhost ~]$ sudo btrfs sub get-default /
ID 272 gen 1213 top level 268 path snapshots/4/snapshot
[franute@localhost ~]$ sudo snapper ls
# | Type | Pre # | Date | User | Cleanup | Description | Userdata
---+--------+-------+------------------------------+------+---------+------------------------------+--------------
0 | single | | | root | | current |
1 | pre | | Sun 24 Jan 2021 18:58:37 WET | root | number | /usr/bin/dnf install -y htop |
2 | post | 1 | Sun 24 Jan 2021 18:58:38 WET | root | number | /usr/bin/dnf install -y htop |
3 | single | | Sun 24 Jan 2021 19:01:22 WET | root | number | rollback backup | important=yes
4* | single | | Sun 24 Jan 2021 19:01:22 WET | root | | writable copy of #1 |
We have in fact booted from snapshot 4, which is just a writeable copy of snapshot 1. The previous state is retained in snapshot 3, so you can even go back to that state.
Now let's check if htop is installed:
[franute@localhost ~]$ htop
bash: htop: command not found...
Similar command is: 'top'
Great! It worked.
Setting up Fedora to work with Snapper is not very different from any other distro. The only real problem was the kernel arguments. Once you get rid of that, it works as expected.
Improvements and considerations
- Nested subvolumes will not be considered in the snapshots, you can use that to ignore certain folders. /var/log is a good candidate in that case, here's my setup.
ID 256 gen 21503 top level 5 path root
ID 257 gen 21516 top level 5 path home
ID 261 gen 18514 top level 256 path var/lib/portables
ID 266 gen 18514 top level 256 path var/lib/machines
ID 269 gen 21515 top level 256 path var/log
ID 270 gen 17131 top level 5 path snapshots
ID 310 gen 17129 top level 270 path snapshots/1/snapshot
There's a problem with Gnome Software and apparently software upgrades performed with it will not generate pre and post snapshots, I'd like to dig into this and see how it works.
You can tweak this root configuration we created in several ways. For example you can enable or disable timed snapshots, define when are they cleaned up, how many to store in the system...
Top comments (0)