Introduction

During the release upgrade of the Fedora based Ultramarine from release 41 to release 42, of the instance originally installed on the Lenovo Legion 5i Pro as Ultramarine 39 using the process described in A Fedora Installation with an openSUSE Style Btrfs Subvolume Layout and Snapper Integration for System Snapshots and Rollbacks, multiple dependency issues required me to manually resolve the issues and restart the upgrade. Because I had not been monitoring the filesystem usage prior to the, after all issues were resolved and the installation began, the space on the filesystem was exhausted, resulting in an incomplete upgrade.

So, I had to reinstall Ultramarine 41 and subsequently upgrade to Ultramarine 42. (Upgrades to Ultramarine 42 are possible but an installation image of Ultramarine is not available at this time.) In my previous installation of Ultramarine, I installed directly on the top level Btrfs subvolume (subvolume path /, subvolume ID 5) and then manually recreated openSUSE's hierarchical subvolume layout, The manual process was necessary because it is not possible to create the four-level hierarchical subvolume layout of openSUSE installations in Anaconda -- even with itsBlivet-GUI advanced partitioning component. This time, I chose to install Ultramarine on a two-level hierarchical subvolume layout similar to that used by Siduction. In the Siduction Btrfs layout, all subvolumes, including the .snapshots subvolume, are created in a flat structure under the top level subvolume. This simpler subvolume layout can be created in Anaconda with its Blivet-GUI partitioning component. The subvolume layout used for the current Ultramarine installation is slightly different from Siduction's subvolume layout in that I didn't create the .snapshots subvolume along with the other subvolumes using Blivet-GUI, but allowed Snapper to create it when creating its configuration for the subvolume mounted at / subsequent to installing it after first boot into the installed system.

In the following set of images, the first image (also the featured image) shows the subvolume layout of the current Ultramarine (Fedora) installation. The left panel shows the subvolume layout as created in Anaconda; the right panel shows the subvolume layout after Snapper creates the necessary subvolume and the directory to which it is mounted, The right panel also shows that after a rollback, one of the subvolumes created as a result of the snapshots becomes the new system root mounted at / replacing the subvolume initially mounted at / as specified in Anaconda. The initial installation is on the subvolume at subvolume path /@-0; after a rollback to Snapshot # L at subvolume path /@-0/.snapshots/L/snapshot, the read-write snapshot created by the rollback, Snapshot # M+2 at subvolume path /@-0/,snapshots/M+2/snapshot, becomes the new system root mounted at /.

The second image shows the Siduction subvolume layout, created automatically by its installer. The only difference is that the subvolume for Snapper snapshots is at the same level as all of the other subvolumes (under the top level-subvolume). The snapshots subvolume could also have been created in Anaconda at the same level, but would have required the additional steps of deleting the subvolume created by Snapper when creating its configuration after installing it after boot into the installed system, and creating the directory to mount it at /.

The third image shows the Ultramarine 39 installation subvolume layout, in the lower left pane, as installed by Anaconda, and in the right pane, after the manual process to recreate the openSUSE layout.

Image 1: The Simple Btrfs Subvolume Layout Created in Anaconda's Blivet-GUI Component
Image 2 shows the Siduction Btrfs Subvolume Layout automatically created by its installer, including the @snapshots subvolume for Snapper. Image 3 shows the subvolume layout of Ultramarine 39 (Fedora 39) on a Btrfs filesystem's default top level subvolume and the subvolume layout after manual conversion to an openSUSE style subvolume layout.

Installation

Using Anaconda to install Fedora, or any other related distribution such as Ultramarine or Red Hat that also uses Anaconda, is familiar to most users, so it is not discussed here. However, using the Anaconda's advanced partitioning component, Blivet-GUI may not be, especially when using it to specify a Btrfs partition and a set of subvolumes for the partition, so it is discussed below.

Partitioning with Blivet-GUI

Blivet-GUI partitioning is activated on Anaconda's disk selection screen through the radio button labeled Advanced Custom (Blivet-GUI), as shown in the following image:

Activating Advanced Custom Partitioning (Blivet-GUI) in the Anaconda Installer

Selecting "Done" at the top of the disk selection screen will bring up the interface to Blivet-GUI. This screen will list devices and volumes in the left side pane and in the main pane, at the top, a representation of the item selected in the left pane, and at the bottom a list of the subvolumes or partitions. The items in the lower part of the main pane can be manipulated by right clicking on the item or selecting the item and using the toolbar items above the listed items.

Image 1

We begin by selecting the disk for the system root in the left pane and in the right pane clicking the free space on the disk, right clicking to display the context menu and selecting "New".

Images 2 - 3

In the resulting dialog, select Btrfs in the "Filesystem dropdown. A partition label, size and other characteristics of the partition can also be specified.

Images 4

After closing the dialog, the main Blivet-GUI interface is shown, with the new partition selected in the left pane, and the Btrfs subvolumes shown in the main pane; at this time only the top level subvolume exists.

