#!/usr/bin/env sh nodes_file="nodes.txt" ### nodes.txt # # # # ... datasets_file="datasets.txt" ### datasets.txt # [remote dataset] # [remote dataset] # [remote dataset] # ... if [ ! -f $nodes_file ]; then echo "No nodes specified."; exit 2; fi if [ ! -f $datasets_file ]; then echo "No datasets specified."; exit 3; fi ZFS_SEND_OPTS="-pRLe" ZFS_SNAPSHOT_OPTS="-r" ZFS_RECV_OPTS="-vsu" SSH_SEND_OPTS="-Ti ~/.ssh/id_rsa" DEF_SPEED="20M" 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" MIRROR_PERMS="compression,receive,create,mount,mountpoint,readonly,jailed" SEND_DATASETS="" SNAPSHOT_DATASETS="" debug() { if [ $BG_DEBUG_MODE = "TRUE" ]; then echo $@ fi } populate_datasets() { while read dataset dataset_options; do case $dataset in \#*) continue;; esac snapshot_dataset="yes" send_dataset="yes" for option in $dataset_options; do case $option in "nosnap"* ) snapshot_dataset="" ;; "nosend"* ) send_dataset="" ;; "remotezfs="*) remotezfs=`echo $option|cut -d'=' -f2` dataset="$dataset:$remotezfs" ;; esac done if [ $send_dataset ]; then SEND_DATASETS="$SEND_DATASETS $dataset" debug "Enabled send for $dataset." fi if [ $snapshot_dataset ]; then SNAPSHOT_DATASETS="$SNAPSHOT_DATASETS $dataset" debug "Enabled snapshot for $dataset." fi done < datasets.txt } list_datasets() { for dataset in $SEND_DATASETS; do dataset=`echo $dataset|cut -d':' -f1` zfs list -H $dataset done } list_remote_datasets() { while read addr user ssh_port zroot speed; do echo "=== $user@$addr $zroot ===" if [ $zroot ]; then case $zroot in *"/") ;; *) zroot="$zroot/" ;; esac fi if [ $BG_TEST_MODE = "TRUE" ]; then echo ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr\ "sh -c 'for dataset in $SEND_DATASETS; do dataset=`echo \\$dataset|cut -d':' -f2` zfs list -H $zroot\$dataset; done'" else ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \ "sh -c 'for dataset in $SEND_DATASETS; do dataset=`echo \\$dataset|cut -d':' -f2` zfs list -H $zroot\$dataset; done';" fi done < sendnodes.tmp } get_latest_snapshot() { zfs list -t snapshot $dataset | tail -n1 | cut -d'@' -f2 | cut -d' ' -f 1 } get_latest_remote_snapshot() { if [ $zroot ]; then case $zroot in *"/") ;; *) zroot="$zroot/" ;; esac fi ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \ "zfs list -t snapshot $zroot$dataset | tail -n1|cut -d'@' -f2|cut -d' ' -f 1" } zfs_allow_origin() { for dataset in $SEND_DATASETS; do dataset=`echo $dataset|cut -d':' -f1` 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 } zfs_allow_mirrors() { while read addr user ssh_port zroot speed; do echo "=== $user@$addr $zroot ===" if [ $zroot ]; then case $zroot in *"/") ;; *) zroot="$zroot/" ;; esac fi if [ $BG_TEST_MODE = "TRUE" ]; then echo ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \ "for dataset in $SEND_DATASETS; do dataset=`echo \\$dataset|cut -d':' -f2` zfs allow -u $user $MIRROR_PERMS $zroot\$dataset done" else ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \ "for dataset in $SEND_DATASETS; do dataset=`echo \\$dataset|cut -d':' -f2` zfs allow -u $user $MIRROR_PERMS $zroot\$dataset done" fi done < sendnodes.tmp } create_snapshots() { snapshot_id="`date -I`-`date +%s`-$LOCAL_TZ" for dataset in $SNAPSHOT_DATASETS; do if [ $BG_TEST_MODE = "TRUE" ]; then echo zfs snapshot $ZFS_SNAPSHOT_OPTS $dataset@$snapshot_id else zfs snapshot $ZFS_SNAPSHOT_OPTS $dataset@$snapshot_id fi done } send_latest() { while read addr user ssh_port zroot speed; do echo "=== $user@$addr $zroot ===" if [ $zroot ]; then case $zroot in *"/") ;; *) zroot="$zroot/" ;; esac fi for dataset in $SEND_DATASETS; do dataset=`echo $dataset|cut -d':' -f1` remote_dataset=`echo $dataset|cut -d':' -f2` debug "dataset: $dataset" debug "remote dataset: $remote_dataset" origin_snapshot=`get_latest_snapshot $dataset` if [ $BG_TEST_MODE = "TRUE" ]; then echo zfs send $ZFS_SEND_OPTS $dataset@$origin_snapshot \| \ pv -qL ${speed:-$DEF_SPEED} \| \ ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \ zfs recv $ZFS_RECV_OPTS $zroot$remote_dataset else zfs send $ZFS_SEND_OPTS $dataset@$origin_snapshot | pv -qL ${speed:-$DEF_SPEED} | ssh $SSH_SEND_OPTS -o port=$ssh_port \ $user@$addr \ zfs recv $ZFS_RECV_OPTS $zroot$remote_dataset fi done done < sendnodes.tmp } send_increment() { while read addr user ssh_port zroot speed; do echo "=== $user@$addr $zroot ===" if [ $zroot ]; then case $zroot in *"/") ;; *) zroot="$zroot/" ;; esac fi for dataset in $SEND_DATASETS; do dataset=`echo $dataset|cut -d':' -f1` remote_dataset=`echo $dataset|cut -d':' -f2` 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 \| \ pv -qL ${speed:-$DEF_SPEED} \| \ ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \ zfs recv $ZFS_RECV_OPTS $zroot$remote_dataset else zfs send $ZFS_SEND_OPTS \ -i $dataset@$remote_snapshot \ $dataset@$origin_snapshot | pv -qL ${speed:-$DEF_SPEED} | ssh $SSH_SEND_OPTS -o port=$ssh_port $user@$addr \ zfs recv $ZFS_RECV_OPTS $zroot$remote_dataset fi done done < sendnodes.tmp } ################################### MAIN #################################### ### Options set -- `getopt "tdl:" "$@"` || { echo "Usage: `basename $0` [-tdl] [command]" 1>&2 exit 1 } BG_TEST_MODE=FALSE while :; do case "$1" in -t) BG_TEST_MODE=TRUE echo Test mode. ;; -d) BG_DEBUG_MODE=TRUE echo Debug mode. ;; --) shift; break ;; esac shift done tail -n +2 $nodes_file|grep -v '^#' > sendnodes.tmp populate_datasets ### Command case "$1" in 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 ;; esac rm sendnodes.tmp