BTRFS Snapshot
BTRFS Snapshot is a simple set of scripts and systemd units for taking and managing BTRFS snapshots. There actual project like Snapper and yabsnap, but this one does exactly what I need reliably and predictably with no frills, GUIs, etc.
Installation
You can install this project with a standard make & make install invocation:
make
sudo make install
You should then enable the various units. E.g., to backup your home directory on a schedule, you can run:
systemctl enable --user --now "$(systemd-escape "$HOME" --template btrfs-snapshot@.timer)"
systemctl enable --user --now "$(systemd-escape "$HOME" --template btrfs-snapshot-cleanup@.timer)"
(assuming your home directory is on its own BTRFS subvolume)
Interval
Snapshots are taken to $SUBVOL/.backups
every hour by the btrfs-snapshot@.timer
unit. The interval can be configured by modifying/replacing the timer unit.
Snapshot Cleaning
Snapshots are cleaned when they’re taken:
- All files specified by
$SUBVOL/.backupignore
are removed. This file lists files rooted at$SUBVOL
with no patterns or globbing for now. - All directories containing a
CACHEDIR.TAG
file are deleted. - All git repositories are cleared of ignored files/directories. I.e.,
git clean -Xd
is run in every git repository.
Snapshot Pruning
If the snapshot cleanup unit (btrfs-snapshot-cleanup@.timer
) is enabled, snapshots will be pruned once a day as follows:
- All snapshots from the last 24 hours are kept.
- One snapshot is kept for each day of the last month.
- One snapshot is kept per week of the last year.
- One snapshot is kept per month forever.
Emacs Integration
If you want to explore a file’s history, I recommend the snapshot-timeline package (available on MELPA). Configure it with the following backend and it should “just work” (automatically locating the most “relevant” .backups
directory.
(defun snapshot-backend-btrfs (filename)
(when-let* ((base (expand-file-name (locate-dominating-file filename ".backups/")))
(rfname (string-remove-prefix base filename))
(bdir (concat base ".backups/"))
(files (directory-files bdir nil (rx bos (not ?.))))
(count 0))
(seq-keep
(lambda (d)
(when-let* ((bname (concat bdir d "/" rfname))
((file-exists-p bname))
(date (ignore-errors
(time-convert (encode-time
(parse-time-string d))
'list))))
(incf count)
(make-snapshot :id count :name (number-to-string count) :file bname :date date)))
files)))
(setopt snapshot-timemachine-snapshot-finder #'snapshot-backend-btrfs)