1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
#!/usr/bin/env bash
set -e
! read -rd '' FILTER_AWK <<'EOF'
function flr(n, s) {
return int(n / s) * s
}
BEGIN {
len = split("h d w m", arr)
for (i = 1; i <= len; ++i) {
b = arr[i]
if (!match(retain, "([*0-9]+)" b, md))
continue
ret[b] = md[1]
buckets[++n_buckets] = b
repl[b] = !!index(replace, b)
}
}
{
t = mktime(gensub(/(.*\/|^)(.+)-(..)-(..)-(..)(..)(..)/,
"\\2 \\3 \\4 \\5 \\6 \\7", 1), 1)
bt["h"] = flr(t, 60*60)
bt["d"] = flr(t, 24*60*60)
bt["m"] = mktime(strftime("%Y %m", t, 1) " 01 00 00 00", 1)
bt["w"] = bt["m"] + flr(t - bt["m"], 7*24*60*60)
for (i = 1; i <= n_buckets; ++i) {
b = buckets[i]
if (repl[b] && bt[b] == last[b, b]) {
for (j = 1; j < i; ++j) {
if (bt[buckets[j]] == last[b, buckets[j]])
break
}
if (j == i)
next
}
if ((b, bt[b]) in bkeep || ret[b] == "*" || bc[b]++ < ret[b])
bkeep[b, bt[b]] = $1
for (j in buckets)
last[b, buckets[j]] = bt[buckets[j]]
}
}
END {
for (i in bkeep)
++keep[bkeep[i]]
asorti(keep)
for (i in keep)
print keep[i]
}
EOF
run() {
echo "* ${@}"
"${@}"
}
backup() {
dest_dir="${1}"
src_path="${2}"
retain="${3}"
replace="${4}"
[[ "$((dcount++))" -ne 0 ]] && echo
echo "[${dcount}] [${retain}] ${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 -v "retain=${retain}" -v "replace=${replace}" \
"${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[@]}"
}
declare -A def_opts=(
['retain']='*h*d*w*m'
['replace']='h'
)
declare -A opts
while IFS='=' read -r f1 f2; do
[[ -z "${f1}" ]] || [[ "${f1}" == '#'* ]] && continue
if [[ "${f1}" =~ ^\[(.*)]$ ]]; then
base_path="${BASH_REMATCH[1]}"
opts="$(declare -p def_opts | sed '1s/[^=]*=//')"
elif [[ "${f1}" == '$'* ]]; then
opt="${f1:1}"
[[ "${f2}" == '-' ]] && opts["${opt}"]= ||
opts["${opt}"]="${f2:-"${def_opts["${opt}"]}"}"
else
backup "${base_path}/${f1}" "${f2}" \
"${opts['retain']}" "${opts['replace']}"
fi
done < "${1}"
|