#!/bin/bash # restic-backup.sh # Full restic backup to AWS S3 with logging, retention policy, # integrity check, and Telegram notification on failure. # # Setup: create /etc/restic/s3-credentials.conf with: # export AWS_ACCESS_KEY_ID=... # export AWS_SECRET_ACCESS_KEY=... # export RESTIC_REPOSITORY=s3:s3.amazonaws.com/your-bucket # export RESTIC_PASSWORD=your-encryption-password # # Usage: sudo bash restic-backup.sh # Timer: see /guides/aws-s3-homelab-backup for systemd timer setup set -euo pipefail CREDENTIALS_FILE="${CREDENTIALS_FILE:-/etc/restic/s3-credentials.conf}" LOG_FILE="${LOG_FILE:-/var/log/restic-backup.log}" TELEGRAM_TOKEN="${TELEGRAM_TOKEN:-}" TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}" # Paths to back up — edit this list BACKUP_PATHS=( "/var/lib/docker/volumes" "/home/${SUDO_USER:-$USER}/homelab/config" "/home/${SUDO_USER:-$USER}/homelab-ansible" "/etc/crontab" "/etc/systemd/system" ) # Paths to exclude EXCLUDES=( "*/cache/*" "*/tmp/*" "*/.Trash/*" "*/node_modules/*" ) # Retention policy KEEP_DAILY=7 KEEP_WEEKLY=4 KEEP_MONTHLY=6 KEEP_YEARLY=2 # ── Helpers ────────────────────────────────────────────────────── log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; } send_telegram() { local msg="$1" [[ -z "$TELEGRAM_TOKEN" || -z "$TELEGRAM_CHAT_ID" ]] && return 0 curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \ -d "chat_id=${TELEGRAM_CHAT_ID}" \ -d "parse_mode=Markdown" \ -d "text=${msg}" > /dev/null || true } # ── Pre-flight checks ──────────────────────────────────────────── if [[ ! -f "$CREDENTIALS_FILE" ]]; then log "ERROR: credentials file not found: $CREDENTIALS_FILE" exit 1 fi # shellcheck source=/dev/null source "$CREDENTIALS_FILE" if ! command -v restic &>/dev/null; then log "ERROR: restic not installed. Run: sudo apt install restic -y" exit 1 fi # ── Run backup ─────────────────────────────────────────────────── log "=== Backup started ===" exclude_args=() for excl in "${EXCLUDES[@]}"; do exclude_args+=(--exclude "$excl") done if restic backup \ "${BACKUP_PATHS[@]}" \ "${exclude_args[@]}" \ --tag "$(hostname)" \ --json \ >> "$LOG_FILE" 2>&1; then log "Backup completed successfully" else log "ERROR: backup failed" send_telegram "❌ *Backup FAILED* on \`$(hostname)\`\nCheck: \`journalctl -u restic-backup\`" exit 1 fi # ── Prune old snapshots ────────────────────────────────────────── log "Pruning snapshots..." restic forget \ --keep-daily "$KEEP_DAILY" \ --keep-weekly "$KEEP_WEEKLY" \ --keep-monthly "$KEEP_MONTHLY" \ --keep-yearly "$KEEP_YEARLY" \ --prune \ >> "$LOG_FILE" 2>&1 # ── Integrity check (10% sample) ───────────────────────────────── log "Running integrity check (10% sample)..." if ! restic check --read-data-subset=10% >> "$LOG_FILE" 2>&1; then log "WARNING: integrity check found issues" send_telegram "⚠️ *Backup integrity check failed* on \`$(hostname)\`\nManual inspection required." fi # ── Report ─────────────────────────────────────────────────────── snapshot_count=$(restic snapshots --compact 2>/dev/null | grep -c "^[a-f0-9]" || echo "?") log "=== Backup complete — ${snapshot_count} snapshots in repository ===" send_telegram "✅ *Backup complete* — \`$(hostname)\`\n${snapshot_count} snapshots in S3"