Install Fedora 36 with LUKS Full Disk Encryption - Feature Image

Install Fedora 36 with LUKS Full Disk Encryption + snapper + grub-btrfs

In this post, I will show you how to install Fedora 36 with LUKS Full Disk Encryption (FDE) with snapper, and grub-btrfs, enabling you to do a full system rollback. In this setup, the /boot partition is included in the root file system, enabling you to make a full system rollback including kernels.

Encrypting your laptop or computer's drive with LUKS Full Disk Encryption protects the data on it even if the disk is physically removed from the system. To access the decrypted content on the device, you need to provide a passphrase or key file.

Apart from GNOME 42, another major change in Fedora 36 is the relocation of the RPM database from /var/lib/rpm to /usr/lib/sysimage/rpm. This is an important step towards full support for snapshot and rollback regimes, much like OpenSUSE Linux. Because the RPM database is now stored in /usr, the /usr may be rolled back as a unit without affecting /var. This will prevent any failed or undesired updates of the operating system.

Here's a quick rundown of what to anticipate.

  1. Install Fedora 36 Workstation with a LUKS encrypted BTRFS ‘root file system’ and a separate un-encrypted EXT4 /boot partition, as the Anaconda installer does not support encryption of the /boot partition.
  2. Move the contents of the /boot partition to the ‘root file system,’ and configure GRUB to boot from it.
  3. Remove the old /boot partition and merge the space it used into the main BTRFS volume.
  4. Configure the system to ask for the passphrase only once by providing the key file.
  5. Install and configure snapper to take snapshots of the root file system.
  6. Install and configure grub-btrfs so that you are able to boot from snapshots using the GRUB menu.
  7. Finally, test the 'snapper undochange' and 'snapper rollback' commands to make sure everything is working correctly.

If you prefer to install Fedora 36 without LUKS Full Disk Encryption, please visit my other article ‘How to Install Fedora 36 with Snapper and Grub-Btrfs‘.

So let’s begin.

Table of Contents

1. Disk Layout for Installing Fedora 36 Workstation

I'll be using an 80 GiB hard disk. Because the Fedora Anaconda installer does not allow encryption of the /boot partition, either separately or within the root file system, you must first create a non-encrypted /boot partition separately. This is how the disk partition appears at first.

NAME          SIZE   FSTYPE        LABEL   MOUNTPOINT
/dev/sda       80G                            
├─/dev/sda1   512M   vfat          EFI     /boot/efi
├─/dev/sda2    78G   btrfs(LUKS)   FEDORA  /            
└─/dev/sda3   1.5G   ext4          BOOT    /boot

Once the installation is finished, the /boot partition is moved to the root file system, and the final disk partition looks like this.

NAME          SIZE   FSTYPE        LABEL   MOUNTPOINT
/dev/sda       80G                            
├─/dev/sda1   512M   vfat          EFI     /boot/efi
└─/dev/sda2  79.5G   btrfs(LUKS)   FEDORA  /        

And these are the subvolumes created from the btrfs volume (/dev/sda2).

NAME         MOUNTPOINT     TYPE
[main]       /              mainvolume
home         /home          subvolume 
opt          /opt           subvolume 
tmp          /tmp           subvolume 
var          /var           subvolume 
usr-local    /usr/local     subvolume 
snapshots    /.snapshots    subvolume 

Subvolumes created to exclude directories from snapshots:

/home

Contains the user's data. It is excluded to avoid data loss on rollbacks.

/opt

Third-party products usually get installed here. It is excluded to avoid applications installed in /opt from being uninstalled during rollbacks.

/tmp

The directory contains temporary files that you do not need when taking a snapshot.

/var

This directory holds various variable data, such as logs, and temporary caches. It is also the default location for virtual machine images and databases. Therefore, this subvolume is created to exclude all of this variable data. Copy-On-Write will also be disabled on this directory.

/usr/local

Contains software that was manually installed. It is excluded to prevent these installations from being uninstalled during rollbacks.

2. Install Fedora 36 Workstation

Boot your system using the Fedora 36 Workstation installer in UEFI mode. On the welcome screen, select the Install to Hard Drive option. Next, select your LanguageKeyboard, and configure Time & Date. Following that, from the INSTALLATION SUMMARY screen, select Installation Destination.

You should now be on the INSTALLATION DESTINATION screen. To proceed, pick the Advanced Custom (Blivet-GUI) radio button and then hit the Done button.

Install Fedora 36 with LUKS Full Disk Encryption - Blivet Screen

You will see the BLIVET GUI PARTITIONING screen. Here you will create partitions, file systems, and btrfs subvolumes necessary to install Fedora 36 Workstation.

First, you need to create and mount the EFI partition. Select the free space and click the + sign to create a partition.

Install Fedora 36 with LUKS Full Disk Encryption - Blivet Partition

Set the partition Size to 512 MiB, the Filesystem to EFI System Partition, and the Mountpoint to /boot/efi.

Install Fedora 36 with LUKS Full Disk Encryption - EFI Partition

Then, you need to create a btrfs volume where you can create all the subvolumes needed to install Fedora Linux.

