Manually taking snapshots with Btrfs is easy. Managing said snapshots is not. This is why several tools exist for the task. The most prominent are Btrbk, Snapper, and Timeshift. Each of these automate taking, naming, and cleaning up snapshots. Btrbk is highly configurable and flexible while also offering incremental backups. It doesn’t handle rollbacks, however. Timeshift is only designed for rolling back a system’s root subvolume and not arbitrary subvolumes. It is rather inflexible, but provides a fantastic graphical interface right out of the box and makes rollbacks quick and easy. Snapper is very configurable, makes rollbacks a breeze, has been around a while, and is quite popular. It’s a bit biased towards openSUSE, it being their tool and all, but packaged for all major Linux distributions nonetheless. My choice was mostly between Btrbk and Snapper. Timeshift won’t snapshot all of the subvolumes I have. I landed on Snapper because it allows users to control their own snapshots and rollbacks without superuser privileges. Btrbk’s additional ability for managing backups makes it a very tempting alternative.

Tutorial

This tutorial contains the necessary steps to setup a recent version of Snapper to take automatic snapshots of a system’s root directory and a user’s home directory using Btrfs. The reference system is running elementary OS 5.1 and uses a {flat-btrfs-layout} for the system’s subvolumes. The layout is discussed extensively in Btrfs Layout. For this tutorial, you should understand the command-line, Btrfs, and the filesystem layout used in Linux. As a matter of preference, the commands here use the fish shell's syntax.

Installation

The version of Snapper packaged by my distribution lags significantly behind the upstream version. Fortunately, Snapper makes newer versions readily available through its own PPA. To install Snapper this way, follow these instructions.

  1. Install the necessary package for easily adding PPA’s.

    sudo apt -y install software-properties-common
  2. Add the Snapper repository for Ubuntu 18.04, off which elementaryOS Hera 5.1 is based, to the system’s sources.

    echo 'deb http://download.opensuse.org/repositories/filesystems:/snapper/xUbuntu_18.04/ /' \
      | sudo tee /etc/apt/sources.list.d/filesystems:snapper.list
    deb http://download.opensuse.org/repositories/filesystems:/snapper/xUbuntu_18.04/ /
  3. Trust the repository’s GPG key.

    wget -qO - https://download.opensuse.org/repositories/filesystems:/snapper/xUbuntu_18.04/Release.key \
      | gpg --dearmor \
      | sudo tee /etc/apt/trusted.gpg.d/filesystems_snapper.gpg > /dev/null
  4. Refresh the package sources.

    sudo apt update
  5. Install Snapper.

    sudo apt -y install snapper
  6. To avoid potential slowdowns, exclude any .snapshots subvolumes in updatedb.conf(5) so that they aren’t indexed by mlocate(1).

    /etc/updatedb.conf
    PRUNENAMES = ".snapshots"

Snapshots Subvolume

For each subvolume you want to snapshot, create a separate subvolume within it to hold the snapshots. This prevents snaphots being included in snapshots. Snapper uses a .snapshots subvolume by convention. The steps below create a .snapshots subvolume in which to keep snapshots of the root subvolume. The subvolume is created according to a flat layout, so it exists at the top-level of the Btrfs volume and is mounted explicitly in fstab(5).

  1. Mount the volume containing the root subvolume to /mnt.

    sudo mount (df --output=source / | tail -n 1) /mnt
  2. Create a btrfs subvolume where the snapshots will be stored.

    Here the subvolume will be named snapshots. If you prefer to prefix the name with @, be my guest.

    sudo btrfs subvolume create /mnt/snapshots
  3. Add an entry in fstab to mount the snapshots subvolume.

    echo (df --output=source / | tail -n 1)" /.snapshots btrfs defaults,autodefrag,compress=zstd,commit=120,noatime,subvol=snapshots 0 0" \
      | tee -a /etc/fstab
  4. Mount the snapshots subvolume.

    sudo mount /.snapshots
  5. Unmount /mnt.

    sudo umount /mnt

Configure

Configuration of Snapper is done through a config file. One config file is used for each subvolume that Snapper will snapshot. Typically, a new Snapper configuration is generated with a single command given the name for the config and the path of the subvolume to snapshot. A configuration for the root subvolume, aptly named root, would be generated as shown here.

sudo snapper -c root create-config /

