#!/bin/bash # # PostgreSQL Backup Script for Ferrero Automation # Supports both daily (pg_dump) and weekly (pg_basebackup) backups # Compatible with Docker containers # # Usage: # ./database/backup.sh --daily # Daily SQL dump backup # ./database/backup.sh --weekly # Weekly full binary backup # set -e # Exit on error # Configuration DB_NAME="ferrero_tracking" DB_USER="ferrero_user" DB_PASSWORD="ferrero_pass_2025" DB_HOST="localhost" DB_PORT="5437" DOCKER_CONTAINER="ferrero-tracking-db" # Directories SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" BACKUP_BASE_DIR="$PROJECT_DIR/backups" DUMP_DIR="$BACKUP_BASE_DIR/dumps" BASEBACKUP_DIR="$BACKUP_BASE_DIR/basebackups" LOG_DIR="$PROJECT_DIR/logs" # Retention DAILY_RETENTION_DAYS=7 WEEKLY_RETENTION_WEEKS=1 # Timestamp TIMESTAMP=$(date +"%Y-%m-%d_%H-%M") DATE_ONLY=$(date +"%Y-%m-%d") # Colors for output GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' # No Color # Logging function log() { echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_DIR/backup.log" } log_error() { echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" >&2 echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$LOG_DIR/backup.log" } log_warning() { echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1" echo "[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1" >> "$LOG_DIR/backup.log" } # Create directories mkdir -p "$DUMP_DIR" "$BASEBACKUP_DIR" "$LOG_DIR" # Daily backup (pg_dump) backup_daily() { log "Starting daily backup (pg_dump)..." BACKUP_FILE="$DUMP_DIR/${DB_NAME}_${TIMESTAMP}.sql.gz" # Check if Docker container is running if ! docker ps | grep -q "$DOCKER_CONTAINER"; then log_error "Docker container $DOCKER_CONTAINER is not running" send_error_email "Daily backup failed: Docker container not running" exit 1 fi # Perform pg_dump via Docker log "Running pg_dump for database: $DB_NAME" if docker exec "$DOCKER_CONTAINER" pg_dump -U "$DB_USER" "$DB_NAME" | gzip > "$BACKUP_FILE"; then BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1) log "✓ Daily backup completed: $BACKUP_FILE ($BACKUP_SIZE)" # Cleanup old backups log "Cleaning up backups older than $DAILY_RETENTION_DAYS days..." find "$DUMP_DIR" -name "*.sql.gz" -type f -mtime +$DAILY_RETENTION_DAYS -delete REMAINING_BACKUPS=$(ls -1 "$DUMP_DIR"/*.sql.gz 2>/dev/null | wc -l) log "Remaining daily backups: $REMAINING_BACKUPS" return 0 else log_error "pg_dump failed" send_error_email "Daily backup failed: pg_dump error" exit 1 fi } # Weekly backup (pg_basebackup) backup_weekly() { log "Starting weekly backup (pg_basebackup)..." BACKUP_DIR="$BASEBACKUP_DIR/ferrero_tracking_${TIMESTAMP}" BACKUP_TAR="$BASEBACKUP_DIR/ferrero_tracking_${TIMESTAMP}.tar.gz" # Check if Docker container is running if ! docker ps | grep -q "$DOCKER_CONTAINER"; then log_error "Docker container $DOCKER_CONTAINER is not running" send_error_email "Weekly backup failed: Docker container not running" exit 1 fi log "Running pg_basebackup..." # Create temp directory for basebackup mkdir -p "$BACKUP_DIR" # Perform pg_basebackup via Docker exec if docker exec "$DOCKER_CONTAINER" pg_basebackup -U "$DB_USER" -D /tmp/backup -Ft -z -P; then # Copy from container to host docker cp "$DOCKER_CONTAINER:/tmp/backup/." "$BACKUP_DIR/" # Create compressed archive tar -czf "$BACKUP_TAR" -C "$BASEBACKUP_DIR" "$(basename "$BACKUP_DIR")" # Remove temp directory rm -rf "$BACKUP_DIR" docker exec "$DOCKER_CONTAINER" rm -rf /tmp/backup BACKUP_SIZE=$(du -h "$BACKUP_TAR" | cut -f1) log "✓ Weekly backup completed: $BACKUP_TAR ($BACKUP_SIZE)" # Cleanup old weekly backups (keep only latest) log "Cleaning up old weekly backups..." ls -t "$BASEBACKUP_DIR"/*.tar.gz | tail -n +2 | xargs -r rm -- REMAINING_BACKUPS=$(ls -1 "$BASEBACKUP_DIR"/*.tar.gz 2>/dev/null | wc -l) log "Remaining weekly backups: $REMAINING_BACKUPS" return 0 else log_error "pg_basebackup failed" rm -rf "$BACKUP_DIR" send_error_email "Weekly backup failed: pg_basebackup error" exit 1 fi } # Send error email notification send_error_email() { local error_message="$1" # Check if Python is available if command -v python3 &> /dev/null; then log "Sending error notification email..." # Use Python to send email via existing notifier python3 << EOF import sys sys.path.insert(0, '$PROJECT_DIR') from scripts.shared.config_loader import load_config from scripts.shared.notifier import Notifier try: config = load_config('$PROJECT_DIR/config/config.yaml') notifier = Notifier(config) notifier.send_email( template_name='backup_failed', recipients=config['notifications']['recipients']['errors'], data={ 'error_message': '$error_message', 'timestamp': '$(date +'%Y-%m-%d %H:%M:%S')', 'server': '$(hostname)' } ) print("Error email sent") except Exception as e: print("Failed to send email: {}".format(str(e))) EOF else log_warning "Python not available - cannot send email notification" fi } # Display usage usage() { echo "Usage: $0 [--daily|--weekly]" echo "" echo "Options:" echo " --daily Perform daily pg_dump backup" echo " --weekly Perform weekly pg_basebackup backup" echo "" echo "Examples:" echo " $0 --daily" echo " $0 --weekly" exit 1 } # Main execution main() { log "=========================================" log "Ferrero Database Backup" log "=========================================" # Parse arguments if [ $# -eq 0 ]; then usage fi case "$1" in --daily) backup_daily ;; --weekly) backup_weekly ;; *) usage ;; esac log "=========================================" log "Backup completed successfully" log "=========================================" } # Run main main "$@"