Images 5

Right-clicking the top-level subvolume in the right pane and selecting "New" in the context menu causes a dialog to be displayed in which a subvolume under the main subvolume can be created. In the dialog shown in the image, a subvolume named @vartmp is created and a mount point at /var/tmp is specified.

Images 6

The context menu that is displayed when right-clicking the top-level subvolume in the right pane of the Blivet-GUI interface is shown in the image.

Images 7

The subvolume creation dialog is shown in which a subvolume for /var/log named @varlog is shown.

Images 8

The subvolume creation dialog is shown in which a subvolume for /usr/local named @usrlocal is shown.

Images 9

This image shows the main Blivet-GUI interface after all of the subvolumes for our installation have been created. The subvolume layout consists of a flat set of subvolumes under the top level subvolume. Note that the @root subvolume to be mounted at /root was not allowed by Anaconda when the installation partitioning scheme was confirmed because it wants /root to be on the same subvolume as /, in this case @-0. So, this subvolume was deleted before commencing the installation and it is not in the final system.

Images 10

This image shows the context menu presented when right-clicking free space on the second disk in the right pane after the target disk is selected in the left pane. The error condition indicated by the orange bar at the bottom of the screen is due to the @root subvolume mentioned in the description of the previous image.

Images 11

After the Btrfs partition is created for /home, right-clicking on the top-level subvolume in the right pane causes the context menu from which "New" can be selected to create a subvolume.

Images 12

In the resulting dialog a subvolume named @home is created and a mount point of /home is specified.

Images 13

This image shows the Blivet-GUI interface displaying the subvolumes on the partition created for the system root. Under the top-level subvolume, created by default when formatting a Btrfs filesystem, there are a set of subvolumes, all on one level, created for the installation as specified in the previous actions in Blivet-GUI. The subvolume named @-0 is mounted at / and will contain all paths in the filesystem hierarchy except /home and the paths to be excluded from snapshots by creating subvolumes for them. Note that before installation was commenced, the @root-0 subvolume was deleted.

Images 14

This image shows the Blivet-GUI interface displaying the subvolumes on the partition created for /home. There is a single subvolume named @home mounted at /home under the top-level subvolume in the partition.

Images 15

This image shows the summary of changes to be made to the storage devices by the partitioning and subvolume layout scheme specified in Blivet-GUI.

Creating the Btrfs Subvolume Layout with Anaconda's Blivet-GUI Component

Post Install Configuration

In the previous installation of Ultramarine 39, described in A Fedora Installation with an openSUSE Style Btrfs Subvolume Layout and Snapper Integration for System Snapshots and Rollbacks, the manual process to recreate the openSUSE style subvolume layout and move the installed system into the appropriate subvolume, required a lot of configuration in the final installed system. With the current simpler installation many of the steps described in that article are not necessary. All that will be required is to

  • Install Snapper and create a Snapper configuration for the subvolume mounted at /. This action will also create a subvolume named .snapshots under the subvolume mounted at /, and the directory /.snapshots
  • Optionally, create a Snapper configuration for the subvolume mounted at /home, if snapshots are desired for this subvolume.
  • Add the .snapshots subvolume to /etc/fstab, specifying a mountpoint at /.snapshots. If a configuration for the subvolume mounted at /home was created, also add its subvolume created by Snapper, whose subvolume path in the partition should be at /@home/.snapshots with a corresponding directory for mounting at /home/.snapshots
  • Install the Snapper DNF plugin. This also requires creating a configuration file that defines the actions associated with the plugin's hooks.
  • Modify Fedora's default GRUB configuration to disable GRUB BLS and delete loader entries.
  • Delete the GRUB Environment Block
  • Install grub-btrfs and enable its related systemd unit.
  • Modify the Snapper configuration to reflect the desired number of automatic timeline and boot snapshots to be made and the automatic cleanup settings.
  • Enable the Snapper helper systemd timers snapper-boot.timer, snapper-timeline.timer, and snapper-cleanup.timer.
  • Enable the systemd Btrfs maintenance timers btrfs-balance.timer, btrfs-scrub.timer, btrfs-defrag.timer
These steps are described below.

