#!/bin/bash set -euo pipefail # --------------------------------------------------------------------------- # restic-backup.sh — generic backup script # Deployed to: /usr/local/bin/restic-backup.sh # # All machine-specific config lives in /etc/restic/env and # /etc/restic/excludes.txt — this script never needs editing. # --------------------------------------------------------------------------- log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } # Load environment — set -a auto-exports all variables to child processes set -a && source /etc/restic/env && set +a # --------------------------------------------------------------------------- # Server reachability check with retry # --------------------------------------------------------------------------- BACKUP_HOST="${BACKUP_SERVER%%:*}" BACKUP_PORT="${BACKUP_SERVER##*:}" MAX_RETRIES=3 RETRY_MIN=600 # 10 min RETRY_MAX=1800 # 30 min server_reachable() { # Dependency-free TCP check using bash built-ins (echo >/dev/tcp/"${BACKUP_HOST}"/"${BACKUP_PORT}") 2>/dev/null } wait_for_server() { local attempt=1 while ! server_reachable; do if [ "$attempt" -ge "$MAX_RETRIES" ]; then log "ERROR: Backup server ${BACKUP_SERVER} unreachable after ${MAX_RETRIES} attempts. Giving up." exit 1 fi local delay=$(( RANDOM % (RETRY_MAX - RETRY_MIN + 1) + RETRY_MIN )) log "Backup server unreachable (attempt ${attempt}/${MAX_RETRIES}). Retrying in $(( delay / 60 )) min..." sleep "$delay" (( attempt++ )) done log "Backup server reachable." } log "=== Backup started on $(hostname) (${MACHINE_NAME}) ===" wait_for_server # --------------------------------------------------------------------------- # Pre-backup hooks — dump system state so it's included in the snapshot # --------------------------------------------------------------------------- # Debian / Ubuntu if command -v dpkg &>/dev/null; then log "Dumping installed packages (dpkg)..." dpkg --get-selections > /etc/backup-package-list.txt # RHEL / Fedora / CentOS elif command -v rpm &>/dev/null; then log "Dumping installed packages (rpm)..." rpm -qa > /etc/backup-package-list.txt # Arch Linux elif command -v pacman &>/dev/null; then log "Dumping installed packages (pacman)..." pacman -Qqe > /etc/backup-package-list.txt fi # --------------------------------------------------------------------------- # Backup # --------------------------------------------------------------------------- log "Running restic backup..." # Word-split BACKUP_PATHS intentionally — each path is a separate argument # shellcheck disable=SC2086 restic backup \ $BACKUP_PATHS \ --exclude-file /etc/restic/excludes.txt \ --one-file-system \ --verbose # --------------------------------------------------------------------------- # Retention # --------------------------------------------------------------------------- log "Applying retention policy (daily=${KEEP_DAILY} weekly=${KEEP_WEEKLY} monthly=${KEEP_MONTHLY})..." restic forget \ --keep-daily "${KEEP_DAILY}" \ --keep-weekly "${KEEP_WEEKLY}" \ --keep-monthly "${KEEP_MONTHLY}" \ --prune log "=== Backup finished on $(hostname) (${MACHINE_NAME}) ==="