#!/bin/bash # {{ ansible_managed }} # Pull application data from the cloud hosts into hardlinked snapshots # rsnapshot-style: rsync --link-dest set -uo pipefail ROOT={{ backup_root }} KEY={{ backup_key_path }} RETAIN={{ backup_retain_recent | default(14) }} RETAIN_MONTHLY={{ backup_retain_monthly | default(6) }} SSH="ssh -i $KEY -o StrictHostKeyChecking=accept-new -o BatchMode=yes" STAMP=$(date +%Y-%m-%d_%H%M) exec 9> "$ROOT/.lock" || exit 1 flock -n 9 || { echo "another backup run is active, aborting"; exit 1; } overall_rc=0 {% raw %} prune() { local host_root=$1 local -a snaps mapfile -t snaps < <(find "$host_root" -mindepth 1 -maxdepth 1 -type d -name '20*' | sort) ((${#snaps[@]})) || return 0 declare -A keep_set month_first # newest RETAIN snapshots local n=${#snaps[@]} start=0 ((n > RETAIN)) && start=$((n - RETAIN)) for s in "${snaps[@]:$start}"; do keep_set[$s]=1; done # oldest snapshot of each month, for the RETAIN_MONTHLY most recent months for s in "${snaps[@]}"; do local m; m=$(basename "$s" | cut -c1-7) [[ -z ${month_first[$m]:-} ]] && month_first[$m]=$s done mapfile -t months < <(printf '%s\n' "${!month_first[@]}" | sort | tail -n "$RETAIN_MONTHLY") for m in "${months[@]}"; do keep_set[${month_first[$m]}]=1; done for s in "${snaps[@]}"; do if [[ -z ${keep_set[$s]:-} ]]; then echo "pruning $s" rm -rf "$s" fi done } {% endraw %} {% for host in groups['backup_sources'] %} ##### {{ host }} ##### host={{ host }} addr={{ hostvars[host].ansible_host }} host_root=$ROOT/$host new=$host_root/incomplete mkdir -p "$host_root" rm -rf "$new" mkdir "$new" link_dest=() [[ -d $host_root/latest/. ]] && link_dest=(--link-dest="$host_root/latest/") host_rc=0 {% for path in hostvars[host].backup_paths %} rsync -aR --numeric-ids --delete --delete-excluded \ {% for ex in hostvars[host].backup_excludes | default([]) %}--exclude='{{ ex }}' {% endfor %}\ -e "$SSH" "${link_dest[@]}" \ "backup@$addr:{{ path }}" "$new/" rc=$? if ((rc != 0 && rc != 24)); then echo "ERROR: $host:{{ path }} failed with rsync exit $rc" >&2 host_rc=1 fi {% endfor %} if ((host_rc == 0)); then mv "$new" "$host_root/$STAMP" ln -sfn "$STAMP" "$host_root/latest" prune "$host_root" echo "$host: snapshot $STAMP complete" else echo "$host: snapshot FAILED, keeping previous 'latest'" >&2 overall_rc=1 fi {% endfor %} exit $overall_rc