Install and Confiure Snapper

  1. Install Snapper with:
    sudo dnf install snapper
    The command with its output:
    brook on ultramarine-16ith6 ~
    ❯ sudo dnf install snapper
    [sudo] password for brook:
    Updating and loading repositories:
    Repositories loaded.
    Package                           Arch      Version                           Repository              Size
    Installing:
     snapper                          x86_64    0.11.0-2.fc41                     fedora               1.7 MiB
    Installing dependencies:
     libbtrfs                         x86_64    6.16-1.fc41                       updates             45.2 KiB
     libbtrfsutil                     x86_64    6.16-1.fc41                       updates             66.2 KiB
     snapper-libs                     x86_64    0.11.0-2.fc41                     fedora               1.0 MiB
    
    Transaction Summary:
     Installing:         4 packages
    
    Total size of inbound packages is 964 KiB. Need to download 964 KiB.
    After this operation, 3 MiB extra will be used (install 3 MiB, remove 0 B).
    Is this ok [Y/n]:
    [1/4] libbtrfsutil-0:6.16-1.fc41.x86_64                           100% | 192.4 KiB/s |  33.1 KiB |  00m00s
    [2/4] libbtrfs-0:6.16-1.fc41.x86_64                               100% | 150.7 KiB/s |  27.7 KiB |  00m00s
    [3/4] snapper-0:0.11.0-2.fc41.x86_64                              100% |   1.3 MiB/s | 530.2 KiB |  00m00s
    [4/4] snapper-libs-0:0.11.0-2.fc41.x86_64                         100% | 895.9 KiB/s | 372.7 KiB |  00m00s
    ----------------------------------------------------------------------------------------------------------
    [4/4] Total                                                       100% |   1.0 MiB/s | 963.8 KiB |  00m01s
    Running transaction
    [1/6] Verify package files                                        100% | 363.0   B/s |   4.0   B |  00m00s
    [2/6] Prepare transaction                                         100% |   5.0   B/s |   4.0   B |  00m01s
    [3/6] Installing libbtrfsutil-0:6.16-1.fc41.x86_64                100% |   2.1 MiB/s |  67.5 KiB |  00m00s
    [4/6] Installing libbtrfs-0:6.16-1.fc41.x86_64                    100% |   1.8 MiB/s |  46.4 KiB |  00m00s
    [5/6] Installing snapper-libs-0:0.11.0-2.fc41.x86_64              100% |  32.2 MiB/s |   1.0 MiB |  00m00s
    [6/6] Installing snapper-0:0.11.0-2.fc41.x86_64                   100% |   1.0 MiB/s |   1.7 MiB |  00m02s
    Complete!
    
  2. Create a Snapper configuration for the Btrfs subvolume mounted at / with:
    sudo snapper -c root create-config /
    The command has no output if the operation is successful.

The Konsole window in the left half of the following image shows the effect of the above commands. The first command in the window displays the subvolumes before Snapper is installed and the configuration is created. The second shows the subvolumes after Snapper is installed and the configuration created, the difference being that there is a new subvolume named .snapshots at subvolume path /@-0/.snapshots.

The Effect of Installing Snapper and Creating a Configuration for It
Creating a Snapper Configuration Causes a Subvolume .snapshots at subvolume path /@-0/.snapshots to Be Created. The Konsole window on the left shows the subvolumes on the partition before and after the configuration is created. The Konsole window on the right shows the installation of Snapper and the creation of its configuration.

The action also causes a directory named .snapshots to be created.

Creating a Snapper Configuration Also Causes the directory /.snapshots to Be Created
This is used in /etc/fstab to mount the .snapshots subvolume.

Modify /etc/fstab

In order for Snapper to use the new subvolume, it must be added to /etc/fstab, i.e, a line like

UUID=4e628d85-695c-42c3-a582-8cb3ae370df7       .snapshots              btrfs   subvol=@-0/.snapshots,autodefrag,compress=zstd:3        0 0
must be added to /etc/fstab. Note, that when adding this line, I took the opportunity to change the Btrfs zstd level to 3, from Anaconda's default level of 1, as well as adding the autodefrag mount option for the Btrfs subvolumes. The modified /etc/fstab is shown in the following image.
The Modified /etc/fstab with an Entry for the .snapshots Subvolume