Select the free space again and click on the + sign to create a Btrfs volume. (1) Set the Device Type to Btrfs Volume(2) Set the Size of the Btrfs volume. At the end, leave at least 1 GiB for the /boot partition. I’ve left around 1.5 GiB unallocated. (3) Set the name for Btrfs volume. I named the Btrfs volume FEDORA, but you can name it whatever you like. (4) Set the Mountpoint to /, (5) Set the Encryption type to luks1, and (6) provide a strong password (password entropy > 60 bits is suggested). Finally, click on the OK button.

Install Fedora 36 with LUKS Full Disk Encryption - BTRFS Vol

Since support for LUKS2 in GRUB 2.0.6 is limited (Argon2 is not yet supported, only the PBKDF2 is supported), it is recommended to use LUKS1 to encrypt the disk if the /boot directory is in the ‘root file system’ and not on a separate partition. Hopefully, in the next release of GRUB, support for /boot partition encryption using LUKS2 with Argon2i and Argon2id will be added.

You then need to create and mount the /boot partition. Set the boot partition to use all of the remaining unallocated space (1534 MiB), the Filesystem to ext4, and the Mountpoint to /boot. After the installation is finished, I’ll move the contents of the /boot partition to the main root file system and merge this 1.5 GiB space with the main BTRFS volume.

Install Fedora 36 with LUKS Full Disk Encryption - BOOT Partition

Next, you must create subvolumes. (1) Select the Btrfs Volume from the left panel, and (2) click on the + sign on the right panel.

Install Fedora 36 with LUKS Full Disk Encryption - Create Subvolumes

Subvolumes, unlike standard partitions or LVM logical volumes, do not have a size; instead, they behave like directories with shared space. However, the resemblance of subvolumes to directories ends here. Each subvolume, like a file system, has its own file tree, has its own POSIX namespace, and has its own pool of inodes. This means that hard links cannot be created between the subvolumes. From this point of view, a subvolume begins to look more like a separate file system. Subvolumes are not block devices either.

The Btrfs subvolumes are created in reverse order in the Anaconda installer for some reason. It means that the first specified subvolume is created last, and the last specified subvolume is created first. I will therefore specify the subvolumes in reverse order.

Create snapshots subvolume. Enter the Name as snapshots and Mountpoint as /.snapshots. Click OK to finish.

Install Fedora 36 with LUKS Full Disk Encryption - snapshots subvolume

Repeat the same process for the other subvolumes. I created other subvolumes in the following order: usr-local (/usr/local), var (/var), tmp (/tmp), opt (/opt), and lastly home (/home).

When completed, the subvolumes must look something like this. Click Done to create subvolumes.

Install Fedora 36 with LUKS Full Disk Encryption - Preview Subvolumes

As with the swap partition, Fedora generates a SwapOnZRAM upon startup, so no separate swap partition is needed.

Verify that the partitions and subvolumes are properly defined on the SUMMARY OF CHANGES screen. To finalize the changes, click the Accept Changes button.

Install Fedora 36 with LUKS Full Disk Encryption - Confirm Subvolumes

You will be returned to the INSTALLATION SUMMARY screen. Press the Begin Installation button to start the installation process. The installation process will start. Just sit back and relax.

Install Fedora 36 with LUKS Full Disk Encryption - Installation

When the installation is finished, click the Finish installation button and restart the system. After the computer restarts, you will be prompted for the LUKS passphrase.

Install Fedora 36 with LUKS Full Disk Encryption - LUKS1 passphrase

Enter the LUKS passphrase that you provided. The last phase of the installation procedure will start. Click the Start Setup button to complete the remaining customization steps, such as setting a new login, password, and so on.

You will then be logged into the Fedora 36 Workstation with the all-new Gnome 42 desktop interface.

Install Fedora 36 with LUKS Full Disk Encryption - GNOME42

Now, open the Gnome terminal and check your current setup.

List the subvolumes.

# btrfs subvolume list /
ID 256 gen 80 top level 5 path home
ID 257 gen 44 top level 5 path opt
ID 258 gen 80 top level 5 path tmp
ID 259 gen 80 top level 5 path var
ID 260 gen 70 top level 5 path usr-local
ID 261 gen 19 top level 5 path snapshots
ID 262 gen 77 top level 259 path var/lib/machines

Show the BTRFS filesystem.

# btrfs filesystem show /
Label: 'FEDORA'  uuid: e03e52c7-1d69-4f72-b674-5116352637a3
	Total devices 1 FS bytes used 6.57GiB
	devid    1 size 78.00GiB used 8.02GiB path /dev/mapper/luks-6bb9a37b-e6f3-4e21-aeb9-841891d3cd71

List block devices.

# lsblk -pf
NAME                                                      FSTYPE      FSVER LABEL  UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
/dev/sda                                                                                                                               
├─/dev/sda1                                               vfat        FAT32 EFI    818A-7CD7                             497.1M     3% /boot/efi
├─/dev/sda2                                               crypto_LUKS 1            6bb9a37b-e6f3-4e21-aeb9-841891d3cd71                
│ └─/dev/mapper/luks-6bb9a37b-e6f3-4e21-aeb9-841891d3cd71 btrfs             FEDORA e03e52c7-1d69-4f72-b674-5116352637a3   70.6G     8% /var
│                                                                                                                                      /usr/local
│                                                                                                                                      /tmp
│                                                                                                                                      /.snapshots
│                                                                                                                                      /opt
│                                                                                                                                      /home
│                                                                                                                                      /
└─/dev/sda3                                               ext4        1.0   BOOT   2ecae8c7-8cd4-48e9-9faa-dc48d590fbbc    1.2G    10% /boot
/dev/zram0                                                                                                                             [SWAP]

