Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
194 lines (174 sloc) 6.63 KB
#!/bin/bash
set -e -f -u -o pipefail
# Set up PATH properly. I need binaries from ~/bin, and rsync from homebrew on
# MacOS.
PATH="${HOME}/bin:/usr/local/bin:${PATH}"
__NUM_FAILED_BACKUPS=0
backup() {
if ! backup-single-directory-to-rsync-net "$@"; then
((__NUM_FAILED_BACKUPS++))
fi
}
all_backups_succeeded() {
if [[ "${__NUM_FAILED_BACKUPS}" -ne 0 && -t 0 ]]; then
# Only show error when interactive because I don't want output in cron jobs.
printf "%d failed backups\\n" "${__NUM_FAILED_BACKUPS}" >&2
fi
return "${__NUM_FAILED_BACKUPS}"
}
dest_dir() {
local hostname="$1" subdir="$2"
printf "%s/%s\\n" "${hostname}" "${subdir}"
}
ssh_key_basename() {
local hostname="$1" subdir="$2"
printf "%s_%s\\n" "${hostname}" "${subdir}"
}
ssh_key_path() {
local hostname="$1" subdir="$2"
printf "%s/.ssh/rsync-net/%s\\n" "${HOME}" \
"$(ssh_key_basename "${hostname}" "${subdir}")"
}
print_setup_instructions() {
local options="$1" hostname="$2" subdir="$3"
if [[ "${options}" != "--delete" && "${options}" != "--nodelete" \
&& "${options}" != "--pull" ]]; then
printf "Bad flag: %s; expecting --delete or --nodelete or --pull\\n" \
"${options}"
return 1
fi
local ssh_key ssh_key_path dest_dir
ssh_key="$(ssh_key_basename "${hostname}" "${subdir}")"
ssh_key_path="$(ssh_key_path "${hostname}" "${subdir}")"
dest_dir="$(dest_dir "${hostname}" "${subdir}")"
printf "Create a key with:\\n"
printf "$ ssh-keygen -t rsa -b 4096 -o -f %s -C rsync-net_%s\\n" \
"${ssh_key_path}" "${ssh_key}"
printf "Add the key to authorized_keys:\\n"
printf "command=\"rsync --server"
if [[ "${options}" == "--pull" ]]; then
printf " --sender"
fi
printf " -vlogDtpre.iLsfxC"
if [[ "${options}" == "--delete" ]]; then
printf " --delete"
fi
printf " --partial-dir=.rsync-partial"
printf " . %s\"" "${dest_dir}"
printf ",no-pty,no-agent-forwarding,no-port-forwarding PUB_KEY\\n"
}
check_ssh_key_exists() {
local options="$1" hostname="$2" subdir="$3" test_keys_only="$4" ssh_key_path
ssh_key_path="$(ssh_key_path "${hostname}" "${subdir}")"
if [[ ! -f "${ssh_key_path}" ]]; then
printf "Missing SSH key :(\\n" >&2
print_setup_instructions "${options}" "${hostname}" "${subdir}" >&2
if [[ "${test_keys_only}" -eq 1 ]]; then
return 0
fi
return 1
fi
return 0
}
run_rsync() {
local options="$1" hostname="$2" subdir="$3" source_dir="$4"
if [[ "${options}" != "--delete" && "${options}" != "--nodelete" \
&& "${options}" != "--pull" ]]; then
printf "Bad flag: %s; expecting --delete or --nodelete or --pull\\n" \
"${options}"
return 1
fi
local dest_dir ssh_key_path
dest_dir="$(dest_dir "${hostname}" "${subdir}")"
ssh_key_path="$(ssh_key_path "${hostname}" "${subdir}")"
# Make sure we use the right key rather than any inherited keys.
unset SSH_AUTH_SOCK
local flags=()
if [[ "${options}" == "--delete" ]]; then
flags+=("--delete")
fi
if [[ "${options}" != "--pull" ]]; then
flags+=("--filter=dir-merge rsync-net_filters")
flags+=("--filter=dir-merge,- .gitignore")
fi
if [[ -t 0 ]]; then
printf "Backing up %s\\n" "${source_dir}"
# Progress indicator when run interactively.
flags+=("--info=progress2,stats" "--progress" "--verbose")
fi
# Flip source and dest if we're pulling.
if [[ "${options}" == "--pull" ]]; then
flags+=("rsync-net:${dest_dir}/" "${source_dir}/")
else
flags+=("${source_dir}/" "rsync-net:${dest_dir}/")
fi
# https://git.samba.org/?p=rsync.git;a=blob_plain;f=support/rsync-no-vanished;hb=HEAD
# Suppress certain error messages by passing stderr through grep, taking care
# to handle grep failing if no lines are output. Why not discard stderr
# entirely? Because I want to know when weird errors happen, e.g. destination
# missing.
# Save the exit status if rsync fails so that it can be checked for the magic
# value 24, which is a magic exit code that means some files disappeared
# during the transfer. TODO: do I really need to do this?
local exit_status=0
( rsync --archive --bwlimit=100K \
--partial-dir=.rsync-partial --partial \
--rsh="ssh -i ${ssh_key_path}" \
"${flags[@]:+${flags[@]}}" \
2> >( grep -v \
-e '^Connection closed by' \
-e '^Connection reset by' \
-e '^Connection to .* timed out$' \
-e '^Timeout, server .* not responding' \
-e '^Warning: Permanently added the .* host key .* known hosts' \
-e '^file has vanished:' \
-e '^packet_write_wait: Connection to .* Broken pipe' \
-e '^rsync error: error in rsync protocol data stream (code 12)' \
-e '^rsync error: error in socket IO (code 10) at io.c' \
-e '^rsync error: unexplained error' \
-e '^rsync warning: some files vanished before they could be ' \
-e '^rsync: .sender. write error: Broken pipe (32)' \
-e '^rsync: connection unexpectedly closed' \
-e '^rsync: safe_write failed to write .* bytes to socket' \
-e '^ssh: Could not resolve hostname' \
-e '^ssh: connect to host .* No route to host' \
-e '^ssh: connect to host .*: Operation timed out' \
-e 'ssh_exchange_identification: read: Connection reset by peer' \
|| true)) \
|| exit_status="$?"
# Replace 24 with 0 so we exit successfully.
if [[ "${exit_status}" == 24 ]]; then
exit_status=0
fi
return "${exit_status}"
}
# Update a sentinel file.
# - Usage: update_sentinel_file "$@"
# - Uses a function named "usage" if bad arguments are passed.
# - Requires a function named "generate_contents" that will be passed the
# directory to put files in and the non-option arguments from "$@".
update_sentinel_file() {
# TODO: this is repetitive, figure out a better way. Also when checking SSH
# key existence.
local test_keys_only=0
if [[ "$#" -gt 1 && "$1" == "--test-keys-only" ]]; then
test_keys_only=1
shift
fi
local hostname_for_key="update" subdir="sentinel"
if ! check_ssh_key_exists --nodelete "${hostname_for_key}" "${subdir}" \
"${test_keys_only}"; then
return 1
fi
if [[ "${test_keys_only}" -eq 1 ]]; then
return 0
fi
local sentinel_dir
sentinel_dir="$(mktemp -d -t sentinel_dir.XXXXXXXXXX)"
# I want ${sentinel_dir} to be expanded now, because when we exit
# successfully it will be out of scope and cannot be expanded.
# shellcheck disable=SC2064
trap "rm -rf \"${sentinel_dir}\"" EXIT
generate_contents "${sentinel_dir}" "$@"
run_rsync --nodelete "${hostname_for_key}" "${subdir}" "${sentinel_dir}"
}
You can’t perform that action at this time.