From: James Bunton Date: Sun, 28 Jan 2024 21:21:31 +0000 (+1100) Subject: Merge ../bsnap X-Git-Url: https://code.delx.au/monosys/commitdiff_plain/6e208e45498acd104abde412aef3239ae12c651a?hp=486ca80fc03b54b74033cc34dd117ed0d86f2397 Merge ../bsnap --- diff --git a/.gitignore b/.gitignore index c2c027f..4083037 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -local \ No newline at end of file +local diff --git a/bsnap/README.md b/bsnap/README.md new file mode 100644 index 0000000..e4e7322 --- /dev/null +++ b/bsnap/README.md @@ -0,0 +1,25 @@ +# bsnap + +Manage snapshots for backups using LVM or btrfs. This is intended for use with [borg](https://www.borgbackup.org) or [rsync](https://rsync.samba.org). + +## Usage + +Running `bsnap on` will do the following: +- Create a new directory `/a` where the snapshots will be mounted. +- Take snapshots as configured in your `/etc/fstab`. +- Mount the snapshots into `/a`. + +At this point you can run something like [borg](https://www.borgbackup.org) or [rsync](https://rsync.samba.org) on `/a` to create your backup. Once that's completed you then run `bsnap off` to unmount, remove the snapshots and cleanup `/a`. + +You must configure your `/etc/fstab` by setting the second last column for each entry: +- `0` -- no snapshot will be taken and the filesystem will not be mounted into `/a` +- `1` -- no snapshot will be taken but the filesystem will be bind mounted into `/a` +- `2` -- a snapshot will be taken and the snapshot will be mounted into `/a` + +``` +# Snapshot the LVM root filesystem +/dev/mapper/vg-root / ext4 defaults 2 0 + +# Mount it into /a so it's included in the backup without snapshotting it +UUID="ABCD-1234" /boot vfat defaults 1 0 +``` diff --git a/bsnap/bsnap b/bsnap/bsnap new file mode 100755 index 0000000..cdfb2db --- /dev/null +++ b/bsnap/bsnap @@ -0,0 +1,124 @@ +#!/bin/bash + +set -eu + +function snap { + unsnap + + mkdir -p /a + + dispatch snap < /etc/fstab +} + +function unsnap { + tac /etc/fstab | dispatch unsnap + + if [ -d "/a" ]; then + rmdir /a + fi +} + +function dispatch { + local action="" snaptype="" + local dev="" mnt="" fstype="" opts="" dump="" + + while read -r dev mnt fstype opts dump pass; do + snaptype="$(get_snaptype "$fstype" "$dump")" + if [ -z "$snaptype" ]; then + continue + fi + action="${1}_${snaptype}" + echo "$action $mnt" + "$action" "$dev" "$mnt" "$opts" + done +} + +function get_snaptype { + local fstype="$1" dump="$2" + if [ "$dump" = "1" ]; then + echo bind + elif [ "$dump" = "2" ] && [ "$fstype" = "btrfs" ]; then + echo btrfs + elif [ "$dump" = "2" ]; then + echo lvm + fi +} + +function snap_bind { + local mnt="$2" + mount --bind "${mnt}" "/a${mnt}" +} + +function unsnap_bind { + local snapmnt="/a$2" + if mountpoint -q "$snapmnt"; then + umount "$snapmnt" + fi +} + +function snap_lvm { + local dev="$1" mnt="$2" + local lvname="" vgname="" + read -r lvname vgname _ < <(lvdisplay --noheadings -C "$dev") + echo "snapshot ${vgname}/${lvname}" + lvcreate -L1G --snapshot --name "${lvname}snap" "${vgname}/${lvname}" + mount -o ro "${dev}snap" "/a${mnt}" +} + +function unsnap_lvm { + local snapdev="${1}snap" + local snapmnt="/a$2" + if mountpoint -q "$snapmnt"; then + umount "$snapmnt" + fi + if [ -b "$(readlink -f "$snapdev")" ]; then + lvremove -f "${dev}snap" + fi +} + +function snap_btrfs { + local mnt="$2" opts="$3" snapdir="" + + snapdir="$(get_btrfs_snapshot_dir "$opts" "$mnt")" + btrfs subvolume snapshot "$mnt" "$snapdir" + + mount --bind "$snapdir" "/a${mnt}" +} + +function unsnap_btrfs { + local mnt="$2" opts="$3" snapdir="" + + local snapmnt="/a$2" + if mountpoint -q "$snapmnt"; then + umount "$snapmnt" + fi + + snapdir="$(get_btrfs_snapshot_dir "$opts" "$mnt")" + if [ -d "$snapdir" ]; then + btrfs subvolume delete "$snapdir" + fi +} + +function get_btrfs_snapshot_dir { + local opts="$1" mnt="$2" subvol="" + subvol="$(echo "$opts" | sed -nE 's/^.*\bsubvol=([^,]+)\b.*$/\1/p')" + if [ -z "$subvol" ]; then + echo "Unknown subvol for mountpoint: $mnt" + exit 1 + fi + echo "/a/${subvol}" +} + +if [ "$(id -u)" -ne 0 ]; then + echo "Must be root" + exit 1 +fi + +if [ "${1:-}" = "off" ]; then + unsnap +elif [ "${1:-}" = "on" ]; then + snap +else + echo "Usage: $0 on|off" + exit 1 +fi