#!/bin/bash # ================================================ # Math Platform - Database Backup Script # ================================================ set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" BACKUP_DIR="$PROJECT_ROOT/docker/backups" DB_CONTAINER="math-postgres" DB_USER="mathuser" DB_NAME="mathdb" TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_FILE="$BACKUP_DIR/mathdb_backup_$TIMESTAMP.sql" COMPRESSED_FILE="$BACKUP_FILE.gz" # Retention settings RETENTION_DAYS=30 RETENTION_COUNT=20 echo -e "${BLUE}============================================${NC}" echo -e "${BLUE}Math Platform - Database Backup${NC}" echo -e "${BLUE}============================================${NC}" # Create backup directory mkdir -p "$BACKUP_DIR" # Function to check if container is running check_container() { if ! docker ps | grep -q $DB_CONTAINER; then echo -e "${RED}Error: Database container $DB_CONTAINER is not running!${NC}" exit 1 fi echo -e "${GREEN}Database container is running${NC}" } # Function to create backup create_backup() { echo -e "${YELLOW}Creating database backup...${NC}" echo -e "Timestamp: $TIMESTAMP" echo -e "Destination: $COMPRESSED_FILE" if docker exec $DB_CONTAINER pg_dump -U $DB_USER $DB_NAME > "$BACKUP_FILE" 2>/dev/null; then if [ -f "$BACKUP_FILE" ] && [ -s "$BACKUP_FILE" ]; then # Compress backup gzip "$BACKUP_FILE" # Get file size FILE_SIZE=$(du -h "$COMPRESSED_FILE" | cut -f1) echo -e "${GREEN}Backup created successfully!${NC}" echo -e "${GREEN}File size: $FILE_SIZE${NC}" echo -e "${GREEN}Location: $COMPRESSED_FILE${NC}" # Create checksum sha256sum "$COMPRESSED_FILE" > "$COMPRESSED_FILE.sha256" echo -e "${GREEN}Checksum: ${COMPRESSED_FILE}.sha256${NC}" else echo -e "${RED}Error: Backup file is empty or was not created!${NC}" rm -f "$BACKUP_FILE" exit 1 fi else echo -e "${RED}Error: Failed to create database backup!${NC}" exit 1 fi } # Function to restore backup restore_backup() { local backup_file=$1 if [ -z "$backup_file" ]; then echo -e "${RED}Error: Please specify backup file to restore${NC}" echo "Usage: $0 --restore " exit 1 fi if [ ! -f "$backup_file" ]; then echo -e "${RED}Error: Backup file not found: $backup_file${NC}" exit 1 fi echo -e "${YELLOW}Restoring database from backup...${NC}" echo -e "Source: $backup_file" echo -e "${RED}WARNING: This will overwrite the current database!${NC}" read -p "Are you sure? (yes/no): " confirm if [ "$confirm" != "yes" ]; then echo -e "${YELLOW}Restore cancelled${NC}" exit 0 fi # Decompress if needed local temp_file="$backup_file" if [[ "$backup_file" == *.gz ]]; then temp_file="/tmp/restore_$(date +%s).sql" gunzip -c "$backup_file" > "$temp_file" fi # Drop existing database and recreate docker exec -i $DB_CONTAINER psql -U $DB_USER -d postgres <<-EOF DROP DATABASE IF EXISTS $DB_NAME; CREATE DATABASE $DB_NAME; EOF # Restore backup docker exec -i $DB_CONTAINER psql -U $DB_USER $DB_NAME < "$temp_file" # Cleanup if [ "$temp_file" != "$backup_file" ]; then rm -f "$temp_file" fi echo -e "${GREEN}Database restored successfully!${NC}" } # Function to list backups list_backups() { echo -e "\n${BLUE}============================================${NC}" echo -e "${BLUE}Available Backups${NC}" echo -e "${BLUE}============================================${NC}" if [ "$(ls -A $BACKUP_DIR 2>/dev/null)" ]; then printf "%-40s %-15s %-10s\n" "File Name" "Date" "Size" printf "%-40s %-15s %-10s\n" "---------" "----" "----" for file in "$BACKUP_DIR"/mathdb_backup_*.sql.gz; do if [ -f "$file" ]; then filename=$(basename "$file") date=$(echo $filename | grep -oP '\d{8}_\d{6}' | sed 's/_/ /') size=$(du -h "$file" | cut -f1) printf "%-40s %-15s %-10s\n" "$filename" "$date" "$size" fi done else echo -e "${YELLOW}No backups found${NC}" fi } # Function to cleanup old backups cleanup_old_backups() { echo -e "${YELLOW}Cleaning up old backups...${NC}" echo -e "Retention: $RETENTION_COUNT backups or $RETENTION_DAYS days" # Remove backups older than retention days find "$BACKUP_DIR" -name "mathdb_backup_*.sql.gz" -mtime +$RETENTION_DAYS -delete find "$BACKUP_DIR" -name "mathdb_backup_*.sql.gz.sha256" -mtime +$RETENTION_DAYS -delete # Keep only the most recent N backups ls -t "$BACKUP_DIR"/mathdb_backup_*.sql.gz 2>/dev/null | tail -n +$((RETENTION_COUNT + 1)) | xargs -r rm ls -t "$BACKUP_DIR"/mathdb_backup_*.sql.gz.sha256 2>/dev/null | tail -n +$((RETENTION_COUNT + 1)) | xargs -r rm # Count remaining backups count=$(ls -1 "$BACKUP_DIR"/mathdb_backup_*.sql.gz 2>/dev/null | wc -l) echo -e "${GREEN}Cleanup completed. $count backup(s) retained${NC}" } # Function to setup automated backups setup_cron() { echo -e "${YELLOW}Setting up automated daily backups...${NC}" # Create cron job local cron_job="0 2 * * * $PROJECT_ROOT/docker/backup.sh >/dev/null 2>&1" # Add to crontab if not exists (crontab -l 2>/dev/null | grep -v "$PROJECT_ROOT/docker/backup.sh"; echo "$cron_job") | crontab - echo -e "${GREEN}Automated backup scheduled for daily at 2:00 AM${NC}" echo -e "${YELLOW}View crontab with: crontab -l${NC}" } # Main execution main() { ACTION="backup" # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --restore) ACTION="restore" RESTORE_FILE="$2" shift 2 ;; --list) ACTION="list" shift ;; --cleanup) ACTION="cleanup" shift ;; --setup-cron) ACTION="setup-cron" shift ;; *) echo -e "${RED}Unknown option: $1${NC}" echo "Usage: $0 [--restore ] [--list] [--cleanup] [--setup-cron]" exit 1 ;; esac done case $ACTION in backup) check_container create_backup cleanup_old_backups ;; restore) check_container restore_backup "$RESTORE_FILE" ;; list) list_backups ;; cleanup) cleanup_old_backups ;; setup-cron) setup_cron ;; esac } # Run main function main "$@"