Install Snapper DNF Plugin

  1. Install the Snapper DNF Plugin with:
    sudo dnf install python3-dnf-plugin-snapper
  2. Also install libdnf5-plugin-actions with:
    sudo dnf install libdnf5-plugin-actions
    The command with the output:
    brook on ultramarine-16ith6 ~ took 2s
    ❯ sudo dnf install libdnf5-plugin-actions
    Updating and loading repositories:
    Repositories loaded.
    Package                                               Arch         Version                                                Repository                         Size
    Installing:
     libdnf5-plugin-actions                               x86_64       5.2.16.0-1.fc41                                        updates                       265.2 KiB
    
    Transaction Summary:
     Installing:         1 package
    
    Total size of inbound packages is 158 KiB. Need to download 158 KiB.
    After this operation, 265 KiB extra will be used (install 265 KiB, remove 0 B).
    Is this ok [Y/n]:
    [1/1] libdnf5-plugin-actions-0:5.2.16.0-1.fc41.x86_64                                                                    100% | 330.8 KiB/s | 158.4 KiB |  00m00s
    -----------------------------------------------------------------------------------------------------------------------------------------------------------------
    [1/1] Total                                                                                                              100% | 232.0 KiB/s | 158.4 KiB |  00m01s
    Running transaction
    [1/3] Verify package files                                                                                               100% | 200.0   B/s |   1.0   B |  00m00s
    [2/3] Prepare transaction                                                                                                100% |   1.0   B/s |   1.0   B |  00m01s
    [3/3] Installing libdnf5-plugin-actions-0:5.2.16.0-1.fc41.x86_64                                                         100% | 344.8 KiB/s | 269.3 KiB |  00m01s
    Complete!
    

    This creates the directory /etc/dnf/libdnf5-plugins and a subdirectory, /etc/dnf/libdnf5-plugins/actions.d. Unfortunately, a necessary file that defines the Snapper commands to execute in order to create the snapshots -- in the DNF5 actions plugin's required format -- is not provided by the maintainers of the package. Fortunately, the Fedora forum has a post that provides suitable contents for the file, that not only create the automated Pre and Post DNF transaction snapshots, but also specifies a description for the snapshot. We will create the file and populate it with the forum provided actions commands.

  3. Create the file /etc/dnf/libdnf5-plugins/actions.d/snapper.actions
  4. Add the text in the following listing to the file:
    # Pre and Post DNF transaction Snapper snapshot creation through DNF Snapper plugin
    # See https://discussion.fedoraproject.org/t/getting-snapper-btrfs-assistant-to-work-with-dnf5/133948/1
    # Get snapshot description
    pre_transaction::::/usr/bin/sh -c echo\ "tmp.cmd=$(ps\ -o\ command\ --no-headers\ -p\ '${pid}')"
    # Creates pre snapshot before the transaction and stores the snapshot number in the "tmp.snapper_pre_number"  variable.
    pre_transaction::::/usr/bin/sh -c echo\ "tmp.snapper_pre_number=$(snapper\ create\ -t\ pre\ -p\ -d\ '${tmp.cmd}')"
    
    # If the variable "tmp.snapper_pre_number" exists, it creates post snapshot after the transaction and removes the variable "tmp.snapper_pre_number".
    post_transaction::::/usr/bin/sh -c [\ -n\ "${tmp.snapper_pre_number}"\ ]\ &&\ snapper\ create\ -t\ post\ --pre-number\ "${tmp.snapper_pre_number}"\ -d\ "${tmp.cmd}"\ ;\ echo\ tmp.snapper_pre_number\ ;\ echo\ tmp.cmd

After the installation of the above two packages and the creation of the actions file, DNF transactions cause the Pre and POst snapshots to be created. The following image displays the output of snapper list showing that Pre and Post snapshots have been created during DNF transactions. It also shows the corresponding directories/snapshots in the filesystem hierarchy.

The Output of snapper list Showing Snapshots Have Been Created During Package Management Transactions
The second Konsole window shows the snapshots at the filesystem hierarchy locations.

(It may be interesting to know, it was not necessary to manually create the actions file when installing Fedora 39 (Ultramarine 39), as the previous version of DNF was used at that time. It was not necessary after upgrade to Ultramarine 40 and 41, after transition to DNF5, as the file must have been provided automatically as part of a package that was installed automatically during the upgrade to 40 and 41, as there were no problems with Snapper DNF transaction snapshots.)

Modify Fedora's Default GRUB Configuration

The default Fedora GRUB configuration enables BLS, an effort to standardize GRUBfor multiple platforms, some as different from x86_64 as S390, by replacing elements of the traditional GRUB configuration with drop in "scriptlets" for each platform. During the previous installation of Ultramarine 39, this configuration interfered with grub-btrfs's functionality of creating and displaying a GRUB submenu of read-only system snapshots, necessitating the disablement of the feature.

I chose to disable GRUB BLS in this installation of Ultramarine also. To disable the feature, in /etc/default/grub, change

GRUB_ENABLE_BLSCFG=true
to
GRUB_ENABLE_BLSCFG="false"

Optionally, if a simpler Linux kernel is desired in /boot/grub2/grub.cfg, i.e.,

/boot/vmlinuz-6.7.6-200.fc39.x86_64
instead of
/@-0/boot/vmlinuz-6.15.10-100.fc41.x86_64
for the initial system, or
/@-0/.snapshots/XX/snapshot/boot/vmlinuz-6.7.6-200.fc39.x86_64
for the system after a rollback, where XX is the snapshot that is the current system root, modify /etc/default/grub to include:
SUSE_BTRFS_SNAPSHOT_BOOTING="true"
If this parameter is included in /etc/default/grub, it will allow GRUB to use relative paths when searching for kernels to boot by setting the relevant parameter found in one of the grub2-mkconfig sourced scripts, /etc/grub.d/00_header, to be set, as shown in the following listing.
if [ "x${SUSE_BTRFS_SNAPSHOT_BOOTING}" = "xtrue" ] &&
   [ "x${GRUB_FS}" = "xbtrfs" ] ; then
    cat <

