#!/bin/bash -e # defaults HOURLY_DEL=25 HOURLY_FAST=1 DAILY_DEL=8 DAILY_FAST=0 WEEKLY_DEL=5 WEEKLY_FAST=1 MONTHLY_DEL= MONTHLY_FAST=1 FAST=0 REBALANCE=0 BTRFS_VOLUMES=${BTRFS_VOLUMES:-$(awk '!/^#/ && $3=="btrfs" {print $2}' /etc/fstab | tr '\n' ' ' | sed 's, $,,')} TMP_MNT=${TMP_MNT:-/var/tmp/btrfs-backup} # overwrite defaults in configs if test -e /etc/btrfs-snapshots.conf; then . /etc/btrfs-snapshots.conf fi if test -e ~/.btrfs-snapshots; then . ~/.btrfs-snapshots fi # evaluate commandline vols=() del=${DEL} dryrun=0 fast=${FAST} rebalance=${REBALANCE} periodity=${0%/*} periodity=${periodity##*/} if [[ $periodity =~ ^cron\... ]]; then periodity=${periodity#cron} else periodity= fi case "$periodity" in (.hourly) del=${HOURLY_DEL}; fast=${HOURLY_FAST};; (.daily) del=${DAILY_DEL}; fast=${DAILY_FAST};; (.weekly) del=${WEEKLY_DEL}; fast=${WEEKLY_FAST};; (.monthly) del=${MONTHLY_DEL}; fast=${MONTHLY_FAST};; esac while test $# -gt 0; do case "$1" in (-h|--help) cat < add a path that contains a btrfs (sub-) volume (defaults to: ${BTRFS_VOLUMES}) -m, --mnt temporary mount point (default: ${TMP_MNT}) not yet implemented: -d, --del delete oldes snapshots beginning at -th ENVIRONMENT BTRFS_VOLUMES specify all volume pathes (instead of -p) TMP_MNT temporary mount point (instead of -m) DESCRIPTION Creates a snapshot for all btrfs volumes specified. Snapshot is named from the subvol name or if there is no subvol, from the path by appending -snapshot-YYYY-MM-DD-HH-mm. To create regular snapshots on a daily base, just run: sudo cp btrfs-snapshots.sh /etc/cron.daily/btrfs-snapshots If btrfs-snapshots is run from a cron.daily, cron.hourly, cron. monthly or cron.weekly directory, the periodity is automatically appended to the snapshot name, and the expiry is set meaningfull: - hourly → keep only 24 hours, --del 25 - daily → keep only 7 days, --del 8 - weekly → keep only 4 weeks, --del 5 - monthly → old snapshots are not deleted EOF exit;; (-n|--dry-run) dryrun=1;; (-f|--fast) fast=1;; (-r|--rebalance) rebalance=1;; (-p|--path) shift; vols+=( "$1" );; (-m|--mnt) shift; TMP_MNT="$1";; (-d|--del) shift; del="$1";; (*) echo "ERROR: unknown option $1, try $0 --help" 1>&2 exit 1;; esac if test $# -lt 1; then echo "ERROR: missing argument, try $0 --help" 1>&2 exit 1 fi shift done if test -n "${vols[*]}"; then BTRFS_VOLUMES="${vols[*]}" fi test -d "${TMP_MNT}" || mkdir -p "${TMP_MNT}" for fs in ${BTRFS_VOLUMES}; do device=$(mount | awk '$3=="'$fs'" && $5=="btrfs" {print $1}') subvol=$(mount | awk '$3=="'$fs'" && $5=="btrfs" {split(substr($6, 2, length($6)-2), res, ","); for (r in res) {if (res[r]~/^subvol=/) {sub(/^subvol=/,"" , res[r]); print res[r]}}}') if test -n "$subvol"; then if test "$subvol" = "/@"; then name=${subvol} else name=${subvol}- fi else if test "$fs" = "/"; then name="/" else name=${fs#/} name=${name%/} name=/${name//\//-}- fi fi date="-$(date +%Y-%m-%d-%H-%M)" target="${name}snapshot${periodity}" if test -n "$device"; then if mount | grep -q " on $TMP_MNT type "; then sudo umount "$TMP_MNT" fi sudo mount "$device" "$TMP_MNT" if test $dryrun -eq 1; then echo -e "→ \e[1mbackup ${subvol:+subvol $subvol of }$fs on $device\e[0m" echo " " mount "$device" "$TMP_MNT" echo " " btrfs subvolume snapshot "${TMP_MNT}${subvol}" "${TMP_MNT}${target}${date}" if test -n "$del"; then for f in $(ls -d1 "${TMP_MNT}${target}"-* | sort -r | tail -n +"$del"); do echo " " btrfs subvolume delete "$f" done fi echo " " umount "$TMP_MNT" if test $fast -eq 0; then echo " " btrfs filesystem defragment ${fs} if test $rebalance -eq 1; then echo " " btrfs balance start ${fs} fi fi else sudo btrfs subvolume snapshot "${TMP_MNT}${subvol}" "${TMP_MNT}${target}${date}" if test -n "$del"; then for f in $(ls -d1 "${TMP_MNT}${target}"-* | sort -r | tail -n +"$del"); do sudo btrfs subvolume delete "$f" done fi fi sudo umount "$TMP_MNT" if test $fast -eq 0; then echo "Defragment subvolume: '${fs}'" sudo btrfs filesystem defragment "${fs}" if test $rebalance -eq 1; then echo "Rebalance subvolume: '${fs}'" sudo btrfs balance start "${fs}" fi fi else echo "ERROR: no device found for $fs" 1>&2 exit 2 fi done