diff options
Diffstat (limited to 'backomp')
| -rwxr-xr-x | backomp | 147 |
1 files changed, 101 insertions, 46 deletions
@@ -95,11 +95,41 @@ err() { echo "${0##*/}: ${err_ctx}: error: $*" >&2 } +parse_flags() { + local -n __arr="${1}" + local __str="${2}" __label="${3}" + + mapfile -td $'\0' __arr < <( + printf '%s' "${__str}" | xargs -r printf '%s\0') + if ! wait "$!"; then + err "parsing ${__label} failed" + return 1 + fi +} + run() { + local res + echo "* $*" - if ! "$@"; then - err "command failed: $*" - return 1 + "$@" + res="$?" + [[ "${res}" -ne 0 ]] && err "command failed: $*" + return "${res}" +} + +ssh_run() { + local host="${1}" cmd_str="${2}" + local cmd res flags + + parse_flags flags "${ssh_flags}" 'ssh_flags' || return 255 + cmd=(ssh "${flags[@]}" -n "${host}" "${cmd_str}") + if ((USE_RUN)); then + run "${cmd[@]}" + else + "${cmd[@]}" + res="$?" + [[ "${res}" -eq 255 ]] && err "command failed: ${cmd[*]}" + return "${res}" fi } @@ -126,26 +156,33 @@ current() { exists() { local path="${1}" - local res + local cmd res - if [[ "${path}" == rclone:* ]]; then - rclone lsf -- "${path#rclone:}" &>/dev/null + if [[ "${path}" == rclone://* ]]; then + cmd=(rclone lsf -- "${path#*//}") + "${cmd[@]}" &>/dev/null res="$?" [[ "${res}" -eq 0 ]] && return 0 [[ "${res}" -eq 3 ]] && return 1 - err "rclone lsf failed: ${path#rclone:}" + err "command failed: ${cmd[*]}" return 2 fi + if [[ "${path}" =~ ^([^:/]*):(.*) ]]; then + ssh_run "${BASH_REMATCH[1]}" \ + "test -e $(printf '%q' "${BASH_REMATCH[2]}")" + return + fi + [[ -e "${path}" ]] } filter() { local -n __src="${1}" __dest="${2}" - local __dest_name="${2}" __label="${3}" __script="${4}" + local __label="${3}" __script="${4}" shift 4 - mapfile -td $'\0' "${__dest_name}" < <( + mapfile -td $'\0' __dest < <( printf '%s\0' "${__src[@]}" | awk -v 'RS=\0' -v 'ORS=\0' \ "${@}" -- "${__script}") if ! wait "$!"; then @@ -163,30 +200,40 @@ filter() { populate() { local -n __arr="${1}" - local __arr_name="${1}" __dir="${2}" + local __dir="${2}" + local cmd - if [[ "${__dir}" == rclone:* ]]; then - mapfile -t "${__arr_name}" < \ - <(rclone lsf --dir-slash=false -- "${__dir#rclone:}" \ - 2>/dev/null) + if [[ "${__dir}" == rclone://* ]]; then + cmd=(rclone lsf --dir-slash=false -- "${__dir#*//}") + mapfile -t __arr < <("${cmd[@]}" 2>/dev/null) wait "$!" if [[ "$?" -ne 0 && "$?" -ne 3 ]]; then - err "rclone lsf failed: ${__dir#rclone:}" + err "command failed: ${cmd[*]}" return 1 fi - __arr=( "${__arr[@]/#/${__dir}/}" ) + __arr=("${__arr[@]/#/${__dir}/}") + return + fi + + if [[ "${__dir}" =~ ^([^:/]*):(.*) ]]; then + mapfile -td $'\0' __arr < \ + <(ssh_run "${BASH_REMATCH[1]}" \ + "shopt -s nullglob; printf '%s\0' $(\ + printf '%q' "${BASH_REMATCH[2]}")/*") + wait "$!" || return 1 + + __arr=("${__arr[@]/#/${BASH_REMATCH[1]}:}") return fi - mapfile -td $'\0' "${__arr_name}" < \ + mapfile -td $'\0' __arr < \ <(shopt -s nullglob; printf '%s\0' "${__dir}/"*) } copy() { local src="${1}" dest="${2}" link_dest="${3}" - local rsync_flags="${4}" rclone_flags="${5}" - local path res flags r_dest r_link_dest + local path res flags r_dest for path in "${dest}" "${dest}.tmp"; do exists "${path}" @@ -198,36 +245,34 @@ copy() { fi done - if [[ "${dest}" == rclone:* ]]; then + if [[ "${dest}" == rclone://* ]]; then r_dest="${dest}.tmp" - r_link_dest="${link_dest}" if [[ -d "${src}" ]]; then # emulates rsync src trailing slash behaviour r_dest+="/${src##*/}" - r_link_dest+="${r_link_dest:+/${src##*/}}" - fi - mapfile -td $'\0' flags < <( - printf '%s' "${rclone_flags}" | xargs -r printf '%s\0') - if ! wait "$!"; then - err 'parsing rclone flags failed' - return 1 + link_dest+="${link_dest:+/${src##*/}}" fi + parse_flags flags "${rclone_flags}" 'rclone_flags' || return 1 run rclone copy "${flags[@]}" \ - ${r_link_dest:+"--copy-dest=${r_link_dest#rclone:}"} \ - -- "${src}" "${r_dest#rclone:}" || return 1 - run rclone move -- "${dest#rclone:}.tmp" "${dest#rclone:}" + ${link_dest:+"--copy-dest=${link_dest#*//}"} \ + -- "${src}" "${r_dest#*//}" || return 1 + run rclone move -- "${dest#*//}.tmp" "${dest#*//}" return fi - mapfile -td $'\0' flags < <( - printf '%s' "${rsync_flags}" | xargs -r printf '%s\0') - if ! wait "$!"; then - err 'parsing rsync flags failed' - return 1 - fi + parse_flags flags "${rsync_flags}" 'rsync_flags' || return 1 run rsync "${flags[@]}" --mkpath \ - ${link_dest:+"--link-dest=${link_dest}"} \ + ${link_dest:+"--link-dest=../${link_dest##*/}"} \ -- "${src}" "${dest}.tmp/" || return 1 + + if [[ "${dest}" =~ ^([^:/]*):(.*) ]]; then + USE_RUN=1 ssh_run "${BASH_REMATCH[1]}" \ + "mv -T -- $(\ + printf '%q' "${BASH_REMATCH[2]}.tmp") $(\ + printf '%q' "${BASH_REMATCH[2]}")" + return + fi + run mv -T -- "${dest}.tmp" "${dest}" || return 1 } @@ -239,8 +284,12 @@ prune() { if [[ "${__snaps[__i]}" == "${__keep[__k]}" ]]; then ((++__k)) else - if [[ "${__snaps[__i]}" == rclone:* ]]; then - run rclone purge -- "${__snaps[__i]#rclone:}" + if [[ "${__snaps[__i]}" == rclone://* ]]; then + run rclone purge -- "${__snaps[__i]#*//}" + elif [[ "${__snaps[__i]}" =~ ^([^:/]*):(.*) ]]; then + USE_RUN=1 ssh_run "${BASH_REMATCH[1]}" \ + "rm -rf -- $(printf '%q' \ + "${BASH_REMATCH[2]}")" else run rm -rf -- "${__snaps[__i]}" fi @@ -251,7 +300,7 @@ prune() { backup() { local src_path="${1}" dest_dir="${2}" local retain="${3}" interval="${4}" week_start="${5}" - local rsync_flags="${6}" rclone_flags="${7}" + local rsync_flags="${6}" rclone_flags="${7}" ssh_flags="${8}" local dest_path snaps keep if [[ ! -e "${src_path}" ]]; then @@ -259,6 +308,9 @@ backup() { return 1 fi + # double colon after host enables daemon mode in rsync + [[ "${dest_dir}" =~ ^(rclone://)?[^:/]*:$ ]] && dest_dir+='.' + populate snaps "${dest_dir}" || return 1 filter snaps snaps 'pre-snapshot awk prefilter' "${AWK_PREFILTER}" \ || return 1 @@ -266,8 +318,7 @@ backup() { && return dest_path="${dest_dir}/$(date -u '+%Y-%m-%d-%H%M%S')" - copy "${src_path}" "${dest_path}" "${snaps[0]}" \ - "${rsync_flags}" "${rclone_flags}" || return 1 + copy "${src_path}" "${dest_path}" "${snaps[0]}" || return 1 if [[ ! "${retain}" =~ ^( *([0-9]+|\*)\^?[hdwM] *)+$ ]]; then err "invalid \$retain setting: ${retain}" @@ -339,7 +390,7 @@ while IFS= read -r line; do fi err_ctx="line ${lnum}" - if [[ ! "${line}" =~ ^((\\[\\=]|[^=])+)=(.*)$ ]]; then + if [[ ! "${line}" =~ ^((\\[\\=]|[^=])+)=(.*) ]]; then err "expected '='" ((++errors)) continue @@ -380,9 +431,12 @@ while IFS= read -r line; do src="${f2}" dest="${base_path}" - [[ -n "${dest}" && "${dest}" != *[/:] ]] && dest="${dest}/" - dest="${dest}$(expand_opts "${f1#./}")" + [[ "${dest:-/}" != */ && ! "${dest}" =~ ^(rclone://)?[^:/]*:$ ]] && + dest+='/' + f1_exp="$(expand_opts "${f1#./}")" tainted_dest="$?" + [[ "${dest}" =~ ^[^:/]*:$ && "${f1_exp}" == :* ]] && dest+='./' + dest+="${f1_exp}" printf '[%d] [%s] %s => %s\n' "${bcount}" "${opts['retain']}" \ "${src}" "${dest}" @@ -417,6 +471,7 @@ while IFS= read -r line; do "${opts['week_start']}" \ "${opts['rsync_flags']}" \ "${opts['rclone_flags']}" \ + "${opts['ssh_flags']}" \ || ((++errors)) done < "${1}" |