Review /etc/fstab file.

# cat /etc/fstab
UUID=e03e52c7-1d69-4f72-b674-5116352637a3  /            btrfs  defaults,x-systemd.device-timeout=0 0 0 
UUID=e03e52c7-1d69-4f72-b674-5116352637a3  /.snapshots  btrfs  subvol=snapshots,compress=zstd:1,x-systemd.device-timeout=0 0 0 
UUID=2ecae8c7-8cd4-48e9-9faa-dc48d590fbbc  /boot        ext4   defaults        1 2 
UUID=818A-7CD7                             /boot/efi    vfat   umask=0077,shortname=winnt 0 2 
UUID=e03e52c7-1d69-4f72-b674-5116352637a3  /home        btrfs  subvol=home,compress=zstd:1,x-systemd.device-timeout=0 0 0 
UUID=e03e52c7-1d69-4f72-b674-5116352637a3  /opt         btrfs  subvol=opt,compress=zstd:1,x-systemd.device-timeout=0 0 0 
UUID=e03e52c7-1d69-4f72-b674-5116352637a3  /tmp         btrfs  subvol=tmp,compress=zstd:1,x-systemd.device-timeout=0 0 0 
UUID=e03e52c7-1d69-4f72-b674-5116352637a3  /usr/local   btrfs  subvol=usr-local,compress=zstd:1,x-systemd.device-timeout=0 0 0 
UUID=e03e52c7-1d69-4f72-b674-5116352637a3  /var         btrfs  subvol=var,compress=zstd:1,x-systemd.device-timeout=0 0 0 

Review the device LUKS encryption details.

# cryptsetup luksDump /dev/sda2
LUKS header information for /dev/sda2

Version:       	1
Cipher name:   	aes
Cipher mode:   	xts-plain64
Hash spec:     	sha256
Payload offset:	4096
MK bits:       	512
MK digest:     	77 ea cc 07 89 01 a1 11 be 48 a8 4f b3 50 c4 6a b6 c0 bf 80 
MK salt:       	62 39 cf ea 60 73 48 0d 9a 8e 95 7b b3 85 f9 71 
               	d2 fb 9b e7 27 24 51 41 77 56 07 97 1e bf 70 22 
MK iterations: 	86118
UUID:          	6bb9a37b-e6f3-4e21-aeb9-841891d3cd71

Key Slot 0: ENABLED
	Iterations:         	1376082
	Salt:               	e1 b3 30 fb 23 b9 fa e5 a4 c4 4b b1 7e e6 f1 67 
	                      	36 8d d0 51 a0 49 7a d2 5d 7d 81 02 11 36 42 f7 
	Key material offset:	8
	AF stripes:            	4000
Key Slot 1: DISABLED
Key Slot 2: DISABLED
Key Slot 3: DISABLED
Key Slot 4: DISABLED
Key Slot 5: DISABLED
Key Slot 6: DISABLED
Key Slot 7: DISABLED

Because of the Copy-On-Write feature of BTRFS, the performance of qcow2 images and databases suffers when they are on a BTRFS subvolume. Therefore, you need to disable the Copy-On-Write function on the /var directory.

# chattr -R -f +C /var

# lsattr -d /var
---------------C------ /var

3. Move the /boot to the 'root file system'

Now that the installation is complete, transfer the contents of the /boot partition to the 'root file system', and configure GRUB to boot from it.

Unmount the /boot mount recursively.

# umount -vR /boot
umount: /boot/efi unmounted
umount: /boot unmounted

Now mount the /dev/sda3 device into the /mnt directory temporarily.

# mount -v /dev/sda3 /mnt
mount: /dev/sda3 mounted on /mnt.

Copy all boot files from /mnt to the /boot directory.

# cp -arv /mnt/. /boot

Unmount the /dev/sda3 partition from /mnt and delete its entry from the /etc/fstab file. You no longer need the /dev/sda3 partition.

# umount -v /mnt
# sed -i.original '/\/boot.*ext4/d' /etc/fstab
# systemctl daemon-reload
# mount -a

Restore the SELinux labels.

# restorecon -RFv /boot

Enable the CRYPTODISK option in GRUB. When enabled, it will check the encrypted disks and generate the additional commands needed to access them during boot.

# echo "GRUB_ENABLE_CRYPTODISK=y" >> /etc/default/grub
# grub2-mkconfig -o /boot/grub2/grub.cfg

The kernel and initrd paths are configured to look in the parent directory of the previous /boot partition (/dev/sda3). This must be changed to the /boot directory in the ‘root file system’ (/dev/sda2).

# grubby --info=DEFAULT
index=0
kernel="/vmlinuz-5.17.5-300.fc36.x86_64"
args="ro rd.luks.uuid=luks-6bb9a37b-e6f3-4e21-aeb9-841891d3cd71 rhgb quiet"
root="UUID=e03e52c7-1d69-4f72-b674-5116352637a3"
initrd="/initramfs-5.17.5-300.fc36.x86_64.img"
title="Fedora Linux (5.17.5-300.fc36.x86_64) 36 (Workstation Edition)"
id="aa65c3ac5d924efba3b0301f2a0cd167-5.17.5-300.fc36.x86_64"

