In Btrfs Snapshot Management With Snapper, the process is described for automating Btrfs snapshots with Snapper. Why stop there when you can use these snapshots for incremental backups? This isn’t the most difficult thing to script yourself, but why do more work than you have to? The Bash script snap-sync makes it easy to incrementally back up Snapper snapshots to another hard drive or a remote machine.

Btrbk already has this capability built-in but Snapper does not, though this may come some day in the future given Snapper issue #368 - Incremental backups. If you don’t need Snapper and want incremental backups of Btrfs snapshots, checkout Btrbk!

Tutorial

Bundled here is a lovely tutorial on how to use snap-sync to backup your Snapper snapshots to an external hard drive. Building on previous blog posts, this tutorial will demonstrate how to do this at the system level.

It’s assumed that you have a Snapper configuration for the system’s root filesystem. If you haven’t set this up, refer to Btrfs Snapshot Management With Snapper. You will need a Cryptsetup encrypted volume on an external hard drive. Backing up to an encrypted volume protects your backups in case something happens to your external drive. You can setup an encrypted volume on an external hard drive by following the steps in the post Encrypt an External Disk on Linux. Furthermore, this post builds on the work in Automount an Encrypted System Volume, which automates the process of unlocking and mounting the encrypted volume. For the purposes of this tutorial, we assume the configuration from these previous blog posts. The encrypted volume on the external hard drive is /dev/sdb1. The system is configured to automatically unlock the encrypted volume and mount its Btrfs subvolume root_backups at /run/media/system/System_Backups. It’s also recommended to mount the Btrfs filesystem with zstd compression and automatic defragmentation. See Btrfs Mount Options and Adjust Mount Options for details on the subject.

The reference system is elementary OS 5.1 based on Ubuntu 18.04. For this tutorial, you should understand the command-line on Linux, Btrfs, filesystems, Cryptsetup, systemd, and Snapper. As a matter of preference, the commands here use the fish shell's syntax.

