Fedora BTRFS+Snapper - The Fedora 29 Edition

History

It’s 2019 and I’m just getting around to converting my desktop system to Fedora 29. For my work laptop I’ve moved on to Fedora Silverblue (previously known as Atomic Workstation) and will probably move my desktop there soon too as I’ve had a good experience so far. For now I’ll stick my desktop system to this old setup with BTRFS+snapper where I am able to snapshot and rollback the entire system by leveraging BTRFS snapshots, and a tool called snapper.

In the past I have documented this setup and all the steps I took in detail for Fedora 22 (part1 and part2), 24, 25, and 27. This is a condensed continuation of those posts for Fedora 29.

Setting up System with LUKS + LVM + BTRFS

The manual steps for setting up the system are detailed in the part1 blog post from Fedora 22. This time around I have created a script that will quickly configure the system with LUKS + LVM + BTRFS. The script will need to be run in an Anaconda environment just like the manual steps were done in part1 the first time.

You can easily enable ssh access to your Anaconda booted machine by adding inst.sshd to the kernel command line arguments. After booting up you can scp the script over and then execute it to build the system. Please read over the script and modify it to your liking.

Alternatively, for an automated install I have embedded that same script into a kickstart file that you can use. The kickstart file doesn’t really leverage Anaconda at all because it simply runs a %pre script and then reboots the box. It’s basically like just telling Anaconda to run a bash script, but allows you to do it in an automated way. None of the kickstart directives at the top of the kickstart file actually get used.

Installing and Configuring Snapper

After the system has booted for the first time, let’s first delete the subvolume automatically created by systemd at /var/lib/portables.

[root@localhost ~]# btrfs subvolume list /
ID 259 gen 1558 top level 5 path var/lib/portables
[root@localhost ~]#
[root@localhost ~]# btrfs subvolume delete /var/lib/portables                                                                                                                                      
Delete subvolume (no-commit): '/var/lib/portables'
[root@localhost ~]#
[root@localhost ~]# btrfs subvolume list /
[root@localhost ~]#

Next, let’s configure the system for doing snapshots. I still want to be able to track how much size each snapshot has taken so I’ll go ahead and enable quota support on BTRFS. I covered how to do this in a previous post.

[root@localhost ~]# btrfs quota enable /
[root@localhost ~]# btrfs qgroup show /
qgroupid         rfer         excl
--------         ----         ----
0/5           1.26GiB      1.26GiB

Next up is installing snapper. I am also going to install the dnf plugin for snapper so that rpm transactions will automatically get snapshotted:

[root@localhost ~]# dnf install -y snapper python3-dnf-plugins-extras-snapper
...
Complete!

Because of a bug causing an SELinux denial I’ll need to disable snapper when I first create the config:

[root@localhost ~]# setenforce 0
[root@localhost ~]# snapper --config=root create-config /
[root@localhost ~]# setenforce 1

We used the snapper command to create a configuration for BTRFS filesystem mounted at /. Now we can look at the snapshot setup and the current configuration:

[root@localhost ~]# snapper ls
Type   | # | Pre # | Date | User | Cleanup | Description | Userdata
-------+---+-------+------+------+---------+-------------+---------
single | 0 |       |      | root |         | current     |         
[root@localhost ~]# 
[root@localhost ~]# snapper list-configs
Config | Subvolume
-------+----------
root   | /        
[root@localhost ~]# 
[root@localhost ~]# btrfs subvolume list /
ID 261 gen 1760 top level 5 path .snapshots

We can see from the btrfs subvolume list / command that snapper also created a .snapshots subvolume. This subvolume will be used to house the COW snapshots that are taken of the system.

Next, we’ll add an entry to fstab so that regardless of what subvolume we are actually booted in we will always be able to view the .snapshots subvolume and all nested subvolumes (snapshots):

[root@localhost ~]# echo '/dev/vgroot/lvroot /.snapshots btrfs subvol=.snapshots 0 0' >> /etc/fstab

Taking Snapshots

OK, now that we have snapper installed and the .snapshots subvolume in /etc/fstab we can start creating snapshots:

[root@localhost ~]# btrfs subvolume get-default /
ID 5 (FS_TREE)
[root@localhost ~]# snapper create --description "BigBang"
[root@localhost ~]# snapper ls
Type   | # | Pre # | Date                            | User | Cleanup | Description | Userdata
-------+---+-------+---------------------------------+------+---------+-------------+---------
single | 0 |       |                                 | root |         | current     |         
single | 1 |       | Sun 06 Jan 2019 06:12:46 PM UTC | root |         | BigBang     |         
[root@localhost ~]# btrfs subvolume list /
ID 261 gen 1769 top level 5 path .snapshots
ID 262 gen 1769 top level 261 path .snapshots/1/snapshot
[root@localhost ~]# ls /.snapshots/1/snapshot/
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