In my opinion, the latter the un-simplified Linux kernel path is preferable, as it allows editing the GRUB configuration to specify a specific snapshot and a kernel path for the boot by selecting a GRUB menu entry and pressing ESC to enter the editing mode. This is helpful if there is a problem with the GRUB configuration which requires manual selection of the snapshot that contains the current system root.

Delete GRUB Environment Block

GRUB has a mechanism to save state information across boots. It is necessary to have a notion of how this mechanism works before describing the reason it may be necessary to delete the information saved by this mechanism.

The state is saved in GRUB environment variables in a text file at /boot/grub2/grubenv, known as the GRUB environment. One possible variable is saved_entry, which the value of which refers to a a file in the directory /boot/loader/entries via the name, i.e., the value of the saved_entry is the same as the filename of the a file in /boot/loader/entries not including its .conf extension. The .conf file contains the characteristics of a GRUB entry. The next three listings show the elements of this mechanism.

The listing below shows the contents of the text file /boot/grub2/grubenv, which shouldn't be manipulated directly, but with the grub2-editenv command.

brook on ultramarine-16ith6 ~ 
❯ sudo ls -la /boot/grub2/grubenv                                                             
-rw-------. 1 root root 1024 Aug 21 02:58 /boot/grub2/grubenv

brook on ultramarine-16ith6 ~ 
❯ sudo cat /boot/grub2/grubenv   
# GRUB Environment Block
# WARNING: Do not edit this file by tools other than grub-editenv!!!
saved_entry=68f1ea76b293441d96a4288def838b3c-6.15.10-100.fc41.x86_64
boot_success=1
##############################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################%

The next listing displays the variables stored in the file as produced by grub2-editenv list.

brook on ultramarine-16ith6 ~ 
❯ sudo grub2-editenv list                                                                     
saved_entry=68f1ea76b293441d96a4288def838b3c-6.15.10-100.fc41.x86_64
boot_success=1

In the following listing, we see the contents of the .conf file, referred to by the saved_entry variable. It contains all of the elements contained in the /boot/grub2/grub.cfg file for a particular GRUB menu.

brook on ultramarine-16ith6 ~ 
❯ sudo cat /boot/loader/entries/68f1ea76b293441d96a4288def838b3c-6.15.10-100.fc41.x86_64.conf
title Ultramarine Linux (6.15.10-100.fc41.x86_64) 41 (Plasma Edition)
version 6.15.10-100.fc41.x86_64
linux /@-0/boot/vmlinuz-6.15.10-100.fc41.x86_64
initrd /@-0/boot/initramfs-6.15.10-100.fc41.x86_64.img $tuned_initrd
options root=UUID=4e628d85-695c-42c3-a582-8cb3ae370df7 ro rootflags=subvol=@-0 resume=UUID=980ddbbf-9b79-4390-be21-850e62b7ebb2 rhgb quiet $tuned_params rd.driver.blacklist=nouveau,nova_core modprobe.blacklist=nouveau,nova_core
grub_users $grub_users
grub_arg --unrestricted
grub_class ultramarine

Now for the reason that may necessitate the deletion of the environment block contained in /boot/grub2/grubenv and the related files in /boot/loader/entries: the environment may contain a state that may interfere with a desired GRUB configuration, for example an existing saved state may interfere with a desired GRUB configuration, after modification of /etc/default/grub, installation of grub-btrfs, or any change to the system which affects GRUB, for example a Snapper rollback which requires use of a new snapshot subvolume as the system root.

The process to delete the existing GRUB environment -- which will be recreated with updated values after the next update of the GRUB menu configuration with grub2-mkconfig -- is as follows.

  1. First verify the existence of the environment block file in its default location on a Fedora system with:
    sudo ls -la /boot/grub2/grubenv
    The command with its output:
    brook on ultramarine-16ith6 ~ 
    ❯  sudo ls -la /boot/grub2/grubenv
    -rw-------. 1 root root 1024 Aug 27 17:08 /boot/grub2/grubenv
    
  2. List the variables stored in the file with:
    sudo grub2-editenv list
    The command with its output:
    brook on ultramarine-16ith6 ~ 
    ❯ sudo grub2-editenv list                     
    saved_entry=68f1ea76b293441d96a4288def838b3c-6.15.10-100.fc41.x86_64
    boot_success=1
    
  3. Delete each variable with:
    grub2-editenv /boot/grub2/grubenv unset <&variable-name>&
    The command as used to delete the two variables shown above with the output of each invocation:
    brook on ultramarine-16ith6 ~ 
    ❯ sudo grub2-editenv /boot/grub2/grubenv unset saved_entry
    
    brook on ultramarine-16ith6 ~ 
    ❯ sudo grub2-editenv /boot/grub2/grubenv unset boot_success
    
    
  4. Verify that the environment block is now empty :
    sudo grub2-editenv list
    The command has no output confirming that the variables have been deleted:
    brook on ultramarine-16ith6 ~ 
    ❯ sudo grub2-editenv list                                  
    
    brook on ultramarine-16ith6 ~ 
    ❯ 
  5. Finally, delete the entries by removing their containing directory with:
    sudo rm -r /boot/loader/entries
    The entries will be recreated and associated with new environment block contents when the GRUB menu configuration is updated with grub2-mkconfig, which we will do after installing grub-btrfs.

