btrbk fixes
[distro-setup] / btrbk-run
1 #!/bin/bash
2 # Copyright (C) 2016 Ian Kelling
3
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7
8 # http://www.apache.org/licenses/LICENSE-2.0
9
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16
17 # todo: if we cancel in the middle of a btrfs send, then run again
18 # immediately, the received subvolume doesn't get a Received UUID:
19 # field, and we won't mount it. Need to figure out a solution that will
20 # fix this.
21
22
23 [[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
24
25 source /usr/local/lib/err
26
27 usage() {
28 cat <<'EOF'
29 btrbk-run [OPTIONS] [run|resume|archive]
30 usually -t TARGET_HOST or -s SOURCE_HOST
31
32 Note, at source location, intentionally not executable, run and read
33 install-my-scripts.
34
35 EOF
36 echo "top of script file:"
37 sed -n '1,/^[# ]*end command line/{p;b};q' "$0"
38 exit $1
39 }
40
41
42
43 script_name="${BASH_SOURCE[0]}"
44 script_name="${script_name##*/}"
45 pre="${SSH_CLIENT:+$HOSTNAME} $script_name:"
46 m() { if $verbose; then printf "$pre%s\n" "$*"; fi; "$@"; }
47 e() { printf "$pre%s\n" "$*"; }
48 die() { printf "$pre%s\n" "$*" >&2; echo "exiting with status 1" >&2; exit 1; }
49 mexit() { echo "$pre: exiting with status $1"; exit $1; }
50
51 # latest $MAIL_HOST
52 if [[ -e /b/bash_unpublished/source-state ]]; then
53 source /b/bash_unpublished/source-state
54 fi
55
56 # note q is owned by root:1000
57
58 mountpoints=()
59
60 rsync_mountpoint=/q
61
62 ret=0
63 # default options
64 conf_only=false
65 dry_run=false # mostly for testing
66 rate_limit=no
67 verbose=true; verbose_arg=-v
68 force=false
69 if [[ $INVOCATION_ID ]]; then
70 # INVOCATION_ID means running as a systemd service. we cant show progress in this case,
71 # but if we pass the arg, it will insert mbuffer into the command.
72 progress_arg=
73 else
74 progress_arg="--progress"
75 fi
76 incremental_strict=false
77 pull_reexec=false
78
79 default_args_file=/etc/btrbk-run.conf
80 if [[ -s $default_args_file ]]; then
81 # shellcheck disable=SC2046 # we want word splitting
82 set -- $(< $default_args_file) "$@"
83 # i havent used this feature yet, so warn about it
84 echo "$0: warning: default btrbk-run options set in $default_args_file (sleeping 5 seconds):"
85 cat $default_args_file
86 sleep 5
87 fi
88
89 targets=()
90 early=false
91 cron=false
92 fast=false
93 kd_spread_maybe=false
94 orig_args=("$@")
95 temp=$(getopt -l cron,fast,pull-reexec,help 23cefikl:m:npqrs:t:vh "$@") || usage 1
96 eval set -- "$temp"
97 while true; do
98 case $1 in
99 # some behaviors specific to running under cron:
100 # - skip hosts where xprintidle haven't been idle recently
101 # - if we can't ssh to 1 or more hosts, still do the rest
102 # - if we aren't MAIL_HOST and no -m or -s, just exit
103 --cron)
104 cron=true
105 pre=
106 ;;
107 # for the rare case we want to run multiple instances at the same time
108 -2) conf_suf=2 ;;
109 -3) conf_suf=3 ;;
110 # only creates the config file, does not run btrbk
111 -c) conf_only=true ;;
112 # quit early, just btrbk, no extra remounting etc.
113 -e) early=true ;;
114 -f) force=true ;;
115 # skip various checks. when we run twice in a row for
116 # switch mail-host, no need to repeat the same checks again.
117 --fast) fast=true ;;
118 -i) incremental_strict=true ;;
119 # note this implies resume
120 -k) kd_spread_maybe=true ;;
121 # bytes per second, suffix k m g
122 -l) rate_limit=$2; shift ;;
123 # Comma separated mountpoints to backup. This has defaults set below.
124 -m) IFS=, mountpoints=($2); unset IFS; shift ;;
125 -n) dry_run=true ;;
126 # hide progress
127 -p) progress_arg= ;;
128 # internal option for rerunning under newer SOURCE_HOST version.
129 --pull-reexec) pull_reexec=true;;
130 # quiet
131 -q) verbose=false; verbose_arg=; progress_arg= ;;
132 # source host to receive a backup from
133 -s)
134 source=$2
135 bbksource=$source
136 if [[ $source == *:* ]]; then
137 bbksource="[$source]"
138 fi
139 shift
140 ;;
141 # target hosts to send to. empty is valid for just doing local
142 # snapshot. we have default hosts we will populate.
143 -t) IFS=, targets=($2); unset IFS; shift ;;
144 # verbose.
145 -v) verbose=true; verbose_arg=-v ;;
146 -h|--help) usage ;;
147 --) shift; break ;;
148 *) die "Internal error!" ;;
149 esac
150 shift
151 done
152
153 cmd_arg="$1"
154
155 if $kd_spread_maybe; then
156 if [[ $cmd_arg && $cmd_arg != resume ]]; then
157 die "dont pass -k without resume or empty run arg"
158 fi
159 cmd_arg=resume
160 fi
161
162 if [[ ! $cmd_arg ]]; then
163 cmd_arg=run
164 fi
165
166
167 std_preserve="36h 14d 8w 24m"
168 q_preserve="18h 14d 8w"
169
170 case $cmd_arg in
171 run|resume) : ;;
172
173 # This works better than the normal archive command. We have to
174 # specify the mount points, but that is what we are used to doing and
175 # we prefer it. Another difference is that archive works recursively
176 # and we don't care about that. Sometimes we may still want to run
177 # btrbk archive, but it doesn't even use the config file, so just
178 # run it directly, eg:
179 # time s btrbk -v archive /mnt/r7/amy/boot/btrbk ssh://bo/mnt/boot2/btrbk
180 archive)
181 cmd_arg=resume
182 std_preserve="999h 999d 999w 999m"
183 q_preserve="$std_preserve"
184 preserve_arg=-p
185 ;;
186 *) die "untested command arg" ;;
187 esac
188
189 if (( $# > 1 )); then
190 die: "only 1 nonoption arg is supported"
191 fi
192
193 if [[ -v targets && $source ]]; then
194 # note, this doesnt need to be the case, but
195 # we would need to think about it.
196 die "error: -t and -s are mutually exclusive"
197 fi
198
199 ### end options parsing
200
201 # remove path from earlier version of btrbk
202 rm -f /usr/sbin/btrbk
203 # note, this still works as intended if there is no /usr/bin/btrbk
204 if [[ /a/opt/btrbk/btrbk -nt /usr/bin/btrbk ]]; then
205 if [[ -e /b/distro-functions/src/package-manager-abstractions ]]; then
206 . /b/distro-functions/src/package-manager-abstractions
207 pi asciidoctor
208 fi
209 cd /a/opt/btrbk
210 m make install
211 fi
212
213 # TODO: i wonder if there should be an option to send to the default
214 # targets, plus any given on the command line.
215
216
217
218 kd_spread=false
219 if ! $cron && $kd_spread_maybe; then
220 kd_spread=true
221 fi
222 # set default targets
223 if [[ ! -v targets && ! $source ]]; then
224 if $cron; then
225 if [[ $HOSTNAME != "$MAIL_HOST" ]]; then
226 if $kd_spread_maybe && [[ $HOSTNAME == kd && $MAIL_HOST == x3 ]]; then
227 if ping -q -c1 -w1 x3.office.fsf.org &>/dev/null; then
228 work_host=x3.office.fsf.org
229 elif ping -q -c1 -w1 x3wg.b8.nz &>/dev/null; then
230 work_host=x3wg.b8.nz
231 fi
232 if [[ $work_host ]]; then
233 source_state="$(ssh $work_host cat /a/bin/bash_unpublished/source-state)"
234 eval "$source_state"
235 if [[ $MAIL_HOST == x3 ]]; then
236 kd_spread=true
237 else
238 # x3 was the mail host, but it moved to some other machine
239 # without updating us yet.
240 echo "MAIL_HOST=$MAIL_HOST, nothing to do"
241 mexit 0
242 fi
243 else
244 echo "MAIL_HOST=$MAIL_HOST, nothing to do"
245 mexit 0
246 fi
247 else
248 echo "MAIL_HOST=$MAIL_HOST, nothing to do"
249 mexit 0
250 fi
251 fi
252 fi
253
254 at_work=false
255 at_home=false
256
257 case $HOSTNAME in
258 kw|kd|frodo|x2|x3|sy) : ;;
259 *)
260 die "error: no default targets for this host, use -t"
261 ;;
262 esac
263
264 case $HOSTNAME in
265 kw)
266 at_work=true
267 ;;&
268 kd|frodo)
269 at_home=true
270 ;;&
271 x2|x3|sy)
272 if [[ $(dig +short @10.2.0.1 -x 10.2.0.2 2>&1 ||:) == kd.b8.nz. ]] \
273 && ip n show 10.2.0.1 | grep . &>/dev/null; then
274 at_home=true
275 elif ping -q -c1 -w1 hal.office.fsf.org &>/dev/null \
276 && ip n show 192.168.0.26 | grep . &>/dev/null; then
277 at_work=true
278 fi
279 ;;&
280 *)
281 if $at_home; then
282 if ! $kd_spread && [[ $HOSTNAME != x3 ]]; then
283 # main work machine
284 if ping -q -c1 -w1 x3.office.fsf.org &>/dev/null; then
285 targets+=(x3.office.fsf.org)
286 elif ping -q -c1 -w1 $h.b8.nz &>/dev/null; then
287 # in case we took it home
288 targets+=(x3.b8.nz)
289 else
290 targets+=(x3wg.b8.nz)
291 fi
292 fi
293 # temporarily disabled while doing recovery
294 # for h in frodo kd; do
295 for h in kd; do
296 if [[ $HOSTNAME == "$h" ]]; then
297 continue
298 fi
299 targets+=($h.b8.nz)
300 done
301 for h in x2 sy; do
302 if [[ $HOSTNAME == "$h" ]]; then
303 continue
304 fi
305 if ping -q -c1 -w1 $h.b8.nz &>/dev/null; then
306 targets+=($h.b8.nz)
307 elif ping -q -c1 -w1 ${h}w.b8.nz &>/dev/null; then
308 targets+=(${h}w.b8.nz)
309 fi
310 done
311 elif $at_work; then
312 targets+=(i.b8.nz)
313 for h in x2 x3 kw; do
314 if [[ $HOSTNAME == "$h" ]]; then
315 continue
316 fi
317 if ping -q -c1 -w1 $h.office.fsf.org &>/dev/null; then
318 targets+=($h.office.fsf.org)
319 fi
320 done
321 else
322 targets+=(i.b8.nz)
323 fi
324 ;;
325 esac
326 fi
327
328 if [[ ${mountpoints[0]} ]]; then
329 for mp in ${mountpoints[@]}; do
330 if [[ -e /nocow/btrfs-stale/$mp ]]; then
331 die "error: $mp is stale, mount-latest-subvol first"
332 fi
333 done
334 else
335 # set default mountpoints
336 if [[ ${targets[0]} == tp ]]; then
337 prospective_mps=(/a)
338 else
339 case $HOSTNAME in
340 *)
341 prospective_mps=()
342 if [[ $source ]]; then
343 source_state="$(ssh $source cat /a/bin/bash_unpublished/source-state)"
344 eval "$source_state"
345 source_host="$(ssh $source cat /etc/hostname)"
346 if [[ $source_host == "$MAIL_HOST" ]]; then
347 prospective_mps+=(/o)
348 fi
349 if [[ $source_host == "$HOST2" ]]; then
350 prospective_mps+=(/a /ar /qr /q)
351 fi
352 else
353 if [[ $HOSTNAME == "$MAIL_HOST" ]]; then
354 prospective_mps+=(/o)
355 fi
356 if [[ $HOSTNAME == "$HOST2" ]]; then
357 prospective_mps+=(/a /ar /qr /q)
358 fi
359 if $kd_spread; then
360 prospective_mps=(/a /ar /o /qr /q)
361 fi
362 fi
363 # note: put q last just in case its specific retention options were to
364 # affect other config sections. I havent tested if that is the case.
365 ;;
366 esac
367 fi
368 for mp in ${prospective_mps[@]}; do # default mountpoints to sync
369 if [[ -e /nocow/btrfs-stale/$mp ]]; then
370 e "warning: $mp stale, not adding to default mountpoints"
371 continue
372 fi
373 if awk '{print $2}' /etc/fstab | grep -xF $mp &>/dev/null; then
374 mountpoints+=($mp)
375 fi
376 done
377 fi
378
379 if (( ! ${#mountpoints[@]} )); then
380 die didnt get mountpoint arg and had no defaults
381 fi
382
383 ##### end command line parsing ########
384
385 #### begin pre-checks #####
386
387 # todo: this has a timing problem, since btrbk.timer could activate the service after this check.
388 if ! $fast && [[ $source ]]; then
389 if [[ $(ssh $source ps --no-headers -o comm 1) == systemd ]]; then
390 status=$(ssh $source systemctl is-active btrbk.service) || : # normally returns 3
391 case $status in
392 inactive|failed) : ;;
393 *)
394 echo "$0: error: cron btrbk is running on source. exiting out of caution"
395 mexit 1
396 esac
397 fi
398 fi
399
400 if ! command -v btrbk &>/dev/null; then
401 die "error: no btrbk binary found"
402 fi
403
404
405 #### end pre-checks #####
406
407 mkdir -p /var/log/btrbk
408 # The journal doesnt go back to my oldest backups, and I've found myself
409 # wanting older logs. Not going to bother expiring old logs, since it is
410 # fine if they go back years.
411 log_path=/var/log/btrbk/$(date +%F_%T%:::z).log
412 echo copying output to $log_path
413 exec &> >(ts "%F %T" | tee -a $log_path)
414
415
416 if $verbose; then
417 printf "$pre options: conf_only=%s\ndry_run=%s\nrate_limit=%s\nverbose=%s\ncmd_arg=%s" "$conf_only" "$dry_run" "$rate_limit" "$verbose" "$cmd_arg"
418 fi
419
420 if [[ -v targets ]]; then
421 echo "targets: ${targets[*]}"
422 fi
423
424 if [[ $source ]]; then
425 echo "source: $source"
426 fi
427
428 echo "mountpoints: ${mountpoints[*]}"
429
430
431
432 # pull_reexec stops us from getting into an infinite loop if there is some
433 # kind of weird problem
434 pulla=false
435 for m in "${mountpoints[@]}"; do
436 if [[ $m == /a ]]; then
437 pulla=true
438 break
439 fi
440 done
441 if ! $pull_reexec && [[ $source ]] && $pulla ; then
442 tmpf=$(mktemp)
443 m rsync -ra $source:/usr/local/bin/{mount-latest-subvol,check-subvol-stale} /usr/local/bin
444 m rsync -ra $source:/usr/local/lib/err /usr/local/lib
445 m scp $source:/a/bin/distro-setup/btrbk-run $tmpf
446 if ! diff -q $tmpf ${BASH_SOURCE[0]}; then
447 e "found different version on host $source. reexecing"
448 install -T $tmpf /usr/local/bin/btrbk-run
449 m /usr/local/bin/btrbk-run --pull-reexec "${orig_args[@]}"
450 mexit 0
451 fi
452 fi
453
454
455 if ! $fast; then
456 # if our mountpoints are from stale snapshots,
457 # it doesn't make sense to do a backup.
458 m check-subvol-stale ${mountpoints[@]} || die "found stale mountpoints in ${mountpoints[*]}"
459
460 # for an initial run, btrbk requires the dir to exist.
461 mkdir -p /mnt/{root,o}/btrbk
462 fi
463 local_zone=$(date +%z)
464
465 if [[ $source ]]; then
466 if $fast; then
467 zone=$local_zone
468 else
469 if ! zone=$(ssh root@$source date +%z); then
470 if $conf_only; then
471 echo "$0: warning: failed to ssh to root@$source"
472 else
473 die failed to ssh to root@$source
474 fi
475 fi
476 if [[ $zone != "$local_zone" ]]; then
477 die "error: dont confuse yourself with multiple time zones. $h has different timezone than localhost"
478 fi
479 fi
480 else
481
482 sshable=()
483 sshfail=()
484 for h in ${targets[@]}; do
485 if $fast || $conf_only; then
486 # Use some typical values in this case
487 root_size=$(( 1024 * 1024 * 2000 )) #2tb
488 percent_used=10
489 zone=$(date +%z)
490 elif remote_str=$(timeout -s 9 6 ssh root@$h "mkdir -p /mnt/root/btrbk /mnt/o/btrbk && date +%z && df --output=size,pcent / | tail -n1"); then
491 mapfile -t tmp_array <<<"$remote_str"
492 zone="${tmp_array[0]}"
493 IFS=" " read -r root_size percent_used <<<"${tmp_array[1]}"
494 percent_used=${percent_used%%%}
495
496 if (( ${#tmp_array[@]} != 2 )); then
497 die "error: didnt get 2 lines in test ssh to target $h. investigate"
498 fi
499 case $percent_used in
500 [0-9]|[1-9][0-9]) : ;;
501 *)
502 die "error: didnt get percent disk use in test ssh to target $h. investigate"
503 ;;
504 esac
505 else
506 sshfail+=($h)
507 continue
508 fi
509
510 # we may be booted into a bootstrap fs or something
511 min_root_kb=$(( 1024 * 1024 * 200 )) # 200 gb
512 if (( root_size < min_root_kb )); then
513 continue
514 fi
515
516 if (( percent_used >= 98 )); then
517 die "error: filesystem on target $h is $percent_used % full"
518 fi
519
520 # on sy, xprintidle is resetting every 12 seconds even when not
521 # idle, i dunno why, instead we are checking if the screen is locked,
522 # which is good enough.
523 #
524 # This is a separate ssh because the command can fail and thatis ok.
525 if $cron && ! $force; then
526 locked=false
527 if lock_info=$(timeout -s 9 6 ssh $h DISPLAY=:0 xscreensaver-command -time); then
528 if [[ $lock_info != *non-blanked* ]]; then
529 locked=true
530 fi
531 else
532 locked=true
533 fi
534 if ! $locked; then
535 # Ignore this host. i sometimes use a non-main machine for
536 # testing or web browsing, knowing that everything will be wiped
537 # by the next backup, but I dont want it to happen as Im using
538 # it from cronjob.
539 e "warning: $h: seems to be actively in use, skipping for now"
540 continue
541 fi
542 fi
543 sshable+=($h)
544 if [[ $zone != "$local_zone" ]]; then
545 die "error: dont confuse yourself with multiple time zones. $h has different timezone than localhost"
546 fi
547 done
548 if [[ ! ${sshable[*]} ]] || { ! $cron && [[ ${sshfail[*]} ]]; }; then
549 die "failed to ssh to hosts: ${sshfail[*]}"
550 else
551 if [[ ${sshfail[*]} ]]; then
552 ret=1
553 e "error: failed to ssh to ${sshfail[*]} but continuing with other hosts"
554 fi
555 targets=(${sshable[@]})
556 fi
557 fi
558
559
560 cat >/etc/btrbk$conf_suf.conf <<EOF
561 ssh_identity /q/root/h
562 #ssh_identity /root/.ssh/home
563
564 # Just a guess that local7 is a good facility to pick.
565 # It's a bit odd that the transaction log has to be logged to
566 # a file or syslog, while other output is sent to std out.
567 # The man does not mention a way for them to be together, but
568 # I dunno if setting a log level like warn might also output
569 # transaction info.
570 transaction_syslog local7
571
572 # trying this out
573 #stream_compress zstd
574
575 # so we only run one at a time
576 lockfile /var/lock/btrbk$conf_suf.lock
577
578 # default format of short does not accomidate hourly preservation setting
579 timestamp_format long-iso
580
581 # only make a snapshot if things have changed
582 snapshot_create onchange
583 # I could make this different from target_preserve,
584 # if one disk had less space.
585 # for now, keeping them equal.
586 snapshot_preserve $std_preserve
587 snapshot_preserve_min 6h
588 snapshot_dir btrbk
589 # so, total backups = ~58
590 target_preserve $std_preserve
591 target_preserve_min 6h
592
593 # i tried this when investigating: clone no source subvolume found error
594 #incremental_prefs sro:1 srn:1 sao san:1 aro:1 arn:1
595
596 # if something fails and it's not obvious, try doing
597 # btrbk -l debug -v dryrun
598
599 rate_limit $rate_limit
600 EOF
601
602 if $incremental_strict; then
603 cat >>/etc/btrbk$conf_suf.conf <<EOF
604 incremental strict
605 EOF
606 fi
607
608 qconf() {
609 case $sub in
610 q)
611 # q has sensitive data i dont want to backup for so long
612 cat >>/etc/btrbk$conf_suf.conf <<EOF
613 snapshot_preserve $q_preserve
614 snapshot_preserve_min 2h
615 snapshot_dir btrbk
616 target_preserve $q_preserve
617 target_preserve_min 2h
618 EOF
619 ;;
620 esac
621
622 }
623
624 # make /q be last
625 mp_count=${#mountpoints[@]}
626 for (( i=0; i < mp_count - 1 ; i++ )); do
627 if [[ ${mountpoints[i]} == /q ]]; then
628 unset "mountpoints[i]"
629 mountpoints+=(/q)
630 fi
631 done
632
633 for m in ${mountpoints[@]}; do
634 case $m in
635 /o)
636 vol=/mnt/o
637 ;;
638 *)
639 vol=/mnt/root
640 ;;
641 esac
642
643 sub=${m#/}
644 if [[ $source ]]; then
645 cat >>/etc/btrbk$conf_suf.conf <<EOF
646 volume ssh://$bbksource$vol
647 subvolume $sub
648 EOF
649 qconf
650 cat >>/etc/btrbk$conf_suf.conf <<EOF
651 target send-receive $vol/btrbk
652 EOF
653 fi
654 if (( ${#targets[@]} )); then
655 cat >>/etc/btrbk$conf_suf.conf <<EOF
656 volume $vol
657 subvolume $sub
658 EOF
659 qconf
660 for tg in ${targets[@]}; do
661 # handle ipv6
662 if [[ $tg == *:* ]]; then
663 tg="[$tg]"
664 fi
665 cat >>/etc/btrbk$conf_suf.conf <<EOF
666 target send-receive ssh://$tg$vol/btrbk
667 EOF
668 done
669 fi
670 done
671
672 # todo: umount first to ensure we don't have any errors
673 # todo: do some kill fuser stuff to make umount more reliable
674
675
676 if $conf_only; then
677 mexit 0
678 fi
679
680
681
682 if $dry_run; then
683 m btrbk -c /etc/btrbk$conf_suf.conf -v -n $cmd_arg
684 mexit 0
685 fi
686 # -q and just using the syslog option seemed nice,
687 # but it doesn't show when a send has a parent and when it doesn't.
688 m btrbk -c /etc/btrbk$conf_suf.conf $preserve_arg $verbose_arg $progress_arg $cmd_arg
689
690 if $early; then
691 exit 0
692 fi
693
694 # todo: tp not valid anymore.
695 # if we have it, sync to systems which don't
696 if mountpoint $rsync_mountpoint >/dev/null; then
697 for tg in ${targets[@]}; do
698 case $tg in
699 tp)
700 dirs=(/p/c/machine_specific/tp)
701 for x in /p/c/machine_specific/*.hosts; do
702 if grep -qxF $tg $x; then
703 dirs+=(${x%.hosts})
704 fi
705 done
706 m rsync -aSAXPH --specials --devices --delete --relative ${dirs[@]} root@$tg:/
707 ;;
708 esac
709 done
710 fi
711
712 subvols=()
713 for mp in "${mountpoints[@]}"; do
714 subvols+=("${mp##*/}")
715 done
716 if [[ $source ]]; then
717 m mount-latest-subvol "${subvols[@]}"
718 else
719 for tg in ${targets[@]}; do
720 m /a/exe/mount-latest-remote "$tg" "${subvols[@]}" || ret=$?
721 done
722 fi
723
724 if [[ $ret == 0 ]]; then
725 for tg in ${targets[@]}; do
726 h=$(ssh $tg hostname)
727 rsync -a -f"- */" -f"+ *" /var/log/btrbk/ root@$tg:/var/log/btrbk/$tg
728 ssh root@$tg /usr/local/bin/mail-backup-clean
729 done
730 if [[ $source ]]; then
731 rsync -a -f"- */" -f"+ *" $source:/var/log/btrbk/ /var/log/btrbk/$source
732 fi
733 fi
734
735 mexit $ret
736
737 # todo: move variable data we don't care about backing up
738 # to /nocow and symlink it.
739
740
741 # background on btrbk timezones. with short/long, timestamps use local time.
742 # for long, if your local time moves backwards, by moving timezones or
743 # for an hour when daylight savings changes it, you will temporarily get
744 # a more aggressive retention policy for the overlapping period, and
745 # vice versa for the opposite timezone move. The alternative is using
746 # long-iso, which puts timezone info into the timestamp, which means
747 # that instead of shifting time, you shift the start of day/week/month
748 # which is used for retention to your new local time, which means for
749 # example, if you moved forward by 8 hours, the daily/weekly/monthly
750 # retention will be 8 hours more aggressive since midnight is at a new
751 # time, unless you fake the timzeone using the TZ env variable.
752 # However, in the short term, there will be no inconsistencies.
753 # I don't see any problem with shifting when the day starts for
754 # retention, so I'm using long-iso.
755
756 # note to create a long-iso timestamp: date +%Y%m%dT%H%M%S%z