first commit
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env bash
|
||||
# Snapshot ZFS filesystems and stream each snapshot to a remote ZFS host.
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: zfs-move-over-ssh.sh [options] SOURCE_ROOT REMOTE_HOST DESTINATION_ROOT
|
||||
|
||||
Create one snapshot for every filesystem under SOURCE_ROOT and send each one
|
||||
over SSH. Filesystems retain their relative paths below DESTINATION_ROOT.
|
||||
|
||||
Arguments:
|
||||
SOURCE_ROOT Source ZFS filesystem, for example: tank/data
|
||||
REMOTE_HOST SSH host, optionally user@host
|
||||
DESTINATION_ROOT Existing or new destination ZFS filesystem, for example: backup/data
|
||||
|
||||
Options:
|
||||
-n, --dry-run Print the ZFS and SSH commands without running them.
|
||||
-i, --identity-file FILE SSH private key to use.
|
||||
-p, --ssh-port PORT SSH port to use.
|
||||
--destroy-source-snapshots Destroy each local migration snapshot after its successful receive.
|
||||
-h, --help Show this help.
|
||||
|
||||
The remote SSH account must be able to run `sudo -n zfs receive` without a
|
||||
password prompt. This script does not destroy source filesystems or data.
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'Error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
DRY_RUN=false
|
||||
DESTROY_SOURCE_SNAPSHOTS=false
|
||||
SSH_OPTIONS=()
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
-n|--dry-run)
|
||||
DRY_RUN=true
|
||||
;;
|
||||
-i|--identity-file)
|
||||
(($# >= 2)) || die "$1 requires a file path"
|
||||
SSH_OPTIONS+=(-i "$2")
|
||||
shift
|
||||
;;
|
||||
-p|--ssh-port)
|
||||
(($# >= 2)) || die "$1 requires a port"
|
||||
SSH_OPTIONS+=(-p "$2")
|
||||
shift
|
||||
;;
|
||||
--destroy-source-snapshots)
|
||||
DESTROY_SOURCE_SNAPSHOTS=true
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
(($# == 3)) || {
|
||||
usage >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
SOURCE_ROOT="$1"
|
||||
REMOTE_HOST="$2"
|
||||
DESTINATION_ROOT="${3%/}"
|
||||
SNAPSHOT_NAME="move-$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
command -v zfs >/dev/null || die "zfs command was not found"
|
||||
command -v ssh >/dev/null || die "ssh command was not found"
|
||||
zfs list -H -o name -t filesystem "$SOURCE_ROOT" >/dev/null || die "source filesystem does not exist: $SOURCE_ROOT"
|
||||
|
||||
if ! $DRY_RUN; then
|
||||
ssh "${SSH_OPTIONS[@]}" "$REMOTE_HOST" 'sudo -n zfs list -H -o name -t filesystem >/dev/null' \
|
||||
|| die "cannot run 'sudo -n zfs' on $REMOTE_HOST"
|
||||
fi
|
||||
|
||||
map_destination() {
|
||||
local filesystem="$1"
|
||||
local relative_path="${filesystem#"$SOURCE_ROOT"}"
|
||||
printf '%s%s\n' "$DESTINATION_ROOT" "$relative_path"
|
||||
}
|
||||
|
||||
run() {
|
||||
if $DRY_RUN; then
|
||||
printf '+' >&2
|
||||
printf ' %q' "$@" >&2
|
||||
printf '\n' >&2
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
mapfile -t FILESYSTEMS < <(zfs list -H -o name -t filesystem -r "$SOURCE_ROOT")
|
||||
((${#FILESYSTEMS[@]} > 0)) || die "no filesystems found below $SOURCE_ROOT"
|
||||
|
||||
printf 'Migration snapshot: %s\n' "$SNAPSHOT_NAME"
|
||||
for filesystem in "${FILESYSTEMS[@]}"; do
|
||||
destination="$(map_destination "$filesystem")"
|
||||
snapshot="${filesystem}@${SNAPSHOT_NAME}"
|
||||
|
||||
printf 'Transferring %s to %s:%s\n' "$filesystem" "$REMOTE_HOST" "$destination"
|
||||
run zfs snapshot "$snapshot"
|
||||
|
||||
if $DRY_RUN; then
|
||||
printf '+ zfs send -p %q | ssh' "$snapshot" >&2
|
||||
printf ' %q' "${SSH_OPTIONS[@]}" "$REMOTE_HOST" "sudo -n zfs receive -uF '$destination'" >&2
|
||||
printf '\n' >&2
|
||||
else
|
||||
zfs send -p "$snapshot" | ssh "${SSH_OPTIONS[@]}" "$REMOTE_HOST" "sudo -n zfs receive -uF '$destination'"
|
||||
fi
|
||||
|
||||
if $DESTROY_SOURCE_SNAPSHOTS; then
|
||||
run zfs destroy "$snapshot"
|
||||
fi
|
||||
done
|
||||
|
||||
printf 'Completed. Local source filesystems were not destroyed.\n'
|
||||
Reference in New Issue
Block a user