Install and Configure grub-btrfs

At the time of my previous Fedora installation (Ultramarine 39) on Btrfs, a current version of grub-btrfs was not available as a package, either from Fedora's official repositores, RPMFusion, or the Fedora Copr. Fortunately, now a Copr package is available for Fedora 40 - 43 and Fedora Rawhide. We will use the Copr repository kylegospo/grub-btrfs.

  1. sudo dnf copr enable kylegospo/grub-btrfs
    The command with the output:
    brook on ultramarine-16ith6 ~
    ❯ sudo dnf copr enable kylegospo/grub-btrfs
    [sudo] password for brook:
     https://copr.fedorainfracloud.org/api_3/rpmrepo/kylegospo/grub-btrfs/fedora-41/              100% |   1.1 KiB/s | 411.0   B |  00m00s
    Enabling a Copr repository. Please note that this repository is not part
    of the main distribution, and quality may vary.
    
    The Fedora Project does not exercise any power over the contents of
    this repository beyond the rules outlined in the Copr FAQ at
    ,
    and packages are not held to any quality or security level.
    
    Please do not file bug reports about these packages in Fedora
    Bugzilla. In case of problems, contact the owner of this repository.
    Is this ok [Y/n]:
  2. Install grub-btrfs with:
    sudo dnf install grub-btrfs
    The command with the output:
    brook on ultramarine-16ith6 ~ took 9s
    ❯ sudo dnf install grub-btrfs
    Updating and loading repositories:
     Copr repo for grub-btrfs owned by kylegospo                                                  100% |   6.0 KiB/s |   2.7 KiB |  00m00s
    Repositories loaded.
    Package                                 Arch   Version                                 Repository                                 Size
    Installing:
     grub-btrfs                             noarch 0.0.git.275.8c61d8ef-1.fc38             copr:copr.fedorainfracloud.org:kylego  74.5 KiB
    
    Transaction Summary:
     Installing:         1 package
    
    Total size of inbound packages is 31 KiB. Need to download 31 KiB.
    After this operation, 75 KiB extra will be used (install 75 KiB, remove 0 B).
    Is this ok [Y/n]:
    [1/1] grub-btrfs-0:0.0.git.275.8c61d8ef-1.fc38.noarch                                         100% | 153.6 KiB/s |  31.5 KiB |  00m00s
    --------------------------------------------------------------------------------------------------------------------------------------
    [1/1] Total                                                                                   100% | 152.1 KiB/s |  31.5 KiB |  00m00s
    [1/2] https://download.copr.fedorainfracloud.org/results/kylegospo/grub- ???% [<=>               ] |   0.0   B/s |   0.0   B |  00m00s
    [1/2] https://download.copr.fedorainfracloud.org/results/kylegospo/grub- ???% [<=>               ] |   0.0   B/s |   0.0   B |  00m00s
    [1/2] https://download.copr.fedorainfracloud.org/results/kylegospo/grub-btrfs/pubkey.gpg      100% |   5.9 KiB/s |   1.0 KiB |  00m00s
    --------------------------------------------------------------------------------------------------------------------------------------
    [2/2] Total                                                                                   100% | 152.1 KiB/s |  31.5 KiB |  00m00s
    Importing OpenPGP key 0x22459A4E:
     UserID     : "kylegospo_grub-btrfs (None) "
     Fingerprint: 6B9061FFDF81E03E76BF009923770C5722459A4E
     From       : https://download.copr.fedorainfracloud.org/results/kylegospo/grub-btrfs/pubkey.gpg
    Is this ok [Y/n]:
    The key was successfully imported.
    [1/3] Verify package files                                                                    100% | 250.0   B/s |   1.0   B |  00m00s
    [2/3] Prepare transaction                                                                     100% |   2.0   B/s |   1.0   B |  00m00s
    [3/3] Installing grub-btrfs-0:0.0.git.275.8c61d8ef-1.fc38.noarch                              100% |  46.8 KiB/s |  76.0 KiB |  00m02s
    Complete!
    
    
  3. Enable and start the grub-btrfs.path systemd unit.
    sudo systemctl enable --now grub-btrfs.path
    The command with the output:
    brook on ultramarine-16ith6 ~ took 20s
    ❯ sudo systemctl enable --now grub-btrfs.path
    Created symlink '/etc/systemd/system/\x2esnapshots.mount.wants/grub-btrfs.path' → '/usr/lib/systemd/system/grub-btrfs.path'.
    
