From fc06953eb7c15dd8f14ef4ed6f052e21d1d7b610 Mon Sep 17 00:00:00 2001 From: Adar Nimrod <nimrod@shore.co.il> Date: Fri, 7 May 2021 16:35:15 +0300 Subject: [PATCH] Another take on btrfs pool backup. The thought is this: convert the pool to a mirror, add the destination to the pool, wait for everything to duplicate, remove the destination and convert back to a single device pool. The upside is that all of the subvolumes are copied without any special effort and that the state of them is synced entirely. The downside is that it doesn't work (I think it's the superblock), one of the devices ends up as broken. Further work is needed. --- roles/debian_server/files/btrfs-backup | 89 ++++++++++++++++---------- 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/roles/debian_server/files/btrfs-backup b/roles/debian_server/files/btrfs-backup index cb65985..ba57c4d 100755 --- a/roles/debian_server/files/btrfs-backup +++ b/roles/debian_server/files/btrfs-backup @@ -1,45 +1,64 @@ #!/bin/sh set -eu -list_subvolumes() { - btrfs subvolume list --sort=path "$1" | awk '{print $9}' | uniq +error() { + echo "$@" >&2 + exit 1 } -cleanup() { - for volume in $(list_subvolumes "$source/.snapshot") - do - if [ -d "$source/.snapshot/$volume" ] - then - btrfs subvolume delete "$source/.snapshot/$volume" - fi - done - btrfs subvolume delete "$source/.snapshot" - sync --file-system "$source" +usage() { + error "Usage: $0 <start|status|cleanup> <source> <destination>" } -if [ "$#" -ne 2 ] -then - echo "Usage: $0 source destination" >&2 - exit 1 -fi - -source="$1" -destination="$2" +start() { + [ "$#" -eq 2 ] || usage + source="$1" + destination="$2" + [ -d "$source" ] || error 'Source is not a btrfs volume.' + [ "$(df --output=fstype "$source" | tail +1)" = 'btrfs' ] || \ + error 'Source is not a btrfs volume.' + [ -b "$destination" ] || error 'Destination is not a block device.' + echo "This will delete all data on $destination !" >&2 + echo 'You have 5 seconds to abort with ctrl+c.' >&2 + sleep 6 + btrfs device add -f "$destination" "$source" + btrfs balance start -f --bg -dconvert=dup -mconvert=dup -sconvert=dup "$source" + echo 'Backup has started.' + echo 'You can check if it has finished by running:' + echo "$0 status $source" +} -# Before the first snapshot is made. -subvolumes="$(list_subvolumes "$source")" -trap 'cleanup' INT QUIT EXIT TERM +status() { + # Ignore the destination, if passed. + [ "$#" -eq 2 ] || [ "$#" -eq 1 ] || usage + source="$1" + [ -d "$source" ] || error 'Source is not a btrfs volume.' + [ "$(df --output=fstype "$source" | tail +1)" = 'btrfs' ] || \ + error 'Source is not a btrfs volume.' + if btrfs balance status "$source" | grep -q '^No balance found' + then + echo 'Backup has finished.' + else + echo 'Backup is still running.' + exit 2 + fi +} -# It would be better to take all snapshots atomically, but that's not possible. -btrfs subvolume snapshot "$source" "$source/.snapshot" -for volume in $subvolumes -do - rm --dir "$source/.snapshot/$volume" - btrfs subvolume snapshot "$source/$volume" "$source/.snapshot/$volume" -done -sync --file-system "$source" +cleanup() { + # Ignore the destination, if passed. + [ "$#" -eq 2 ] || [ "$#" -eq 1 ] || usage + source="$1" + [ -d "$source" ] || error 'Source is not a btrfs volume.' + [ "$(df --output=fstype "$source" | tail +1)" = 'btrfs' ] || \ + error 'Source is not a btrfs volume.' + btrfs device remove "$destination" "$source" + btrfs balance start --force -dconvert=single -mconvert=single -sconvert=single"$source" +} -rsync --archive \ - --delete \ - "$source/.snapshot/" \ - "$destination" +[ "$#" -gt 1 ] || usage +case "$1" in + start) shift; start "$@";; + status) shift; status "$@";; + cleanup) shift; cleanup "$@";; + *) usage;; +esac -- GitLab