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