summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbackomp80
1 files changed, 80 insertions, 0 deletions
diff --git a/backomp b/backomp
new file mode 100755
index 0000000..0a73a2f
--- /dev/null
+++ b/backomp
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+
+set -e
+
+! read -rd '' FILTER_AWK <<'EOF'
+function flr(n, s) { return int(n / s) * s }
+
+{
+ t = mktime(gensub(/(.*\/|^)(.+)-(..)-(..)-(..)(..)(..)/,
+ "\\2 \\3 \\4 \\5 \\6 \\7", 1), 1)
+
+ h = flr(t, 60*60)
+ d = flr(t, 24*60*60)
+ m = mktime(strftime("%Y %m", t, 1) " 01 00 00 00", 1)
+ w = m + flr(t - m, 7*24*60*60)
+}
+
+h in hkeep || hc++ < 24 { hkeep[h] = $1 }
+d in dkeep || dc++ < 7 { dkeep[d] = $1 }
+w in wkeep || wc++ < 4 { wkeep[w] = $1 }
+{ mkeep[m] = $1 }
+
+END {
+ for (i in hkeep) keep[hkeep[i]]++
+ for (i in dkeep) keep[dkeep[i]]++
+ for (i in wkeep) keep[wkeep[i]]++
+ for (i in mkeep) keep[mkeep[i]]++
+
+ asorti(keep)
+ for (i in keep) print keep[i]
+}
+EOF
+
+run() {
+ echo "* ${@}"
+ "${@}"
+}
+
+backup() {
+ dest_dir="${1}"
+ src_path="${2}"
+
+ [[ "$((dcount++))" -ne 0 ]] && echo
+ echo "[${dcount}] ${src_path} => ${dest_dir}"
+ mapfile -t snaps < <(shopt -s nullglob;
+ printf '%s\n' "${dest_dir}/"* | sort -r | head -c -1)
+
+ dest_path="${dest_dir}/$(date -u '+%Y-%m-%d-%H%M%S')"
+ run rsync -ac --mkpath ${snaps[0]:+"--link-dest=${snaps[0]}"} \
+ "${src_path}" "${dest_path}"
+ snaps=("${dest_path}" "${snaps[@]}")
+
+ mapfile -t keep < <(
+ awk "${FILTER_AWK}" < <(printf '%s\n' "${snaps[@]}"))
+ if [[ -z "${keep}" ]]; then
+ echo "WARNING: dirs to keep shouldn't be empty; skipping" >&2
+ return
+ fi
+
+ for ((i = ${#snaps[@]} - 1, ki = 0; i >= 0; --i)); do
+ if [[ "${snaps[i]}" == "${keep[ki]}" ]]; then
+ ((++ki))
+ else
+ run rm -r "${snaps[i]}"
+ fi
+ done
+
+ printf 'kept %s\n' "${keep[@]}"
+}
+
+while IFS='=' read -r f1 f2; do
+ [[ -z "${f1}" ]] && continue
+
+ if [[ "${f1}" =~ ^\[(.*)]$ ]]; then
+ base_path="${BASH_REMATCH[1]}"
+ continue
+ fi
+
+ backup "${base_path}/${f1}" "${f2}"
+done < "${1}"