#!/usr/bin/env bash # Pull encrypted backups from production — managed by Ansible (roles/backup_store) # # Everything here is already ciphertext: this host only moves and expires files, # and holds no key that can read them. set -uo pipefail # deliberately NOT -e; see the loop below SSH_KEY="{{ backup_store_ssh_key }}" STORE="{{ backup_store_dir }}" log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*"; } fail() { echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: $*" >&2; failures=$((failures + 1)); } failures=0 # One source failing must not stop the others. The whole point of this box is # that a single dead host cannot silently take the rest of the backups with it — # which is exactly how the laptop-based jobs failed unnoticed for nine months. {% for src in backup_store_sources %} # --- {{ src.name }} --- pull_{{ src.name | replace('-', '_') }}() { local dir="${STORE}/{{ src.name }}" mkdir -p "$dir" log "Pulling {{ src.name }} from {{ src.source }}..." if rsync -az --timeout=120 \ -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15" \ "{{ src.source }}" "$dir/"; then log " {{ src.name }}: ok ($(find "$dir" -maxdepth 1 -type f | wc -l) artefacts, $(du -sh "$dir" | cut -f1))" else fail "{{ src.name }}: rsync failed" return 1 fi log " {{ src.name }}: pruning older than {{ src.retention_days }} days" find "$dir" -maxdepth 1 -type f -name '{{ src.name }}_*' -mtime +{{ src.retention_days }} -delete } pull_{{ src.name | replace('-', '_') }} || true {% endfor %} if [ "$failures" -gt 0 ]; then log "FAILED: $failures source(s) did not pull" exit 1 fi log "All sources pulled."