snap-sync doesn’t handle cleanup yet so you’ll have to prune back snapshots on the backup drive yourself for now, or use a more robust tool like Btrbk.

  1. Download the latest snap-sync release.

    wget -q -nv -O - https://api.github.com/repos/wesbarnett/snap-sync/releases/latest \
      | awk -F': ' '/browser_download_url/ && /snap-sync-[0-9]\.[0-9]\.tar\.gz/ \
      {gsub(/"/, "", $(NF)); system("wget -qLP ~/Downloads/ " $(NF))}'
  2. Verify the tarball’s signature.

    The command here does this with the added convenience of importing the signing key for you if you don’t already have it.

    gpg --verify --auto-key-retrieve --keyserver keyserver.ubuntu.com ~/Downloads/snap-sync-*.tar.gz.sig
    gpg: assuming signed data in '/home/jordan/Downloads/snap-sync-0.7.tar.gz'
    gpg: Signature made Fri 29 Jan 2021 09:19:24 PM CST
    gpg:                using EDDSA key F7B28C61944FE30DABEEB0B01070BCC98C18BD66
    gpg: requesting key 1070BCC98C18BD66 from hkp server keyserver.ubuntu.com
    gpg: key 1070BCC98C18BD66: 1 signature not checked due to a missing key
    gpg: key 1070BCC98C18BD66: 3 signatures reordered
    gpg: key 1070BCC98C18BD66: public key "Wes Barnett <wes@barnett.science>" imported
    gpg: marginals needed: 3  completes needed: 1  trust model: pgp
    gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
    gpg: Total number processed: 1
    gpg:               imported: 1
    gpg: Good signature from "Wes Barnett <wes@barnett.science>" [unknown]
    gpg:                 aka "Wes Barnett <wes@wbarnett.us>" [unknown]
    gpg: WARNING: This key is not certified with a trusted signature!
    gpg:          There is no indication that the signature belongs to the owner.
    Primary key fingerprint: F7B2 8C61 944F E30D ABEE  B0B0 1070 BCC9 8C18 BD66

    Good signature in the output signifies that the signature is indeed valid which indicates that the tarball has not been unduly tampered with. Assuming, of course, that no unauthorized persons have managed to get their hands on the private signing key.

  3. Unpack the tarball.

    tar -C ~/Downloads -xvf ~/Downloads/snap-sync-*.tar.gz
  4. Install snap-sync.

    sudo make -C ~/Downloads/snap-sync install
  5. Remove the snap-sync files since they are no longer needed.

    rm -rf ~/Downloads/snap-sync*
  6. Plug in the external hard drive.

  7. Trigger systemd to unlock and mount the encrypted volume.

    sudo systemctl start run-media-system-System_Backups.mount

    This systemd mount unit comes from the fstab(5) entry added in Automount an Encrypted System Volume.

  8. Sync the initial snapshot to the external hard drive.

    snap-sync is run manually here because it needs to be configured on the first run of any combination of Snapper config and backup location. Subsequent runs using the same configuration and destination won’t require user input, allowing snap-sync to be run as a background service. The initial sync operation copies the entire root subvolume as part of the first snapshot. After this, it will only send the data that has changed since the previous sync operation. Some scripting in the command below determines the UUID of the Btrfs filesystem and the id of the subvolume where backups will stored.

    sudo snap-sync -c root \
      --UUID $(sudo blkid -o value -s UUID /dev/mapper/backup_crypt) \
      --subvolid $(sudo btrfs subvolume show /run/media/system/System_Backups \
        | awk -F ":[ \t]*" '/Subvolume ID:/ {gsub(//,""); print $2}')
    
    snap-sync version 0.7, Copyright (C) 2016-2021 Wes Barnett
    snap-sync comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. See the license for more information.
    
    
    You selected the disk with uuid=2eb01d94-9aa1-4bd1-8c99-950be806f449, subvolid=257.
    The disk is mounted at '/run/media/system/System_Backups'.
    
    No backups have been performed for 'root' on this disk.
    Enter name of subvolume to store backups, relative to /run/media/system/System_Backups (to be created if not existing):
    This will be the initial backup for snapper configuration 'root' to this disk. This could take awhile.
    Creating new local snapshot for 'root' configuration...
    Will backup //.snapshots/204/snapshot to /run/media/system/System_Backups//root/204//snapshot
    Proceed with backup of 'root' configuration [Y/n]? y
    
    Performing backups...
    
    Sending first snapshot for 'root' configuration...
    At subvol /.snapshots/204/snapshot
    Tagging local snapshot as latest backup for 'root' configuration...
    
    Done!
  9. Configure a systemd.service to backup the root filesystem with snap-sync.

    /etc/systemd/system/snap-sync-root.service
    [Unit]
    Description=Backup Snapper snapshots of the root filesystem
    BindsTo=media-run-media-system-System_Backups.mount
    After=media-run-media-system-System_Backups.mount
    
    [Install]
    WantedBy=multi-user.target
    
    [Service]
    Type=simple
    ExecStart=/usr/bin/snap-sync -c root --UUID 2eb01d94-9aa1-4bd1-8c99-950be806f449 --subvolid 257 --noconfirm --quiet

    This is a fairly straight-forward service unit. Of note are the BindsTo and After keys, which tell systemd that this service should only run while /run/media/system/System_Backups is mounted. If you yank the drive’s connection, the service won’t keep running. The UUID and subvolid, taken from the output of the initial snap-sync run, are hard-coded here, and all notifications and prompts are disabled.

  10. Set up a systemd.timer to run the snap-sync backup service every hour.

    /etc/systemd/system/snap-sync-root.timer
    [Unit]
    Description=Backup Snapper snapshots of the root filesystem every hour
    
    [Timer]
    OnCalendar=hourly
    AccuracySec=15min
    Persistent=true
    
    [Install]
    WantedBy=timers.target

    This timer will run the snap-sync systemd service unit configured in the previous step. It runs every hour within a 15 minute margin according to the value of AccuracySec. This adds a bit of flexibility for how the timer is scheduled, which can reduce CPU wake-ups and save power. If the service can’t be run for any reason when the timer fires, the Persistent option ensures that the service will be run immediately when next possible. This comes in handy when backups can’t happen while the drive is unplugged or the computer is powered down. Once the drive is plugged in or the computer is booted, the latest snapshot is synced to the backup drive.

  11. Start the timer now and automatically at boot.

    sudo systemctl enable --now snap-sync-root.timer
    Created symlink /etc/systemd/system/timers.target.wants/snap-sync-root.timer → /etc/systemd/system/snap-sync-root.timer.
  12. Finally, feel free to check the status of the timer with systemctl status.

    sudo systemctl status snap-sync-root.timer

Conclusion

You should now have examples of everything you need to get up-and-running with automated Btrfs snapshots and backups to an encrypted, external hard drive. From here, it should be trivial to configure snapshots for more than just the root filesystem. If you want to configure backups over the network, that shouldn’t be too hard with snap-sync, either.