#!/bin/bash # ============================================================================= # AYON Automated Backup Script # Author: Daniel van Westen # Date: 2026-02-04 # License: MIT - Use at your own risk, no warranty! # ============================================================================= set -euo pipefail # === Configuration === BACKUP_DIR="$HOME/ayon-docker/backups" LOG_DIR="$HOME/ayon-docker/logs" AYON_DIR="$HOME/ayon-docker" LOCKFILE="/tmp/ayon_backup.lock" DATE=$(date +%Y%m%d) TIMESTAMP=$(date +%Y%m%d_%H%M%S) WEEKDAY=$(date +%u) # 1=Monday, 7=Sunday # Retention settings FULL_BACKUP_KEEP=4 # Number of full backups to keep PROJECT_BACKUP_DAYS=14 # Delete project backups older than X days # === Create directories === mkdir -p "$BACKUP_DIR" "$LOG_DIR" # === Logging === LOGFILE="$LOG_DIR/backup_${DATE}.log" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE" } log_error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "$LOGFILE" >&2 } # === Lock mechanism === exec 200>"$LOCKFILE" if ! flock -n 200; then log_error "Backup already running (lock file: $LOCKFILE)" exit 1 fi # === Cleanup on exit === cleanup() { local exit_code=$? if [ $exit_code -ne 0 ]; then log_error "Backup finished with error code $exit_code" fi # Lock is automatically released } trap cleanup EXIT # === Check if container is running === cd "$AYON_DIR" if ! docker compose ps postgres --format '{{.State}}' 2>/dev/null | grep -q running; then log_error "PostgreSQL container is not running!" exit 1 fi log "=== Backup started ===" # === Backup validation === validate_backup() { local file="$1" local min_size="${2:-1024}" # At least 1KB if [ ! -f "$file" ]; then log_error "Backup file does not exist: $file" return 1 fi local size size=$(stat -c%s "$file") if [ "$size" -lt "$min_size" ]; then log_error "Backup too small ($size bytes): $file" return 1 fi log " Validated: $file ($size bytes)" return 0 } # ------------------------------------------------------------ # 1) Full backup: only once per week (Sunday) # ------------------------------------------------------------ if [ "$WEEKDAY" -eq 7 ]; then FULL_BACKUP_FILE="$BACKUP_DIR/${DATE}_full_backup.sql.gz" log "Creating full backup: $FULL_BACKUP_FILE" if docker compose exec -T postgres \ pg_dump -U ayon --no-owner --no-privileges --format=plain ayon \ | gzip > "$FULL_BACKUP_FILE"; then if validate_backup "$FULL_BACKUP_FILE" 100; then log "Full backup successful" else rm -f "$FULL_BACKUP_FILE" log_error "Full backup failed - file deleted" fi else log_error "pg_dump for full backup failed" rm -f "$FULL_BACKUP_FILE" fi # Clean up old full backups log "Cleaning up old full backups (keeping $FULL_BACKUP_KEEP)" find "$BACKUP_DIR" -name "*_full_backup.sql.gz" -type f | sort -r | tail -n +$((FULL_BACKUP_KEEP + 1)) | xargs -r rm -v -- 2>&1 | while read -r line; do log " Deleted: $line"; done fi # ------------------------------------------------------------ # 2) Get project list from database # ------------------------------------------------------------ log "Fetching project list from database" PROJECTS=$(docker compose exec -T postgres psql -U ayon -d ayon -t -c "SELECT name FROM projects;" | tr -d ' ' | grep -v '^$') if [ -z "$PROJECTS" ]; then log "No projects found - skipping project backups" else PROJECT_COUNT=$(echo "$PROJECTS" | wc -l) log "Found: $PROJECT_COUNT project(s)" # ------------------------------------------------------------ # 3) Daily backup for each project # ------------------------------------------------------------ for PROJECT in $PROJECTS; do log "Backup for project: $PROJECT" if make dump projectname="$PROJECT" >> "$LOGFILE" 2>&1; then # Find dump file DUMP_FILE="dump.${PROJECT}.sql" if [ -f "$DUMP_FILE" ]; then TARGET_FILE="$BACKUP_DIR/${DATE}_${PROJECT}.sql.gz" # Compress and move if gzip -c "$DUMP_FILE" > "$TARGET_FILE"; then rm "$DUMP_FILE" if validate_backup "$TARGET_FILE" 100; then log " -> saved: $TARGET_FILE" else rm -f "$TARGET_FILE" fi else log_error " Compression failed for $PROJECT" rm -f "$DUMP_FILE" "$TARGET_FILE" fi else log_error " No dump file found: $DUMP_FILE" fi else log_error " make dump failed for $PROJECT" fi done # ------------------------------------------------------------ # 4) Clean up old project backups # ------------------------------------------------------------ log "Cleaning up project backups older than $PROJECT_BACKUP_DAYS days" find "$BACKUP_DIR" -name "${DATE}_*.sql.gz" -prune -o -name "*_*.sql.gz" ! -name "*_full_backup.sql.gz" -type f -mtime +$PROJECT_BACKUP_DAYS -print | xargs -r rm -v -- 2>&1 | while read -r line; do log " Deleted: $line"; done fi log "=== Backup completed ==="