#!/bin/bash # ============================================================================= # Borg Backup Script - MariaDB Restart Guaranteed # ============================================================================= set -euo pipefail # ========================= CONFIGURATION ========================= NAME="borg-2025" REPO="/home/srv/files/backups/$NAME" TARGET="/home/srv/files/content" LOGDIR="/var/log/borg" LOGFILE="${LOGDIR}/backup-$(date +%Y-%m-%d-%H%M%S).log" # Optional but strongly recommended HEALTHCHECK_URL="https://hc-ping.com/your-uuid-here" RCLONE_REMOTE="scaleway" # ================================================================= mkdir -p "$LOGDIR" exec > >(tee -a "$LOGFILE") exec 2>&1 echo "=== Backup started at $(date '+%Y-%m-%d %H:%M:%S') ===" echo "Repository: $REPO" echo "Target: $TARGET" echo "Log: $LOGFILE" echo "-----------------------------------------------------------" # ----------------------- Lockfile ----------------------- LOCKFILE="/var/lock/borg-backup.lock" MARIADB_STOPPED=false cleanup() { local exit_code=${1:-$?} if [ "$MARIADB_STOPPED" = true ]; then echo "=== Cleanup: Starting MariaDB container ===" docker start mariadb || echo "WARNING: Failed to start mariadb container" sleep 3 echo "MariaDB restart completed." fi rm -f "$LOCKFILE" if [ "$exit_code" -eq 0 ]; then echo "=== Backup completed SUCCESSFULLY at $(date '+%Y-%m-%d %H:%M:%S') ===" send_healthcheck else echo "=== Backup FAILED at $(date '+%Y-%m-%d %H:%M:%S') (exit code $exit_code) ===" send_healthcheck "fail" fi echo "Full log: $LOGFILE" } if [ -e "$LOCKFILE" ]; then echo "ERROR: Another backup is already running (lockfile exists)" echo "Remove it manually if stale: $LOCKFILE" exit 1 fi touch "$LOCKFILE" trap 'cleanup $?' EXIT # ----------------------- Borg & Functions ----------------------- export BORG_REPO="$REPO" export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes send_healthcheck() { local status="${1:-}" if [[ -n "${HEALTHCHECK_URL:-}" ]] && [[ "$HEALTHCHECK_URL" != *"your-uuid-here"* ]]; then case "$status" in start) curl -s -m 10 --retry 3 "${HEALTHCHECK_URL}/start" >/dev/null || true ;; fail) curl -s -m 10 --retry 3 "${HEALTHCHECK_URL}/fail" -d "Backup failed - check $LOGFILE" >/dev/null || true ;; *) curl -s -m 10 --retry 3 "$HEALTHCHECK_URL" >/dev/null || true ;; esac fi } run_cmd() { echo "[RUN] $*" "$@" } # ================================================================= send_healthcheck "start" # 1. MariaDB dump (while running) echo "=== Step 1: MariaDB dump ===" DUMP_SCRIPT="${TARGET}/mariadb/dump_db.sh" if [[ -x "$DUMP_SCRIPT" ]]; then run_cmd "$DUMP_SCRIPT" else echo "WARNING: Dump script not found or not executable: $DUMP_SCRIPT" fi # 2. Stop MariaDB for consistent backup echo "=== Step 2: Stopping MariaDB container ===" run_cmd docker stop mariadb MARIADB_STOPPED=true # 3. Create Borg archive echo "=== Step 3: Creating Borg archive ===" run_cmd borg create \ --stats \ --progress \ --list \ --filter=AME \ --compression zstd,8 \ --exclude-caches \ --exclude-if-present .nobackup \ "::${now:%Y-%m-%dT%H-%M-%S}" \ "$TARGET" # 4. Restart MariaDB (this line is now also in the cleanup trap) echo "=== Step 4: Starting MariaDB container ===" run_cmd docker start mariadb MARIADB_STOPPED=false # 5. Offsite sync echo "=== Step 5: Syncing to Scaleway S3 ===" run_cmd rclone sync -v \ --fast-list \ --transfers=8 \ --checkers=16 \ "$REPO" "${RCLONE_REMOTE}:/par-backup-1/$NAME" # 6. Prune & Compact echo "=== Step 6: Pruning and compacting ===" run_cmd borg prune \ --list \ --keep-daily=7 \ --keep-weekly=4 \ --keep-monthly=6 \ --keep-within=7d \ "$REPO" run_cmd borg compact --progress "$REPO" # The cleanup trap will run automatically and mark success