Commit fc06953e authored by nimrod's avatar nimrod
Browse files

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.
parent c5fd0196
Loading
Loading
Loading
Loading
Loading
+54 −35
Original line number Diff line number Diff line
#!/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

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