summaryrefslogtreecommitdiff
path: root/backomp
diff options
context:
space:
mode:
authorDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2026-02-27 01:44:15 -0800
committerDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2026-02-27 02:27:43 -0800
commitce9441e87dab47bd0741b2be59d335643dc8a31b (patch)
tree841d773ca36ef924e5c2134a37f720ac8087e1b3 /backomp
parent26d67debb8eeb9f8ef06ce8f28ea48e9f5703d77 (diff)
downloadbackomp-master.tar.gz
backomp-master.tar.xz
support rcloneHEADmaster
Diffstat (limited to 'backomp')
-rwxr-xr-xbackomp134
1 files changed, 107 insertions, 27 deletions
diff --git a/backomp b/backomp
index 2fdd219..bf91371 100755
--- a/backomp
+++ b/backomp
@@ -124,69 +124,149 @@ current() {
(($(date -u +%s) / s == $(date -ud "${d}" +%s) / s))
}
+exists() {
+ local path="${1}"
+ local res
+
+ if [[ "${path}" == rclone:* ]]; then
+ rclone lsf -- "${path#rclone:}" &>/dev/null
+ res="$?"
+ [[ "${res}" -eq 0 ]] && return 0
+ [[ "${res}" -eq 3 ]] && return 1
+ err "rclone lsf failed: ${path#rclone:}"
+ return 2
+ fi
+
+ [[ -e "${path}" ]]
+}
+
filter() {
- local -n _f_src="${1}" _f_dest="${2}"
- local _f_dest_name="${2}" _f_label="${3}" _f_script="${4}"
+ local -n __src="${1}" __dest="${2}"
+ local __dest_name="${2}" __label="${3}" __script="${4}"
shift 4
- mapfile -td $'\0' "${_f_dest_name}" < <(
- printf '%s\0' "${_f_src[@]}" | awk -v 'RS=\0' -v 'ORS=\0' \
- "${@}" -- "${_f_script}")
+ mapfile -td $'\0' "${__dest_name}" < <(
+ printf '%s\0' "${__src[@]}" | awk -v 'RS=\0' -v 'ORS=\0' \
+ "${@}" -- "${__script}")
wait "$!"
if [[ "$?" -ne 0 ]]; then
- err "${_f_label} failed"
+ err "${__label} failed"
return 1
fi
- if [[ ${#_f_src[@]} -gt 0 && ! -d "${_f_dest[0]}" ]]; then
- err "${_f_label} returned empty or invalid list"
+
+ if [[ ${#__src[@]} -gt 0 ]]; then
+ exists "${__dest[0]}" && return 0
+ [[ "$?" -eq 1 ]] && \
+ err "${__label} returned empty or invalid list"
return 1
fi
}
+populate() {
+ local -n __arr="${1}"
+ local __arr_name="${1}" __dir="${2}"
+
+ if [[ "${__dir}" == rclone:* ]]; then
+ mapfile -t "${__arr_name}" < \
+ <(rclone lsf --dir-slash=false -- "${__dir#rclone:}" \
+ 2>/dev/null)
+ wait "$!"
+ if [[ "$?" -ne 0 && "$?" -ne 3 ]]; then
+ err "rclone lsf failed: ${__dir#rclone:}"
+ return 1
+ fi
+
+ __arr=( "${__arr[@]/#/${__dir}/}" )
+ return
+ fi
+
+ mapfile -td $'\0' "${__arr_name}" < \
+ <(shopt -s nullglob; printf '%s\0' "${__dir}/"*)
+}
+
+copy() {
+ local src="${1}" dest="${2}" link_dest="${3}"
+ local path res r_dest r_link_dest
+
+ for path in "${dest}" "${dest}.tmp"; do
+ exists "${path}"
+ res="$?"
+ if [[ "${res}" -ne 1 ]]; then
+ [[ "${res}" -eq 0 ]] && \
+ err "destination already exists: ${path}"
+ return 1
+ fi
+ done
+
+ 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
+ run rclone copy --sftp-copy-is-hardlink \
+ ${r_link_dest:+"--copy-dest=${r_link_dest#rclone:}"} \
+ -- "${src}" "${r_dest#rclone:}" || return 1
+ run rclone move -- "${dest#rclone:}.tmp" "${dest#rclone:}"
+ return
+ fi
+
+ run rsync -ac --mkpath \
+ ${link_dest:+"--link-dest=${link_dest}"} \
+ -- "${src}" "${dest}.tmp/" || return 1
+ run mv -T -- "${dest}.tmp" "${dest}" || return 1
+}
+
+prune() {
+ local -n __snaps="${1}" __keep="${2}"
+ local __i __k
+
+ for ((__i = ${#__snaps[@]} - 1, __k = 0; __i >= 0; --__i)); do
+ if [[ "${__snaps[__i]}" == "${__keep[__k]}" ]]; then
+ ((++__k))
+ else
+ if [[ "${__snaps[__i]}" == rclone:* ]]; then
+ run rclone purge -- "${__snaps[__i]#rclone:}"
+ else
+ run rm -rf -- "${__snaps[__i]}"
+ fi
+ fi
+ done
+}
+
backup() {
local src_path="${1}" dest_dir="${2}"
local retain="${3}" interval="${4}" week_start="${5}"
- local dest_path snaps keep skip i ki
+ local dest_path snaps keep
if [[ ! -e "${src_path}" ]]; then
err "source does not exist: ${src_path}"
return 1
fi
- mapfile -td $'\0' snaps < \
- <(shopt -s nullglob; printf '%s\0' "${dest_dir}/"*)
+ populate snaps "${dest_dir}" || return 1
filter snaps snaps 'pre-snapshot awk prefilter' "${AWK_PREFILTER}" \
|| return 1
[[ -n "${snaps}" ]] && current "${snaps[0]##*/}" "${interval}" \
&& return
dest_path="${dest_dir}/$(date -u '+%Y-%m-%d-%H%M%S')"
- if [[ -e "${dest_path}" || -e "${dest_path}.tmp" ]]; then
- err "destination already exists: ${dest_path}"
- return 1
- fi
- run rsync -ac --mkpath ${snaps[0]:+"--link-dest=${snaps[0]}"} \
- "${src_path}" "${dest_path}.tmp/" || return 1
- run mv -T "${dest_path}.tmp" "${dest_path}" || return 1
+ copy "${src_path}" "${dest_path}" "${snaps[0]}" || return 1
if [[ ! "${retain}" =~ ^( *([0-9]+|\*)\^?[hdwM] *)+$ ]]; then
err "invalid \$retain setting: ${retain}"
return 1
fi
+
snaps+=("${dest_path}")
filter snaps snaps 'post-snapshot awk prefilter' "${AWK_PREFILTER}" \
|| return 1
filter snaps keep 'awk filter' "${AWK_FILTER}" \
-v "opt_retain=${retain}" -v "opt_week_start=${week_start}" \
|| return 1
-
- for ((i = ${#snaps[@]} - 1, ki = 0; i >= 0; --i)); do
- if [[ "${snaps[i]}" == "${keep[ki]}" ]]; then
- ((++ki))
- else
- run rm -rf "${snaps[i]}"
- fi
- done
+ prune snaps keep
}
declare -A def_opts=(
@@ -212,7 +292,7 @@ while IFS='=' read -r f1 f2; do
printf '[%d] [%s] %s => %s%s\n' "$((++dcount))" \
"${opts['retain']}" "${f2}" "${dest}"
- dest_rp="$(realpath -m "${dest}")"
+ dest_rp="$(realpath -m -- "${dest}")"
if [[ -v "seen["${dest_rp}"]" ]]; then
err "destination already seen: ${dest}"
((++errors))