zfs-backup/backup.sh

295 lines
7.1 KiB
Bash
Raw Normal View History

2022-11-19 08:34:22 +00:00
#!/usr/bin/env sh
2023-01-17 03:58:23 +00:00
# requires zfs, pv, ssh
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 03:38:45 +00:00
# <dataset 1> [options]
# <dataset 2> [options]
# <dataset 3> [options]
2022-11-19 08:36:40 +00:00
# ...
2023-01-17 03:38:45 +00:00
# recognized options: nosend nosnap(shot) remotezfs=<remote dset name>
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"
ZFS_RECV_OPTS="-vsu"
2023-01-09 06:23:59 +00:00
SSH_SEND_OPTS="-Ti ~/.ssh/id_rsa"
2023-01-17 03:58:23 +00:00
DEFAULT_SPEED="20M"
2023-01-02 22:07:37 +00:00
read LOCAL_HOST LOCAL_USER LOCAL_PORT LOCAL_TZ SEND_COMPRESSED < $nodes_file
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=""
2023-01-02 22:07:37 +00:00
2023-01-17 00:44:22 +00:00
debug()
{
2023-01-17 03:44:37 +00:00
if [ "$ZB_VERBOSE" = "TRUE" ]; then
2023-01-17 00:44:22 +00:00
echo $@
fi
}
2023-01-17 03:18:27 +00:00
run_send_recv()
{
2023-01-17 03:55:02 +00:00
ssh_cmd="ssh $SSH_SEND_OPTS -o port=$port $user@$addr"
2023-01-17 05:05:31 +00:00
pv_cmd="pv -qL ${speed:-$DEFAULT_SPEED}"
2023-01-17 05:41:22 +00:00
if [ "$ZB_TEST_MODE" = "TRUE" ]; then
2023-01-17 03:55:02 +00:00
echo "$send_cmd | $pv_cmd | $ssh_cmd $recv_cmd"
2023-01-17 03:18:27 +00:00
else
2023-01-17 03:55:02 +00:00
$send_cmd | $pv_cmd | $ssh_cmd $recv_cmd
2023-01-17 03:18:27 +00:00
fi
}
2023-01-17 03:38:45 +00:00
run_snapshot()
{
2023-01-17 05:39:31 +00:00
if [ "$ZB_TEST_MODE" = "TRUE" ]; then
2023-01-17 03:38:45 +00:00
echo zfs snapshot $ZFS_SNAPSHOT_OPTS $dset@$snapid
2023-01-17 05:01:50 +00:00
else
2023-01-17 03:38:45 +00:00
zfs snapshot $ZFS_SNAPSHOT_OPTS $dset@$snapid
fi
}
run_allow()
{
2023-01-17 05:39:31 +00:00
if [ "$ZB_TEST_MODE" = "TRUE" ]; then
2023-01-17 03:38:45 +00:00
echo zfs allow -u $LOCAL_USER $ORIGIN_PERMS $dset
else
zfs allow -u $LOCAL_USER $ORIGIN_PERMS $dset
fi
}
run_remote()
{
ssh_cmd="ssh $SSH_SEND_OPTS -o port=$port $user@$addr"
2023-01-17 05:39:31 +00:00
if [ "$ZB_TEST_MODE" = "TRUE" ]; then
2023-01-17 03:41:43 +00:00
echo "\`$ssh_cmd $remote_cmd\`"
2023-01-17 03:38:45 +00:00
else
$ssh_cmd $remote_cmd
fi
}
2023-01-17 02:28:42 +00:00
set_compression() {
2023-01-17 05:39:31 +00:00
case "$SEND_COMPRESSED" in
2023-01-17 02:28:42 +00:00
[tTyY]*) ZFS_SEND_OPTS="$ZFS_SEND_OPTS -c"
debug "Enabled send as compressed datasets." ;;
esac
}
2023-01-17 00:46:05 +00:00
populate_datasets()
{
2023-01-17 03:38:45 +00:00
while read dset dataset_options; do
2023-01-17 05:39:31 +00:00
case "$dset" in
2023-01-17 00:46:05 +00:00
\#*) continue;;
esac
snapshot_dataset="yes"
send_dataset="yes"
for option in $dataset_options; do
2023-01-17 05:39:31 +00:00
case "$option" in
2023-01-17 00:48:28 +00:00
"nosnap"* )
snapshot_dataset=""
;;
"nosend"* )
send_dataset=""
;;
"remotezfs="*)
remotezfs=`echo $option|cut -d'=' -f2`
2023-01-17 03:38:45 +00:00
dset="$dset:$remotezfs"
2023-01-17 00:48:28 +00:00
;;
2023-01-17 00:46:05 +00:00
esac
done
if [ $send_dataset ]; then
2023-01-17 03:38:45 +00:00
SEND_DATASETS="$SEND_DATASETS $dset"
debug "Enabled send for $dset."
2023-01-17 00:46:05 +00:00
fi
if [ $snapshot_dataset ]; then
2023-01-17 03:38:45 +00:00
SNAPSHOT_DATASETS="$SNAPSHOT_DATASETS $dset"
debug "Enabled snapshot for $dset."
2023-01-17 00:46:05 +00:00
fi
done < datasets.txt
}
2023-01-02 22:07:37 +00:00
list_datasets()
2022-11-19 08:34:22 +00:00
{
2023-01-17 03:38:45 +00:00
for dset in $SEND_DATASETS; do
dset=`echo $dset|cut -d: -f1`
zfs list -H $dset
2023-01-02 22:07:37 +00:00
done
2022-11-19 08:34:22 +00:00
}
2023-01-02 22:07:37 +00:00
list_remote_datasets() {
2023-01-17 03:38:45 +00:00
while read addr user port zroot speed; do
2023-01-02 22:07:37 +00:00
echo "=== $user@$addr $zroot ==="
2023-01-17 05:39:31 +00:00
if [ "$zroot" ]; then
case "$zroot" in
2023-01-08 08:01:38 +00:00
*"/") ;;
*) zroot="$zroot/" ;;
esac
fi
2023-01-17 03:38:45 +00:00
remote_cmd="
sh -c 'for dset in $SEND_DATASETS; do
dset=`echo \\$dset|cut -d: -f2`
zfs list -H $zroot\$dset;
done'
"
run_remote
2023-01-02 22:07:37 +00:00
done < sendnodes.tmp
}
2022-11-19 08:34:22 +00:00
get_latest_snapshot()
{
2023-01-17 03:38:45 +00:00
zfs list -t snapshot $dset | 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
2023-01-17 05:39:31 +00:00
case "$zroot" in
2023-01-08 08:01:38 +00:00
*"/") ;;
*) zroot="$zroot/" ;;
esac
fi
2023-04-21 06:11:30 +00:00
list_cmd="zfs list -t snapshot $dset"
2023-01-17 03:55:02 +00:00
remote_cmd="$list_cmd|tail -n1|cut -d'@' -f2|cut -d' ' -f 1"
2023-01-17 03:38:45 +00:00
run_remote
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-17 03:38:45 +00:00
for dset in $SEND_DATASETS; do
dset=`echo $dset|cut -d: -f1`
run_allow
2023-01-02 22:07:37 +00:00
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 03:38:45 +00:00
while read addr user port zroot speed; do
2023-01-02 22:07:37 +00:00
echo "=== $user@$addr $zroot ==="
2023-01-17 05:39:31 +00:00
if [ "$zroot" ]; then
case "$zroot" in
2023-01-08 08:01:38 +00:00
*"/") ;;
*) zroot="$zroot/" ;;
esac
fi
2023-01-17 03:18:27 +00:00
remote_cmd="
2023-01-17 03:38:45 +00:00
for dset in $SEND_DATASETS; do
dset=`echo \\$dset|cut -d: -f2`
zfs allow -u $user $MIRROR_PERMS $zroot\$dset
done
"
2023-01-17 03:38:45 +00:00
run_remote
2023-01-02 22:07:37 +00:00
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-17 03:38:45 +00:00
snapid="`date -I`-`date +%s`-$LOCAL_TZ"
for dset in $SNAPSHOT_DATASETS; do
run_snapshot
2023-01-17 05:39:31 +00:00
debug "Created snapshot $dset@$snapid."
2023-01-02 22:07:37 +00:00
done
}
send_latest()
{
2023-01-17 03:38:45 +00:00
while read addr user port zroot speed; do
2023-01-02 22:07:37 +00:00
echo "=== $user@$addr $zroot ==="
2023-01-17 05:39:31 +00:00
if [ "$zroot" ]; then
case "$zroot" in
2023-01-08 08:01:38 +00:00
*"/") ;;
*) zroot="$zroot/" ;;
esac
fi
2023-01-17 03:38:45 +00:00
for dset in $SEND_DATASETS; do
rset=`echo $dset|cut -d: -f2`
dset=`echo $dset|cut -d: -f1`
debug "Processing $dset (remote $rset)."
2023-01-17 05:06:25 +00:00
snap1=`get_latest_snapshot`
2023-01-17 03:38:45 +00:00
send_cmd="zfs send $ZFS_SEND_OPTS $dset@$snap1"
2023-01-17 03:55:02 +00:00
recv_cmd="zfs recv $ZFS_RECV_OPTS $zroot$rset"
2023-01-17 03:18:27 +00:00
run_send_recv
2023-01-02 22:07:37 +00:00
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 03:38:45 +00:00
while read addr user port zroot speed; do
2023-01-02 22:07:37 +00:00
echo "=== $user@$addr $zroot ==="
2023-01-17 05:39:31 +00:00
if [ "$zroot" ]; then
case "$zroot" in
2023-01-08 08:01:38 +00:00
*"/") ;;
*) zroot="$zroot/" ;;
esac
fi
2023-01-17 03:38:45 +00:00
for dset in $SEND_DATASETS; do
rset=`echo $dset|cut -d: -f2`
dset=`echo $dset|cut -d: -f1`
snap1=`get_latest_snapshot`
snap0=`get_latest_remote_snapshot`
send_cmd="zfs send $ZFS_SEND_OPTS -i $dset@$snap0 $dset@$snap1"
2023-01-17 03:55:02 +00:00
recv_cmd="zfs recv $ZFS_RECV_OPTS $zroot$rset"
2023-01-17 03:18:27 +00:00
run_send_recv
2023-01-02 22:07:37 +00:00
done
done < sendnodes.tmp
2022-11-19 08:34:22 +00:00
}
2022-11-20 08:33:44 +00:00
################################### MAIN ####################################
2023-01-17 03:44:59 +00:00
set -- `getopt "tvl:" "$@"` || {
2023-01-17 03:45:15 +00:00
echo "Usage: `basename $0` [-tvl] [command]" 1>&2
2022-11-20 08:33:44 +00:00
exit 1
}
2023-01-17 05:01:50 +00:00
2022-11-20 08:33:44 +00:00
while :; do
case "$1" in
-t)
2023-01-17 03:08:50 +00:00
ZB_TEST_MODE=TRUE
2023-01-17 04:40:25 +00:00
debug "Enabled test mode."
2022-11-20 08:33:44 +00:00
;;
2023-01-17 03:44:37 +00:00
-v)
ZB_VERBOSE=TRUE
2023-01-17 01:09:44 +00:00
ZFS_SEND_OPTS="$ZFS_SEND_OPTS -v"
2023-01-17 02:27:36 +00:00
# Current on by default.
# ZFS_RECV_OPTS="$ZFS_RECV_OPTS -v"
2023-01-17 05:02:50 +00:00
debug "Enabled verbose mode."
2023-01-17 00:42:20 +00:00
;;
2022-11-20 08:33:44 +00:00
--) 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
2023-01-17 02:28:42 +00:00
set_compression
2023-01-17 00:46:05 +00:00
populate_datasets
2022-11-20 08:33:44 +00:00
### Command
case "$1" in
2023-01-17 03:59:16 +00:00
list ) list_datasets ;;
list-remote ) list_remote_datasets ;;
snapshot ) create_snapshots ;;
2023-01-17 04:52:24 +00:00
#configure-mirrors ) zfs_allow_mirrors ;;
configure ) zfs_allow_origin ;;
2023-01-17 03:59:16 +00:00
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
2023-01-17 04:52:24 +00:00
rm sendnodes.tmp