]> code.delx.au - monosys/commitdiff
Merge ../bsnap
authorJames Bunton <jamesbunton@delx.au>
Sun, 28 Jan 2024 21:21:31 +0000 (08:21 +1100)
committerJames Bunton <jamesbunton@delx.au>
Sun, 28 Jan 2024 21:21:31 +0000 (08:21 +1100)
.gitignore
bsnap/README.md [new file with mode: 0644]
bsnap/bsnap [new file with mode: 0755]

index c2c027fec1a7e09465dfb6b80e9f9e4c3d63a2b5..40830374235df1c19661a2901b7ca73cc9499f3d 100644 (file)
@@ -1 +1 @@
-local
\ No newline at end of file
+local
diff --git a/bsnap/README.md b/bsnap/README.md
new file mode 100644 (file)
index 0000000..e4e7322
--- /dev/null
@@ -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 (executable)
index 0000000..cdfb2db
--- /dev/null
@@ -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