zfs-backup/backup.sh

259 lines
7.8 KiB
Bash
Raw Normal View History

2022-11-19 08:34:22 +00:00
#!/usr/bin/env sh
nodes_file="nodes.txt"
2022-11-19 08:49:50 +00:00
### nodes.txt
2023-01-02 22:07:37 +00:00
# <origin host> <user> <ssh port> <timezone> <compressed flag>
2023-01-17 00:30:22 +00:00
# <mirror 1 host> <user> <ssh port> <zfs root> <send speed>
# <mirror 2 host> <user> <ssh port> <zfs root> <send speed>
2022-11-19 08:36:40 +00:00
# ...
datasets_file="datasets.txt"
2022-11-21 01:15:43 +00:00
### datasets.txt
2023-01-17 00:19:06 +00:00
# <dataset 1> [remote dataset]
# <dataset 2> [remote dataset]
# <dataset 3> [remote dataset]
2022-11-19 08:36:40 +00:00
# ...
2023-01-02 22:07:37 +00:00
if [ ! -f $nodes_file ]; then echo "No nodes specified."; exit 2; fi
if [ ! -f $datasets_file ]; then echo "No datasets specified."; exit 3; fi
2023-01-09 07:21:29 +00:00
ZFS_SEND_OPTS="-pRLe"
2023-01-02 22:07:37 +00:00
ZFS_SNAPSHOT_OPTS="-r"
2023-01-09 07:21:29 +00:00
ZFS_RECV_OPTS="-vsu"
2023-01-09 06:23:59 +00:00
SSH_SEND_OPTS="-Ti ~/.ssh/id_rsa"
2023-01-17 00:25:20 +00:00
DEF_SPEED="20M"
2023-01-02 22:07:37 +00:00
read LOCAL_HOST LOCAL_USER LOCAL_PORT LOCAL_TZ SEND_COMPRESSED < $nodes_file
case $SEND_COMPRESSED in
[tTyY]*) ZFS_SEND_OPTS="$ZFS_SEND_OPTS -c" ;;
esac
ORIGIN_PERMS="send,snapshot,hold"
2023-01-09 06:23:59 +00:00
MIRROR_PERMS="compression,receive,create,mount,mountpoint,readonly,jailed"
2023-01-02 22:07:37 +00:00
2023-01-08 07:10:51 +00:00
SEND_DATASETS=""
SNAPSHOT_DATASETS=""
while read dataset dataset_options; do
2023-01-02 22:07:37 +00:00
case $dataset in
\#*) continue;;
esac
2023-01-08 07:10:51 +00:00
snapshot_dataset="yes"
send_dataset="yes"
2023-01-17 00:19:06 +00:00
for option in $dataset_options; do
case $option in
2023-01-17 00:37:24 +00:00
"nosnap"* ) snapshot_dataset="" ;;
"nosend"* ) send_dataset="" ;;
"remotezfs="*) remotezfs=`echo $option|cut -d'=' -f2` ;
2023-01-17 00:19:06 +00:00
dataset="$dataset:$remotezfs" ;;
esac
done
2023-01-08 07:10:51 +00:00
if [ $send_dataset ]; then
SEND_DATASETS="$SEND_DATASETS $dataset"
fi
if [ $snapshot_dataset ]; then
SNAPSHOT_DATASETS="$SNAPSHOT_DATASETS $dataset"
fi
done < datasets.txt
2023-01-02 22:07:37 +00:00
list_datasets()
2022-11-19 08:34:22 +00:00
{
2023-01-08 07:10:51 +00:00
for dataset in $SEND_DATASETS; do
2023-01-17 00:19:06 +00:00
dataset=`echo $dataset|cut -d':' -f1`
2023-01-02 22:07:37 +00:00
zfs list -H $dataset
done
2022-11-19 08:34:22 +00:00
}
2023-01-02 22:07:37 +00:00
list_remote_datasets() {
2023-01-17 00:25:20 +00:00
while read addr user ssh_port zroot speed; do
2023-01-02 22:07:37 +00:00
echo "=== $user@$addr $zroot ==="
2023-01-08 08:01:38 +00:00
if [ $zroot ]; then
case $zroot in
*"/") ;;
*) zroot="$zroot/" ;;
esac
fi
2022-11-24 03:56:12 +00:00
if [ $BG_TEST_MODE = "TRUE" ]; then
2023-01-09 06:23:59 +00:00
echo ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr\
2023-01-17 00:19:06 +00:00
"sh -c 'for dataset in $SEND_DATASETS; do
dataset=`echo \\$dataset|cut -d':' -f2`
zfs list -H $zroot\$dataset;
2023-01-02 22:07:37 +00:00
done'"
else
2023-01-09 06:23:59 +00:00
ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \
2023-01-17 00:19:06 +00:00
"sh -c 'for dataset in $SEND_DATASETS; do
dataset=`echo \\$dataset|cut -d':' -f2`
zfs list -H $zroot\$dataset;
2023-01-02 22:07:37 +00:00
done';"
2022-11-24 03:56:12 +00:00
fi
2023-01-02 22:07:37 +00:00
done < sendnodes.tmp
}
2022-11-19 08:34:22 +00:00
get_latest_snapshot()
{
2023-01-02 22:07:37 +00:00
zfs list -t snapshot $dataset | tail -n1 | cut -d'@' -f2 | cut -d' ' -f 1
2022-11-19 08:34:22 +00:00
}
2023-01-02 22:07:37 +00:00
get_latest_remote_snapshot()
2022-11-19 08:34:22 +00:00
{
2023-01-08 08:01:38 +00:00
if [ $zroot ]; then
case $zroot in
*"/") ;;
*) zroot="$zroot/" ;;
esac
fi
2023-01-09 06:23:59 +00:00
ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \
2023-01-17 00:30:22 +00:00
"zfs list -t snapshot $zroot$dataset |
2023-01-02 22:07:37 +00:00
tail -n1|cut -d'@' -f2|cut -d' ' -f 1"
2022-11-19 08:34:22 +00:00
}
2023-01-02 22:07:37 +00:00
zfs_allow_origin()
2022-11-19 08:34:22 +00:00
{
2023-01-08 07:10:51 +00:00
for dataset in $SEND_DATASETS; do
2023-01-17 00:19:06 +00:00
dataset=`echo $dataset|cut -d':' -f1`
2023-01-02 22:07:37 +00:00
if [ $BG_TEST_MODE = "TRUE" ]; then
echo zfs allow -u $LOCAL_USER $ORIGIN_PERMS $dataset
else
zfs allow -u $LOCAL_USER $ORIGIN_PERMS $dataset
fi
done
2022-11-19 08:34:22 +00:00
}
2023-01-02 22:07:37 +00:00
zfs_allow_mirrors()
2022-11-19 08:34:22 +00:00
{
2023-01-17 00:25:20 +00:00
while read addr user ssh_port zroot speed; do
2023-01-02 22:07:37 +00:00
echo "=== $user@$addr $zroot ==="
2023-01-08 08:01:38 +00:00
if [ $zroot ]; then
case $zroot in
*"/") ;;
*) zroot="$zroot/" ;;
esac
fi
2023-01-02 22:07:37 +00:00
if [ $BG_TEST_MODE = "TRUE" ]; then
2023-01-09 06:23:59 +00:00
echo ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \
2023-01-08 07:10:51 +00:00
"for dataset in $SEND_DATASETS; do
2023-01-17 00:19:06 +00:00
dataset=`echo \\$dataset|cut -d':' -f2`
2023-01-08 08:01:38 +00:00
zfs allow -u $user $MIRROR_PERMS $zroot\$dataset
2023-01-02 22:07:37 +00:00
done"
else
2023-01-09 06:23:59 +00:00
ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \
2023-01-08 07:10:51 +00:00
"for dataset in $SEND_DATASETS; do
2023-01-17 00:19:06 +00:00
dataset=`echo \\$dataset|cut -d':' -f2`
2023-01-08 08:01:38 +00:00
zfs allow -u $user $MIRROR_PERMS $zroot\$dataset
2023-01-02 22:07:37 +00:00
done"
fi
done < sendnodes.tmp
2022-11-19 08:34:22 +00:00
}
2023-01-02 22:07:37 +00:00
create_snapshots()
2022-11-19 08:34:22 +00:00
{
2023-01-02 22:07:37 +00:00
snapshot_id="`date -I`-`date +%s`-$LOCAL_TZ"
2023-01-08 07:10:51 +00:00
for dataset in $SNAPSHOT_DATASETS; do
2022-11-21 02:51:03 +00:00
if [ $BG_TEST_MODE = "TRUE" ]; then
2023-01-02 22:07:37 +00:00
echo zfs snapshot $ZFS_SNAPSHOT_OPTS $dataset@$snapshot_id
else
zfs snapshot $ZFS_SNAPSHOT_OPTS $dataset@$snapshot_id
2022-11-20 08:33:44 +00:00
fi
2023-01-02 22:07:37 +00:00
done
}
send_latest()
{
2023-01-17 00:25:20 +00:00
while read addr user ssh_port zroot speed; do
2023-01-02 22:07:37 +00:00
echo "=== $user@$addr $zroot ==="
2023-01-08 08:01:38 +00:00
if [ $zroot ]; then
case $zroot in
*"/") ;;
*) zroot="$zroot/" ;;
esac
fi
2023-01-08 07:10:51 +00:00
for dataset in $SEND_DATASETS; do
2023-01-17 00:19:06 +00:00
dataset=`echo $dataset|cut -d':' -f1`
remote_dataset=`echo $dataset|cut -d':' -f2`
2023-01-02 22:07:37 +00:00
origin_snapshot=`get_latest_snapshot $dataset`
if [ $BG_TEST_MODE = "TRUE" ]; then
echo zfs send $ZFS_SEND_OPTS $dataset@$origin_snapshot \| \
2023-01-17 00:25:20 +00:00
pv -qL ${speed:-$DEF_SPEED} \| \
2023-01-09 06:35:38 +00:00
ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \
2023-01-17 00:19:06 +00:00
zfs recv $ZFS_RECV_OPTS $zroot$remote_dataset
2023-01-02 22:07:37 +00:00
else
zfs send $ZFS_SEND_OPTS $dataset@$origin_snapshot |
2023-01-17 00:25:20 +00:00
pv -qL ${speed:-$DEF_SPEED} |
2023-01-09 06:35:38 +00:00
ssh $SSH_SEND_OPTS -o port=$ssh_port \
$user@$addr \
2023-01-17 00:19:06 +00:00
zfs recv $ZFS_RECV_OPTS $zroot$remote_dataset
2023-01-02 22:07:37 +00:00
fi
done
2022-11-20 08:33:44 +00:00
done < sendnodes.tmp
2022-11-19 08:34:22 +00:00
}
2023-01-02 22:07:37 +00:00
send_increment()
2022-11-19 08:34:22 +00:00
{
2023-01-17 00:25:20 +00:00
while read addr user ssh_port zroot speed; do
2023-01-02 22:07:37 +00:00
echo "=== $user@$addr $zroot ==="
2023-01-08 08:01:38 +00:00
if [ $zroot ]; then
case $zroot in
*"/") ;;
*) zroot="$zroot/" ;;
esac
fi
2023-01-08 07:10:51 +00:00
for dataset in $SEND_DATASETS; do
2023-01-17 00:19:06 +00:00
dataset=`echo $dataset|cut -d':' -f1`
remote_dataset=`echo $dataset|cut -d':' -f2`
2023-01-02 22:07:37 +00:00
origin_snapshot=`get_latest_snapshot`
remote_snapshot=`get_latest_remote_snapshot`
if [ $BG_TEST_MODE = "TRUE" ]; then
echo zfs send $ZFS_SEND_OPTS \
-i $dataset@$remote_snapshot \
$dataset@$origin_snapshot \| \
2023-01-17 00:25:20 +00:00
pv -qL ${speed:-$DEF_SPEED} \| \
2023-01-09 06:35:38 +00:00
ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \
2023-01-17 00:19:06 +00:00
zfs recv $ZFS_RECV_OPTS $zroot$remote_dataset
2023-01-02 22:07:37 +00:00
else
zfs send $ZFS_SEND_OPTS \
-i $dataset@$remote_snapshot \
2023-01-09 06:35:38 +00:00
$dataset@$origin_snapshot |
2023-01-17 00:25:20 +00:00
pv -qL ${speed:-$DEF_SPEED} |
2023-01-09 06:35:38 +00:00
ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \
2023-01-17 00:19:06 +00:00
zfs recv $ZFS_RECV_OPTS $zroot$remote_dataset
2023-01-02 22:07:37 +00:00
fi
done
done < sendnodes.tmp
2022-11-19 08:34:22 +00:00
}
2022-11-20 08:33:44 +00:00
################################### MAIN ####################################
### Options
set -- `getopt "tl:" "$@"` || {
echo "Usage: `basename $0` [-tl] [command]" 1>&2
exit 1
}
2022-11-21 02:51:03 +00:00
BG_TEST_MODE=FALSE
2022-11-20 08:33:44 +00:00
while :; do
case "$1" in
-t)
2022-11-21 02:51:03 +00:00
BG_TEST_MODE=TRUE
2022-11-20 08:33:44 +00:00
echo Test mode.
;;
--) shift; break ;;
esac
shift
done
2023-01-02 22:07:37 +00:00
tail -n +2 $nodes_file|grep -v '^#' > sendnodes.tmp
2022-11-20 08:33:44 +00:00
### Command
case "$1" in
2023-01-02 22:07:37 +00:00
list) list_datasets ;;
list-remote) list_remote_datasets ;;
snapshot) create_snapshots ;;
configure-mirrors) zfs_allow_mirrors ;;
configure-origin) zfs_allow_origin ;;
mirror-reset) send_latest ;;
mirror-increment) send_increment ;;
#mirror-prune) send_increment_with_F ;;
2022-11-20 08:33:44 +00:00
esac
2023-01-02 22:07:37 +00:00
rm sendnodes.tmp