To do this, first, remove the old Boot Loader Specification (BLS) configuration files.

# rm /boot/loader/entries/*.conf

Then recreate the BLS config file by adding the kernel image.

# kernel-install -v add $(uname -r) /lib/modules/$(uname -r)/vmlinuz

Examine the newly created BLS configuration files. The ‘/boot‘ directory should be added to the kernel and initrd lines.

# grubby --info=DEFAULT
index=0
kernel="/boot/vmlinuz-5.17.5-300.fc36.x86_64"
args="ro rd.luks.uuid=luks-6bb9a37b-e6f3-4e21-aeb9-841891d3cd71 rhgb quiet"
root="UUID=e03e52c7-1d69-4f72-b674-5116352637a3"
initrd="/boot/initramfs-5.17.5-300.fc36.x86_64.img"
title="Fedora Linux (5.17.5-300.fc36.x86_64) 36 (Workstation Edition)"
id="aa65c3ac5d924efba3b0301f2a0cd167-5.17.5-300.fc36.x86_64"

As CRYPTODISK is now enabled, you must make changes to /boot/efi/EFI/fedora/grub.cfg file as well.

Setup access to the encrypted boot device.

# CRYPTOUUID="$(grub2-probe --target=cryptodisk_uuid /)"
# sed -i.backup1 "1i cryptomount -u $CRYPTOUUID" /boot/efi/EFI/fedora/grub.cfg

Replace the old /dev/sda3 device UUID with a LUKS encrypted device UUID where the root file system is mounted.

# OLDBOOTUUID="$(lsblk -dno uuid /dev/sda3)"
# NEWLUKSUUID="$(lsblk -dno uuid /dev/mapper/luks-$(cryptsetup luksUUID /dev/sda2))"
# sed -i.backup2 "s/$OLDBOOTUUID/$NEWLUKSUUID/" /boot/efi/EFI/fedora/grub.cfg

Finally, add /boot to the grub2 path.

# sed -i.backup3 's#($dev)/grub2#($dev)/boot/grub2#' /boot/efi/EFI/fedora/grub.cfg

In the end, your /boot/efi/EFI/fedora/grub.cfg file should look like this.

# cat /boot/efi/EFI/fedora/grub.cfg
cryptomount -u 6bb9a37be6f34e21aeb9841891d3cd71
search --no-floppy --fs-uuid --set=dev e03e52c7-1d69-4f72-b674-5116352637a3
set prefix=($dev)/boot/grub2
export $prefix
configfile $prefix/grub.cfg

The changes are as follows.

  1. Setup access to the encrypted boot device. The UUID here is of the /dev/sda2 partition without dashes.
  2. Change the UUID to the mapped LUKS1 device that contains the root file system.
  3. Insert the '/boot' directory into the ‘set prefix‘ line.

Reboot the computer now to ensure that everything is working correctly. You will be prompted for the passphrase twice. The first time is before loading GRUB, and the second time is before mounting the root file system. Depending on your computer's CPU, it may take up to 30 seconds for the GRUB to display the second passphrase prompt. So please be patient.

4. Delete the /dev/sda3 Partition and Reclaim the Space

You no longer need the /dev/sda3 partition since you moved the boot files to the /boot directory in the root file system. As a result, you may safely delete the /dev/sda3 partition from the partition table and move the 1.5 GiB it occupies to the BTRFS volume (/dev/sda2).

Run the gdisk utility.

# gdisk /dev/sda

On the command input prompt, enter the key p to print the partition table.

