summaryrefslogtreecommitdiff
path: root/backomp
diff options
context:
space:
mode:
Diffstat (limited to 'backomp')
-rwxr-xr-xbackomp147
1 files changed, 101 insertions, 46 deletions
diff --git a/backomp b/backomp
index 69ac1ca..1813ea2 100755
--- a/backomp
+++ b/backomp
@@ -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}"