We made our first snapshot called BigBang and then ran a btrfs subvolume list / to view that a new snapshot was actually created. Notice at the top of the output of the sections that we ran a btrfs subvolume get-default /. This outputs what the currently set default subvolume is for the BTRFS filesystem. Right now we are booted into the root subvolume but that will change as soon as we decide we want to use one of the snapshots for rollback.

Since we took a snapshot let’s go ahead and make some changes to the system by updating the kernel:

[root@localhost ~]# dnf update -y kernel
...
Complete!
[root@localhost ~]# rpm -q kernel
kernel-4.18.16-300.fc29.x86_64
kernel-4.19.13-300.fc29.x86_64
[root@localhost ~]# snapper ls
Type   | # | Pre # | Date                            | User | Cleanup | Description                   | Userdata
-------+---+-------+---------------------------------+------+---------+-------------------------------+---------
single | 0 |       |                                 | root |         | current                       |         
single | 1 |       | Sun 06 Jan 2019 06:12:46 PM UTC | root |         | BigBang                       |         
pre    | 2 |       | Sun 06 Jan 2019 06:14:36 PM UTC | root | number  | /usr/bin/dnf update -y kernel |         
post   | 3 | 2     | Sun 06 Jan 2019 06:15:11 PM UTC | root | number  | /usr/bin/dnf update -y kernel |

So we updated the kernel and the snapper dnf plugin automatically created a pre and post snapshot for us. Let’s reboot the system and see if the new kernel boots properly:

[root@localhost ~]# reboot 
[root@localhost ~]# Connection to 192.168.122.237 closed by remote host.
Connection to 192.168.122.237 closed.
[dustymabe@media images]$ cd
[dustymabe@media ~]$ ssh root@192.168.122.237
root@192.168.122.237's password: 
Last login: Sun Jan  6 18:00:45 2019 from 192.168.122.1
[root@localhost ~]# 
[root@localhost ~]# uname -r
4.19.13-300.fc29.x86_64

Rolling Back

Say we don’t like that new kernel. Let’s go back to the earlier snapshot we made:

[root@localhost ~]# snapper rollback 1 
Creating read-only snapshot of current system. (Snapshot 4.)
Creating read-write snapshot of snapshot 1. (Snapshot 5.)
Setting default subvolume to snapshot 5.
[root@localhost ~]# reboot

snapper created a read-only snapshot of the current system and then a new read-write subvolume based on the snapshot we wanted to go back to. It then sets the default subvolume to be the newly created read-write subvolume. After reboot you’ll be in the newly created read-write subvolume and exactly back in the state you system was in at the time the snapshot was created.

In our case, after reboot we should now be booted into snapshot 5 as indicated by the output of the snapper rollback command above and we should be able to inspect information about all of the snapshots on the system:

[root@localhost ~]# btrfs subvolume get-default /
ID 268 gen 1842 top level 261 path .snapshots/5/snapshot
[root@localhost ~]# snapper ls
Type   | # | Pre # | Date                            | User | Cleanup | Description                   | Userdata                                                                                   
-------+---+-------+---------------------------------+------+---------+-------------------------------+--------------                                                                              
single | 0 |       |                                 | root |         | current                       |                                                                                            
single | 1 |       | Sun 06 Jan 2019 06:12:46 PM UTC | root |         | BigBang                       |                                                                                            
pre    | 2 |       | Sun 06 Jan 2019 06:14:36 PM UTC | root | number  | /usr/bin/dnf update -y kernel |                                                                                            
post   | 3 | 2     | Sun 06 Jan 2019 06:15:11 PM UTC | root | number  | /usr/bin/dnf update -y kernel |                                                                                            
single | 4 |       | Sun 06 Jan 2019 06:18:34 PM UTC | root | number  | rollback backup               | important=yes                                                                              
single | 5 |       | Sun 06 Jan 2019 06:18:35 PM UTC | root |         |                               |                                                                                            
[root@localhost ~]# ls /.snapshots/
1  2  3  4  5
[root@localhost ~]# btrfs subvolume list /
ID 261 gen 1817 top level 5 path .snapshots
ID 262 gen 1816 top level 261 path .snapshots/1/snapshot
ID 263 gen 1775 top level 261 path .snapshots/2/snapshot
ID 264 gen 1787 top level 261 path .snapshots/3/snapshot
ID 265 gen 1816 top level 261 path .snapshots/4/snapshot
ID 266 gen 1842 top level 261 path .snapshots/5/snapshot

And the big test is to see if the change we made to the system was actually reverted:

[root@localhost ~]# uname -r
4.18.16-300.fc29.x86_64
[root@localhost ~]# rpm -q kernel
kernel-4.18.16-300.fc29.x86_64

Enjoy!