Command (? for help): p
Disk /dev/sda: 167772160 sectors, 80.0 GiB
Model: WD800AAJS       
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 748465AC-93DB-4F42-B799-499466E5D0C5
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 167772126
Partitions will be aligned on 2048-sector boundaries
Total free space is 4029 sectors (2.0 MiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         1050623   512.0 MiB   EF00  EFI System Partition
   2         1050624       164628479   78.0 GiB    8300  
   3       164628480       167770111   1.5 GiB     8300  

Delete partition #3 (/dev/sda3) by pressing the key d and then the number 3.

Command (? for help): d
Partition number (1-3): 3

Command (? for help): p
Disk /dev/sda: 167772160 sectors, 80.0 GiB
Model: WD800AAJS       
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 748465AC-93DB-4F42-B799-499466E5D0C5
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 167772126
Partitions will be aligned on 2048-sector boundaries
Total free space is 3145661 sectors (1.5 GiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         1050623   512.0 MiB   EF00  EFI System Partition
   2         1050624       164628479   78.0 GiB    8300  

Then, delete partition #2 (/dev/sda2) as well by pressing the key d and then the number 2. Don’t worry, until you permanently write the modifications, the data will not be lost.

Command (? for help): d
Partition number (1-2): 2

Command (? for help): p
Disk /dev/sda: 167772160 sectors, 80.0 GiB
Model: WD800AAJS       
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 748465AC-93DB-4F42-B799-499466E5D0C5
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 167772126
Partitions will be aligned on 2048-sector boundaries
Total free space is 166723517 sectors (79.5 GiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         1050623   512.0 MiB   EF00  EFI System Partition

Now, create a new partition by pressing the key n. Choose the entire disc space by pressing the [Enter] key four times. Don’t input any values; instead, let it use the defaults. Your data will remain intact with the new expanded size.

Command (? for help): n
Partition number (2-128, default 2): 
First sector (34-167772126, default = 1050624) or {+-}size{KMGTP}: 
Last sector (1050624-167772126, default = 167772126) or {+-}size{KMGTP}: 
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): 
Changed type of partition to 'Linux filesystem'

Command (? for help): p
Disk /dev/sda: 167772160 sectors, 80.0 GiB
Model: WD800AAJS       
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 748465AC-93DB-4F42-B799-499466E5D0C5
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 167772126
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         1050623   512.0 MiB   EF00  EFI System Partition
   2         1050624       167772126   79.5 GiB    8300  Linux filesystem

Finally, hit the w key to permanently save the table to disk and quit.

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/sda.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot or after you
run partprobe(8) or kpartx(8)
The operation has completed successfully.

For the changes to take effect, restart the system. After restarting the system, use the following command to reclaim all available space on the BTRFS volume.

# btrfs filesystem resize max /

Now check the space used up by the BTRFS volume.

# btrfs filesystem show /
Label: 'FEDORA'  uuid: e03e52c7-1d69-4f72-b674-5116352637a3
	Total devices 1 FS bytes used 6.69GiB
	devid    1 size 79.50GiB used 8.02GiB path /dev/mapper/luks-6bb9a37b-e6f3-4e21-aeb9-841891d3cd71

List block devices to verify whether the /dev/sda3 partition has actually been removed.

# lsblk -p /dev/sda
NAME                                                      MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS
/dev/sda                                                    8:0    0   80G  0 disk  
├─/dev/sda1                                                 8:1    0  512M  0 part  /boot/efi
└─/dev/sda2                                                 8:2    0 79.5G  0 part  
  └─/dev/mapper/luks-6bb9a37b-e6f3-4e21-aeb9-841891d3cd71 253:0    0 79.5G  0 crypt /var
                                                                                    /usr/local
                                                                                    /tmp
                                                                                    /opt
                                                                                    /home
                                                                                    /.snapshots
                                                                                    /

5. Bypass the Additional Passphrase Prompt

While GRUB asks for a passphrase to unlock the encrypted /boot files, this information is not passed on to initramfs. As a result, during the initramfs stage, the root (/) must be unlocked once again. This means that either the user must input his passphrase twice, or the initramfs image itself must include a file with the root partition’s passphrase.

Because the initramfs image will be in the /boot directory of the encrypted root partition (/), the passphrase will be secure when the data is at rest. However, once the data is online, it must be safeguarded by making it read-only for the root user.

So let’s create a key file so that the passphrase is only asked once.

Create /etc/cryptsetup-keys.d directory if it is not already there.

# mkdir /etc/cryptsetup-keys.d

Create a key file with 4KiB of random data. The key file has to be in volume.key format.

# dd if=/dev/random \
    of=/etc/cryptsetup-keys.d/luks-$(cryptsetup luksUUID /dev/sda2).key \
    bs=512 count=8

Ensure that only the root user has read access to the key file.

# chmod 0400 /etc/cryptsetup-keys.d/*.key

Attach the created key to the encrypted device’s available key slot. When prompted for a passphrase, enter the normal LUKS passphrase.

# cryptsetup luksAddKey /dev/sda2 \
    /etc/cryptsetup-keys.d/luks-$(cryptsetup luksUUID /dev/sda2).key

Include the key in the initramfs image as well. To do so, open (or create) the /etc/dracut.conf.d/cryptodisk.conf file.

# vim /etc/dracut.conf.d/cryptodisk.conf

And add the following line.

install_items+=" /etc/cryptsetup-keys.d/* "

Finally, rebuild the initramfs image.

# dracut -vf

Reboot the computer and verify that everything is operating correctly. The LUKS passphrase should only be asked once.

6. Install and Configure snapper for the 'root file system'

Install snapper and the optional package python3-dnf-plugin-snapper. The python3-dnf-plugin-snapper package allows you to generate pre and post snapshots every time you install a package on the system with the dnf command-line tool.

$ sudo dnf install snapper python3-dnf-plugin-snapper

Then you must create a new snapper configuration for the BTRFS volume at '/'. Because you previously created a snapshots subvolume with ID=5 in the /.snapshots directory during Fedora installation, attempting to create a new snapper configuration for '/' now will result in an error. To work around this issue, make the following adjustments before creating a new snapper configuration for the '/'.

Remove the directory /.snapshots.

$ sudo umount /.snapshots
$ sudo rmdir /.snapshots

Now, create a new snapper configuration named root for the BTRFS volume at /.

$ sudo snapper -c root create-config /

This will create a configuration file at /etc/snapper/configs/root, a new subvolume .snapshots, and the directory /.snapshots.

Now that the snapper is configured, delete the /.snapshots subvolume.

$ sudo btrfs subvolume delete /.snapshots

This will remove the newly created subvolume .snapshots and directory /.snapshots, but the configuration file at /etc/snapper/configs/root will be kept.

Create the directory /.snapshots again.

$ sudo mkdir /.snapshots

As you have already linked the /.snapshots directory to the snapshots subvolume in the /etc/fstab file, all you have to do now is reload it.

$ sudo systemctl daemon-reload
$ sudo mount -a

Enable snapshot booting by appending the SUSE_BTRFS_SNAPSHOT_BOOTING="true" option to the /etc/default/grub file.

$ echo 'SUSE_BTRFS_SNAPSHOT_BOOTING="true"' | sudo tee -a /etc/default/grub

Update the grub.cfg file.

$ sudo grub2-mkconfig -o /boot/grub2/grub.cfg

Because snapshot booting is now enabled, you must make modifications to the /boot/efi/EFI/fedora/grub.cfg file as well.

$ sudo sed -i '1i set btrfs_relative_path="yes"' /boot/efi/EFI/fedora/grub.cfg

You’re almost done; all that’s left is for the normal user to be able to list snapshots. Add your username to snapper's root config.

$ sudo snapper -c root set-config ALLOW_USERS=$USER SYNC_ACL=yes

And change the group permission of the /.snapshots directory.

$ sudo chown -R :$USER /.snapshots

Finally, disable the GRUB menu auto-hide feature. When Fedora is the only operating system installed on the system, Fedora implements a feature called ‘Hidden Grub Menu‘. The goal is to keep the interface as simple as possible, providing information only when it is necessary. However, you need to visually see the GRUB menu to verify whether the rollback is executed correctly.

This feature can be disabled by doing the following.

$ sudo grub2-editenv - list
saved_entry=aa65c3ac5d924efba3b0301f2a0cd167-5.17.5-300.fc36.x86_64
menu_auto_hide=1
boot_success=1
boot_indeterminate=0

$ sudo grub2-editenv - unset menu_auto_hide

The snapper configuration is now complete. Now list the snapshots.

$ snapper ls
 # | Type   | Pre # | Date | User | Cleanup | Description | Userdata
---+--------+-------+------+------+---------+-------------+---------
0  | single |       |      | root |         | current     |         

At this stage, you do not have any snapshots, we will create some later.

7. Setup Snapshot Booting from the GRUB Menu

As you have already installed snapper on your system, you can rollback to any snapshot by executing snapper rollback from the functioning system.

But what if you make a mistake and the system fails to boot properly the following time? You might wish to revert to a previous working snapshot. This is where snapshot booting from the GRUB menu comes in handy.

Thanks to Antynea, there is already a package called grub-btrfs, which you can now easily install on your system. The package adds "Fedora Linux snapshots" to the GRUB menu and allows you to boot to a snapshot in read-only mode to test it before rolling back to it in read-write mode.

So let's get grub-btrfs installed.

$ git clone https://github.com/Antynea/grub-btrfs.git
$ cd grub-btrfs
$ sudo make install

To make it work with Fedora, you must make a few adjustments to the grub-btrfs config file. So, open the config file and uncheck the lines listed below. Also, the path to grub2-mkconfig is /usr/sbin/grub2-mkconfig, not /usr/bin/grub2-mkconfig.

$ sudo vim /etc/default/grub-btrfs/config
...
GRUB_BTRFS_SHOW_TOTAL_SNAPSHOTS_FOUND="true"
GRUB_BTRFS_GRUB_DIRNAME="/boot/grub2"
GRUB_BTRFS_MKCONFIG=/usr/sbin/grub2-mkconfig
GRUB_BTRFS_SCRIPT_CHECK=grub2-script-check
...

Update the grub.cfg file. You will receive the 'No snapshots found' error since you have not yet created any snapshots, but don't worry, it will function properly after you do.

$ sudo grub2-mkconfig -o /boot/grub2/grub.cfg

Finally, enable the grub-btrfsd service.

$ sudo systemctl enable --now grub-btrfsd

Your grub-btrfs setup is now complete.

8. Create Snapshots and Test the Undochange and Rollback Features

I'll do two tests here. I'll start by installing a package, then run the snapper undochange command to ensure that all modifications have been undone. Second, I will upgrade to the most current kernel, boot into it, purposely render the system unusable, and then attempt to rollback to the same kernel using the snapper rollback command using the post snapshot of the kernel update in an operational state.

Test 1: Install a package and undo it.

I'm going to install a little package called ps_mem here for testing purposes. I'll inspect the system after it's been installed to see what changes have been done. Then I'll try to undo the changes and see if I can get the system back to where it was before I installed it.

Install the ps_mem package. It displays the core memory used per program (not per process).

$ sudo dnf install ps_mem

Run the ps_mem program.

$ sudo ps_mem
 Private  +   Shared  =  RAM used	Program
144.0 KiB +  23.5 KiB = 167.5 KiB	fusermount
288.0 KiB +  25.0 KiB = 313.0 KiB	mcelog
388.0 KiB +  78.5 KiB = 466.5 KiB	rtkit-daemon
492.0 KiB + 117.0 KiB = 609.0 KiB	gnome-session-ctl
....
....
 29.4 MiB +   6.9 MiB =  36.2 MiB	systemd-journald
 44.0 MiB +   8.0 MiB =  52.0 MiB	gnome-software
191.3 MiB +   1.7 MiB = 193.0 MiB	packagekitd
190.7 MiB +  27.6 MiB = 218.3 MiB	gnome-shell
---------------------------------
                          1.0 GiB
=================================

OK, the ps_mem package has been installed successfully. Check the snapper for snapshots.

$ snapper ls
 # | Type   | Pre # | Date                             | User | Cleanup | Description                 | Userdata
---+--------+-------+----------------------------------+------+---------+-----------------------------+---------
0  | single |       |                                  | root |         | current                     |         
1  | pre    |       | Saturday 21 May 2022 11:47:50 AM | root | number  | /usr/bin/dnf install ps_mem |         
2  | post   |     1 | Saturday 21 May 2022 11:47:55 AM | root | number  | /usr/bin/dnf install ps_mem |

As you can see, the pre (#1) and the post (#2) snapshots have been created for the ps_mem package.

Let me now examine the changes it has made to the system, between snapshots #1 and #2.

$ snapper status 1..2
+..... /boot/grub2/grub.cfg.new
+..... /usr/bin/ps_mem
c..... /usr/lib/sysimage/rpm/rpmdb.sqlite-shm
c..... /usr/lib/sysimage/rpm/rpmdb.sqlite-wal
+..... /usr/share/doc/ps_mem
+..... /usr/share/doc/ps_mem/LICENSE
+..... /usr/share/man/man1/ps_mem.1.gz

Now I’ll undo the changes.

$ sudo snapper undochange 1..2
create:0 modify:2 delete:5

Check to see if the package ps_mem was indeed removed.

$ sudo ps_mem
sudo: ps_mem: command not found

Cool! The undo has been successful. But, because I want to keep the package ps_mem, I’ll undo the changes once again. This time from snapshot #2 to snapshot #1.

$ sudo snapper undochange 2..1
create:5 modify:2 delete:0

$ which ps_mem
/usr/bin/ps_mem

Test 2: Update the kernel and do a rollback.

In this rollback test, I'll update to the most latest kernel available at the time of writing. Then I'll boot into it and delete all of the kernel and initrd files, causing the system to fail the next time it boots. Then I'll check if I can recover the system by rolling back to the post snapshot of the kernel update and save the day.

Check the current kernel version.

$ uname -r
5.17.5-300.fc36.x86_64

Now update to the latest kernel.

$ sudo dnf -y update kernel

After the kernel update is finished, restart the system and check the kernel version.

$ uname -r
5.17.8-300.fc36.x86_64

Also, check the snapper for pre (#3) and post (#4) kernel update snapshots.

$ snapper ls
 # | Type   | Pre # | Date                             | User | Cleanup | Description                   | Userdata
---+--------+-------+----------------------------------+------+---------+-------------------------------+---------
0  | single |       |                                  | root |         | current                       |         
1  | pre    |       | Saturday 21 May 2022 11:47:50 AM | root | number  | /usr/bin/dnf install ps_mem   |         
2  | post   |     1 | Saturday 21 May 2022 11:47:55 AM | root | number  | /usr/bin/dnf install ps_mem   |         
3  | pre    |       | Saturday 21 May 2022 12:28:21 PM | root | number  | /usr/bin/dnf -y update kernel |         
4  | post   |     3 | Saturday 21 May 2022 12:29:26 PM | root | number  | /usr/bin/dnf -y update kernel |

For the sake of the experiment, I’ll now delete all kernel and initrd files and make the system unusable.

$ sudo -i

# rm /boot/{vmlinuz,initramfs}*
rm: remove regular file '/boot/vmlinuz-0-rescue-aa65c3ac5d924efba3b0301f2a0cd167'? y
rm: remove regular file '/boot/vmlinuz-5.17.5-300.fc36.x86_64'? y
rm: remove regular file '/boot/vmlinuz-5.17.8-300.fc36.x86_64'? y
rm: remove regular file '/boot/initramfs-0-rescue-aa65c3ac5d924efba3b0301f2a0cd167.img'? y
rm: remove regular file '/boot/initramfs-5.17.5-300.fc36.x86_64.img'? y
rm: remove regular file '/boot/initramfs-5.17.8-300.fc36.x86_64.img'? y

When I rebooted the system, I got this boot error.

Install Fedora 36 with LUKS Full Disk Encryption - Boot Error

Now I’ll return to the GRUB menu and this time I’ll boot from snapshot #4 with the latest kernel.

Install Fedora 36 with LUKS Full Disk Encryption - Snapshots Menu
Install Fedora 36 with LUKS Full Disk Encryption - Select Snapshot No.4
Install Fedora 36 with LUKS Full Disk Encryption - Select Latest Kernel

Open the terminal and make sure everything is back to normal.

# uname -r
5.17.8-300.fc36.x86_64

# ll /boot/{vmlinuz,initramfs}*
-rw-------. 1 root root 103939016 May 19 22:41 /boot/initramfs-0-rescue-aa65c3ac5d924efba3b0301f2a0cd167.img
-rw-------. 1 root root  19805772 May 20 23:55 /boot/initramfs-5.17.5-300.fc36.x86_64.img
-rw-------. 1 root root  19806036 May 21 12:29 /boot/initramfs-5.17.8-300.fc36.x86_64.img
-rwxr-xr-x. 1 root root  11802352 May 20 21:11 /boot/vmlinuz-0-rescue-aa65c3ac5d924efba3b0301f2a0cd167
-rwxr-xr-x. 1 root root  11802352 Apr 28 21:51 /boot/vmlinuz-5.17.5-300.fc36.x86_64
-rwxr-xr-x. 1 root root  11803056 May 16 06:51 /boot/vmlinuz-5.17.8-300.fc36.x86_64

Because you booted into the snapshot, the ‘/‘ file system is read-only.

# btrfs property get -ts /
ro=true

Now that everything appears to be operating properly, it is time to roll back this snapshot in read-write mode.

# snapper ls
 # | Type   | Pre # | Date                             | User | Cleanup | Description                   | Userdata
---+--------+-------+----------------------------------+------+---------+-------------------------------+---------
0  | single |       |                                  | root |         | current                       |         
1  | pre    |       | Saturday 21 May 2022 11:47:50 AM | root | number  | /usr/bin/dnf install ps_mem   |         
2  | post   |     1 | Saturday 21 May 2022 11:47:55 AM | root | number  | /usr/bin/dnf install ps_mem   |         
3  | pre    |       | Saturday 21 May 2022 12:28:21 PM | root | number  | /usr/bin/dnf -y update kernel |         
4- | post   |     3 | Saturday 21 May 2022 12:29:26 PM | root | number  | /usr/bin/dnf -y update kernel |         

# snapper --ambit classic rollback 4
Ambit is classic.
Creating read-only snapshot of current system. (Snapshot 5.)
Creating read-write snapshot of snapshot 4. (Snapshot 6.)
Setting default subvolume to snapshot 6.

# reboot

After the reboot, check the system once again.

# uname -r
5.17.8-300.fc36.x86_64

# btrfs property get -ts /
ro=false

# snapper ls
 # | Type   | Pre # | Date                             | User | Cleanup | Description                   | Userdata     
---+--------+-------+----------------------------------+------+---------+-------------------------------+--------------
0  | single |       |                                  | root |         | current                       |         
1  | pre    |       | Saturday 21 May 2022 11:47:50 AM | root | number  | /usr/bin/dnf install ps_mem   |         
2  | post   |     1 | Saturday 21 May 2022 11:47:55 AM | root | number  | /usr/bin/dnf install ps_mem   |         
3  | pre    |       | Saturday 21 May 2022 12:28:21 PM | root | number  | /usr/bin/dnf -y update kernel |         
4  | post   |     3 | Saturday 21 May 2022 12:29:26 PM | root | number  | /usr/bin/dnf -y update kernel |         
5  | single |       | Saturday 21 May 2022 12:44:25 PM | root | number  | rollback backup               | important=yes
6* | single |       | Saturday 21 May 2022 12:44:26 PM | root |         | writable copy of #4           |              

# btrfs subvolume get-default /
ID 268 gen 273 top level 261 path snapshots/6/snapshot

# btrfs subvolume list /
ID 256 gen 274 top level 5 path home
ID 257 gen 201 top level 5 path opt
ID 258 gen 274 top level 5 path tmp
ID 259 gen 274 top level 5 path var
ID 260 gen 205 top level 5 path usr-local
ID 261 gen 274 top level 5 path snapshots
ID 262 gen 271 top level 259 path var/lib/machines
ID 263 gen 245 top level 261 path snapshots/1/snapshot
ID 264 gen 247 top level 261 path snapshots/2/snapshot
ID 265 gen 251 top level 261 path snapshots/3/snapshot
ID 266 gen 267 top level 261 path snapshots/4/snapshot
ID 267 gen 266 top level 261 path snapshots/5/snapshot
ID 268 gen 273 top level 261 path snapshots/6/snapshot

# ll /boot/{vmlinuz,initramfs}*
-rw-------. 1 root root 103939016 May 19 22:41 /boot/initramfs-0-rescue-aa65c3ac5d924efba3b0301f2a0cd167.img
-rw-------. 1 root root  19805772 May 20 23:55 /boot/initramfs-5.17.5-300.fc36.x86_64.img
-rw-------. 1 root root  19806036 May 21 13:48 /boot/initramfs-5.17.8-300.fc36.x86_64.img
-rwxr-xr-x. 1 root root  11802352 May 20 21:11 /boot/vmlinuz-0-rescue-aa65c3ac5d924efba3b0301f2a0cd167
-rwxr-xr-x. 1 root root  11802352 Apr 28 21:51 /boot/vmlinuz-5.17.5-300.fc36.x86_64
-rwxr-xr-x. 1 root root  11803056 May 16 06:51 /boot/vmlinuz-5.17.8-300.fc36.x86_64

After a successful rollback, always ensure that the grub.cfg file is updated so that the snapshots on the grub-btrfs menu are always in sync.

# grub2-mkconfig -o /boot/grub2/grub.cfg

You have now successfully reverted to the most recent kernel fully functional with read-write mode.

Both tests were successful.

9. Conclusion

With Fedora 36 installed with LUKS Full Disk Encryption, snapper, and grub-btrfs, you can now be certain that your data at rest is safe, and if you make a mistake, you can easily roll back to a previous functioning snapshot.

You can also better secure your machine from Evil Maid attacks by using UEFI Secure Boot custom key enrollment and a self-signed kernel and bootloader. Check out my other blog 'Enable Fedora UEFI Secure Boot with Custom Keys and Self-Signed Bootloader, Kernel, and Modules' on how to do this.

If you want to configure the snapper to take snapshots of the home subvolume as well, or if you want to configure the snapper to take snapshots at predetermined intervals, see the Arch Wiki webpage for further information.

Watch on YouTube