Due to some sort of misconfiguration or bug, the create-config subcommand fails. The instructions below create a config manually as a workaround.

  1. Create a root config file by copying the template file to the configs directory.

    cp /etc/snapper/config-templates/default /etc/snapper/configs/root
  2. Edit the config file to your liking.

    Below is an example of a root config which uses the timeline features of Snapper to create and cleanup snapshots. The entire configuration file is included but the TIMELINE_ variables are the most important. They enable automatically creating and removing snapshots and designate how many snapshots to retain for a particular period.

    /etc/snapper/configs/root
    # subvolume to snapshot
    SUBVOLUME="/"
    
    # filesystem type
    FSTYPE="btrfs"
    
    
    # btrfs qgroup for space aware cleanup algorithms
    QGROUP=""
    
    
    # fraction of the filesystems space the snapshots may use
    SPACE_LIMIT="0.5"
    
    # fraction 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="1800"
    NUMBER_LIMIT="50"
    NUMBER_LIMIT_IMPORTANT="10"
    
    
    # create hourly snapshots
    TIMELINE_CREATE="yes"
    
    # cleanup hourly snapshots after some time
    TIMELINE_CLEANUP="yes"
    
    # limits for timeline cleanup
    TIMELINE_MIN_AGE="1800"
    TIMELINE_LIMIT_HOURLY="24"
    TIMELINE_LIMIT_DAILY="10"
    TIMELINE_LIMIT_WEEKLY="3"
    TIMELINE_LIMIT_MONTHLY="0"
    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="1800"

    This configuration keeps one snapshot for each of the previous 24 hours, 10 days, and 3 weeks. I could retain snapshots for months and years, but for my desktop’s root filesystem this just isn’t unnecessary. Refer to Tuning Periodic Snapshotting from the Btrfs maintenance toolbox for good rules of thumb.

  3. Now that the config is ready, enable it by adding it to the list of Snapper configs in /etc/sysconfig/snapper.

    /etc/sysconfig/snapper
    SNAPPER_CONFIGS="root"

User Snapshots

One of the features of Snapper is that users can manage snapshots within their home directory. This introduces a nice separation of concerns and responsibilities. A PAM module is also provided which can take snapshots whenever a user logs in and subsequently logs out.

Snapshots Subvolume

In contrast to the previous configuration, the snapshots directory created for the user’s home directory follows a nested layout. This is much simpler.

Create a subvolume for snapshots in the user’s home directory.

sudo btrfs subvolume create ~/.snapshots

The ~/.snapshots subvolume must be owned by root otherwise Snapper will throw an error.

Configure

The steps outlined here configure snapshots for a user’s home directory. This configuration assumes that the user’s home directory resides on a dedicated subvolume.

  1. Create a Snapper configuration file for the user’s home directory.

    sudo cp /etc/snapper/config-templates/default /etc/snapper/configs/home_jordan
  2. Edit the template as necessary.

    There are two important distinctions from the root filesystem configuration. First, the ALLOW_USERS parameter includes the name of the user. This permits the user to work with the config. Second, SYNC_ACL is enabled, allowing the user to work with the snapshots in the ~.snapshots directory.

    /etc/snapper/configs/home_jordan
    # subvolume to snapshot
    SUBVOLUME="/home/jordan"
    
    # filesystem type
    FSTYPE="btrfs"
    
    
    # btrfs qgroup for space aware cleanup algorithms
    QGROUP=""
    
    
    # fraction of the filesystems space the snapshots may use
    SPACE_LIMIT="0.5"
    
    # fraction of the filesystems space that should be free
    FREE_LIMIT="0.2"
    
    
    # users and groups allowed to work with config
    ALLOW_USERS="jordan"
    ALLOW_GROUPS=""
    
    # sync users and groups from ALLOW_USERS and ALLOW_GROUPS to .snapshots
    # directory
    SYNC_ACL="yes"
    
    
    # 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="1800"
    NUMBER_LIMIT="50"
    NUMBER_LIMIT_IMPORTANT="10"
    
    
    # create hourly snapshots
    TIMELINE_CREATE="yes"
    
    # cleanup hourly snapshots after some time
    TIMELINE_CLEANUP="yes"
    
    # limits for timeline cleanup
    TIMELINE_MIN_AGE="1800"
    TIMELINE_LIMIT_HOURLY="48"
    TIMELINE_LIMIT_DAILY="14"
    TIMELINE_LIMIT_WEEKLY="8"
    TIMELINE_LIMIT_MONTHLY="12"
    TIMELINE_LIMIT_YEARLY="2"
    
    
    # cleanup empty pre-post-pairs
    EMPTY_PRE_POST_CLEANUP="yes"
    
    # limits for empty pre-post-pair cleanup
    EMPTY_PRE_POST_MIN_AGE="1800"

    This configuration keeps one snapshot for each of the previous 48 hours, 14 days, 8 weeks, 12 months, and 2 years. This is quite extensive, but for preserving critical data in a user’s home directory it’s sensible.

  3. Enable the home_jordan Snapper config by adding to the SNAPPER_CONFIGS list.

    /etc/sysconfig/snapper
    SNAPPER_CONFIGS="root home_jordan"

Snapper PAM

If you wish to take snapshots of a user’s home directory upon log in and log out, you’ll need to install the PAM module. The user’s home directory must be its own subvolume and must have an enabled Snapper config such as the one created previously. The steps necessary to install Snapper’s PAM module follow.

  1. Install Snapper’s PAM module.

    sudo apt -y install libpam-snapper
  2. Add the pam_snapper.so module to the PAM configuration for the session type.

    /etc/pam.d/common-session
    session    optional    pam_snapper.so ignoreroot cleanup=timeline

    You will almost certainly want to set a cleanup algorithm as done here otherwise old snapshots won’t be deleted automatically.

A Graphical Interface

Snapper doesn’t provide a graphical user interface, but that doesn’t mean there isn’t one. Checkout the snapper-gui project if you’d like one!

Conclusion

This post has given a run down of managing Btrfs snapshots with Snapper. You should now be able to create Snapper configurations at both the system and user levels. Now you’ll probably want to backup those snapshots in case there’s a catastrophic failure or some such. Stay tuned for an upcoming tutorial on configuring backups plus more posts on getting the most out of Snapper!