At this point whenever the GRUB configuration file, /boot/grub2/grub.cfg is updated with grub2-mkconfig by invoking it directly or when it is invoked as part of a DNF update, the existing snapshots are added to the GRUB configuration and made available in the GRUB menu so they can be selected for booting.

The following image shows the systemd status of grub-btrfs.path as well as the output of grub2-mkconfig -o /boot/grub2/grub.cfg immediately after the above steps.

Enabling grub-btrfs.path and the Output of grub2-mkconfig which Now Adds Snapshots to the GRUBMenu

Modify the Snapper Configuration

The default Snapper configuration file for the subvolume mounted at / written automatically by Snapper when we issued the snapper create command immediately after installing Snapper at /etc/snapper/configs/root specifies -- possibly -- an excessive number of automatic snapshots to be created. This can be a problem if the size of the Btrfs partition is not appropriately large.

Snapper allows automatic snapshots to be created periodically, at boot, and during package management transactions. Periodic snapshots can be created hourly, daily, monthly and yearly. It specifies up to 10 hourly snapshots, up to 10 daily snapshots, up to 10 monthly snapshots and up to 10 yearly snapshots to be retained. It specifies that the automatic cleanup of snapshots retain an overall number of 50 snapshots, whether they are periodic snapshots or those created during package management transactions. These settings are excessive for the 72 GB Btrfs partition for the filesystem root of this installation.

So, we will modify the settings to limit the number of snapshots that are created and decrease the number of snapshots that are preserved by the timeline cleanup algorithm, by editing the values of configuration parameters TIMELINE_LIMIT_HOURLY, TIMELINE_LIMIT_DAILY, TIMELINE_LIMIT_WEEKLY, TIMELINE_LIMIT_MONTHLY, and TIMELINE_LIMIT_YEARLY to appropriate values. The modified values of these parameters are shown in the following listing of /etc/snapper/configs/root. We will also modify the settings related to the number cleanup algorithm by reducing the values of the parameters NUMBER_LIMIT and NUMBER_LIMIT_IMPORTANT


# subvolume to snapshot
SUBVOLUME="/"

# filesystem type
FSTYPE="btrfs"


# btrfs qgroup for space aware cleanup algorithms
QGROUP=""


# fraction or absolute size of the filesystems space the snapshots may use
SPACE_LIMIT="0.5"

# fraction or absolute size of the filesystems space that should be free
FREE_LIMIT="0.2"


# users and groups allowed to work with config
ALLOW_USERS=""
ALLOW_GROUPS=""

# sync users and groups from ALLOW_USERS and ALLOW_GROUPS to .snapshots
# directory
SYNC_ACL="no"


# start comparing pre- and post-snapshot in background after creating
# post-snapshot
BACKGROUND_COMPARISON="yes"


# run daily number cleanup
NUMBER_CLEANUP="yes"

# limit for number cleanup
NUMBER_MIN_AGE="3600"
NUMBER_LIMIT="25"
NUMBER_LIMIT_IMPORTANT="5"


# create hourly snapshots
TIMELINE_CREATE="yes"

# cleanup hourly snapshots after some time
TIMELINE_CLEANUP="yes"

# limits for timeline cleanup
TIMELINE_MIN_AGE="3600"
TIMELINE_LIMIT_HOURLY="2"
TIMELINE_LIMIT_DAILY="2"
TIMELINE_LIMIT_WEEKLY="1"
TIMELINE_LIMIT_MONTHLY="1"
TIMELINE_LIMIT_QUARTERLY="1"
TIMELINE_LIMIT_YEARLY="0"


# cleanup empty pre-post-pairs
EMPTY_PRE_POST_CLEANUP="yes"

# limits for empty pre-post-pair cleanup
EMPTY_PRE_POST_MIN_AGE="3600"

Enable the Various systemd Timers for Automatic Snapshot Creation and Cleanup

We will ensure that the Snapper helper systemd timers snapper-boot.timer, snapper-timeline.timer, and snapper-cleanup.timer which, respectively, create a snapshot automatically at boot, create hourly, daily, weekly, and monthly periodic snapshots, and delete old snapshots -- keeping the specified number of each type of periodic snapshot and the total number, as specified in other configuration parameters are enabled. These timers activate corresponding systemd services which in turn cause the actual commands that perform the actions are executed.

The following listing shows that the timeline timer has already been enabled by previous actions, but the boot and cleanup timers have not.

brook on ultramarine-16ith6 ~ 
❯ sudo systemctl status snapper-{boot,timeline,cleanup}.timer
○ snapper-boot.timer - Take snapper snapshot of root on boot
     Loaded: loaded (/usr/lib/systemd/system/snapper-boot.timer; disabled; preset: disabled)
     Active: inactive (dead)
    Trigger: n/a
   Triggers: ● snapper-boot.service

