Skip to content

Instantly share code, notes, and snippets.

@qiwichupa
Last active July 8, 2024 13:40
Show Gist options
  • Save qiwichupa/0c7b32052c91c5ac1f5958a2e7abf0bc to your computer and use it in GitHub Desktop.
Save qiwichupa/0c7b32052c91c5ac1f5958a2e7abf0bc to your computer and use it in GitHub Desktop.
btrfs_snapshot.sh - create snapshot for btrfs mountpoints/volumes (and send to another btrfs backup directory)
#!/bin/bash
# v. 2023.06.29
MOUNTPOINTS=('/home' '/')
SNAPSHOTDIRNAME='.snapshots'
# snapshots in SNAPSHOTDIRNAME
RETAINSNAPS=2
# send to BACKUPDIR: yes or no
SENDTOBACKUP=no
# optional, set SENDTOBACKUP=yes
BACKUPDIR='/mnt/backup'
# snapshots in BACKUPDIR (if SENDTOBACKUP=yes)
RETAINBACKS=7
function is_btrfs {
local FS=$(stat -f --format=%T "$1")
[ "$FS" = "btrfs" ] && return 0
return 1
}
function is_btrfs_volume {
findmnt -rn "$1" | grep "subvol=" > /dev/null 2>&1 && return 1 # return false if path is mountpoint
btrfs subvolume show "$1" > /dev/null 2>&1 && return 0
return 1
}
function is_btrfs_mountpoint {
findmnt -rn "$1" | grep "subvol=" > /dev/null 2>&1 && return 0
return 1
}
function create_snapshot {
local mountpoint=$1
local snapdir="$SNAPSHOTDIRNAME"
local date=$(date +%Y%m%d-%H%M%S)
local snaproot
local vol
local isbtrfsvolume
local snapcount
local lastvol
local allsnapsarray
local snapstoremovecount
if [[ "$SENDTOBACKUP" == "yes" ]]; then
if ! is_btrfs "$BACKUPDIR" ; then
echo \"$BACKUPDIR\" is not a btrfs!! Exit.
exit 1
fi
fi
if [[ "$mountpoint" != "/" ]]; then mountpoint=${mountpoint%/}; fi; # remove trainling slash for any dir except root (/)
# find btrfs volume name for path
if is_btrfs_volume "$mountpoint"; then
vol=$mountpoint
echo \"$mountpoint\" is volume
elif is_btrfs_mountpoint "$mountpoint"; then
vol=$(findmnt -rn "$mountpoint" | awk -F "subvol=" 'NR==1 {print $2}')
echo \"$mountpoint\" is mountpoint
else
echo \"$mountpoint\" is not a btrfs mountpoint or volume!! Exit.
exit 1
fi
if [[ "$vol" == "/" ]]; then
vol='@btrfsroot'
else
vol="${vol:1}" # remove lead slash
vol="${vol//'/'/__}" # replace slashes by __
fi
echo "mp: $mountpoint"
echo "vol: $vol"
snaproot="${mountpoint}/${snapdir}"
mkdir -p "$snaproot" > /dev/null 2>&1
btrfs sub snap -r "${mountpoint}" "${snaproot}/@snp_${vol}_ro_${date}" || exit 1
if [[ "$SENDTOBACKUP" == "yes" ]]; then
snapcount=$(ls "$snaproot" | grep "@snp_${vol}_ro" | wc -l)
if [[ "$snapcount" == "1" ]]; then
btrfs send "${snaproot}/@snp_${vol}_ro_${date}" | btrfs receive "$BACKUPDIR"
else
lastvol=$(ls "$snaproot" | grep "@snp_${vol}_ro" | tail -2 | head -n -1)
btrfs send -p "${snaproot}/${lastvol}" "${snaproot}/@snp_${vol}_ro_${date}" | btrfs receive "$BACKUPDIR"
fi
if [ "$RETAINBACKS" -gt "0" ]; then
allbacksarray=($(ls "$BACKUPDIR" | grep "@snp_${vol}_ro"))
if [ "$RETAINBACKS" -lt "${#allbacksarray[@]}" ]; then
backstoremovecount=$(( ${#allbacksarray[@]} - $RETAINBACKS ))
for ((i=0;i<$backstoremovecount;i++)); do
btrfs subvolume delete "${BACKUPDIR}/${allbacksarray[$i]}"
done
fi
fi
fi
if [ "$RETAINSNAPS" -gt "0" ]; then
allsnapsarray=($(ls "$snaproot" | grep "@snp_${vol}_ro"))
if [ "$RETAINSNAPS" -lt "${#allsnapsarray[@]}" ]; then
snapstoremovecount=$(( ${#allsnapsarray[@]} - $RETAINSNAPS ))
for ((i=0;i<$snapstoremovecount;i++)); do
btrfs subvolume delete "${snaproot}/${allsnapsarray[$i]}"
done
fi
fi
echo ""
}
for mp in ${MOUNTPOINTS[*]}; do
create_snapshot "$mp"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment