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.
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 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!
-
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
-
Install the Snapper DNF Plugin with:
sudo dnf install python3-dnf-plugin-snapper
-
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.
-
Create the file /etc/dnf/libdnf5-plugins/actions.d/snapper.actions
-
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.
-
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
-
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
-
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
-
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 ~
❯
-
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.
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.
-
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]:
-
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!
-
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'.
Install btrfsmaintenance and enable Related systemd Timers
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'.