● snapper-timeline.timer - Timeline of Snapper Snapshots
     Loaded: loaded (/usr/lib/systemd/system/snapper-timeline.timer; enabled; preset: disabled)
     Active: active (waiting) since Thu 2025-08-28 14:31:28 EDT; 1h 51min ago
 Invocation: 55af267313af4dbe8b47c6654a29e60a
    Trigger: Thu 2025-08-28 17:00:00 EDT; 37min left
   Triggers: ● snapper-timeline.service
       Docs: man:snapper(8)
             man:snapper-configs(5)

Aug 28 14:31:28 ultramarine-16ith6 systemd[1]: Started snapper-timeline.timer - Timeline of Snapper Snapshots.

○ snapper-cleanup.timer - Daily Cleanup of Snapper Snapshots
     Loaded: loaded (/usr/lib/systemd/system/snapper-cleanup.timer; disabled; preset: disabled)
     Active: inactive (dead)
    Trigger: n/a
   Triggers: ● snapper-cleanup.service
       Docs: man:snapper(8)
             man:snapper-configs(5)

We will enable both of these timers and start them with the command:

systemctl enable --now snapper-{boot,cleanup}.timer
The command with the output:
brook on ultramarine-16ith6 ~ 
❯ sudo systemctl enable --now snapper-{boot,cleanup}.timer     
[sudo] password for brook: 
Created symlink '/etc/systemd/system/timers.target.wants/snapper-boot.timer' → '/usr/lib/systemd/system/snapper-boot.timer'.
Created symlink '/etc/systemd/system/timers.target.wants/snapper-cleanup.timer' → '/usr/lib/systemd/system/snapper-cleanup.timer'.

The btrfsmaintenance provides several systemd timers -- btrfs-balance.timer, btrfs-scrub.timer, btrfs-defrag.timer -- which activate corresponding services, which in turn execute scripts, which themselves invoke btrfs provided commands to perform Btrfs balance, scrub, and defrag operations on the filesystem. We install the package and enable the timers as follows.

Install the package with:

dnf install btrfsmaintenance
The command with the output:
brook on ultramarine-16ith6 ~ took 2s 
❯ sudo dnf install btrfsmaintenance
Updating and loading repositories:
Repositories loaded.
Package                                    Arch        Version                                    Repository                 Size
Installing:
 btrfsmaintenance                          noarch      0.5.2-2.fc41                               fedora                 56.9 KiB

Transaction Summary:
 Installing:         1 package

Total size of inbound packages is 31 KiB. Need to download 31 KiB.
After this operation, 57 KiB extra will be used (install 57 KiB, remove 0 B).
Is this ok [Y/n]: 
[1/1] btrfsmaintenance-0:0.5.2-2.fc41.noarch                                             100% |  90.3 KiB/s |  31.4 KiB |  00m00s
---------------------------------------------------------------------------------------------------------------------------------
[1/1] Total                                                                              100% |  50.3 KiB/s |  31.4 KiB |  00m01s
Running transaction
[1/3] Verify package files                                                               100% | 250.0   B/s |   1.0   B |  00m00s
[2/3] Prepare transaction                                                                100% |   1.0   B/s |   1.0   B |  00m01s
[3/3] Installing btrfsmaintenance-0:0.5.2-2.fc41.noarch                                  100% |  30.0 KiB/s |  60.6 KiB |  00m02s
Complete!

Enable the related services with the command:

systemctl enable --now btrfs-{balance,scrub,defrag}.timer
The command with the output:
brook on ultramarine-16ith6 ~ 
❯ sudo systemctl enable --now btrfs-{balance,scrub,defrag}.timer
Created symlink '/etc/systemd/system/timers.target.wants/btrfs-balance.timer' → '/usr/lib/systemd/system/btrfs-balance.timer'.
Created symlink '/etc/systemd/system/timers.target.wants/btrfs-scrub.timer' → '/usr/lib/systemd/system/btrfs-scrub.timer'.
Created symlink '/etc/systemd/system/timers.target.wants/btrfs-defrag.timer' → '/usr/lib/systemd/system/btrfs-defrag.timer'.

Conclusion

The Ultramarine installation emulating the openSUSE style of a four-level hierarchical Btrfs subvolume layout is rational, in the sense that it encapsulates subvolumes in other subvolumes by purpose and places the initial system in the same containing subvolume as future, after rollback system roots. However, the process -- which could be simplified by using a Kickstart file for the installation -- is time consuming and complicated. The alternative presented here of using Anaconda's Blivet-GUI partitioning component to create a simpler subvolume layout, and allowing Snapper to create its own subvolume during its configuration, results -- in practice -- in a system with the same functionality.

References

  1. A Fedora Installation with an openSUSE Style Btrfs Subvolume Layout and Snapper Integration for System Snapshots and Rollbacks
  2. Siduction Linux 2024.1.0 (Shine On) Review [KDE Plasma Edition]
  3. Fedora Discussion Post: Getting snapper/btrfs-assistant to work with dnf5
  4. GRUB Manual; Section 15.2: The GRUB environment block