Implements dual backup strategy with daily SQL dumps and weekly binary backups, complete with restore capabilities and health monitoring. Backup System Components: 1. database/backup.sh: - Daily mode: pg_dump SQL dumps (7-day retention) - Weekly mode: pg_basebackup binary backup (latest only) - Automatic cleanup of old backups - Compression (gzip) for space efficiency - Email notifications on failures - Docker-compatible execution 2. database/restore.sh: - Restore from SQL dump backups - Safety backup before restore - Confirmation prompts - Validation and verification - List available backups 3. database/check_backups.sh: - Health check monitoring - Verifies latest backup age (warns if > 25 hours) - Displays backup counts and sizes - Quiet mode for cron automation - Lists all available backups Documentation: - DATABASE_BACKUP_GUIDE.md: Complete backup/restore guide - Automated cron setup - Manual backup procedures - Restore scenarios - Troubleshooting - Disk space management - backups/README.md: Quick reference - Directory structure - Common commands - Retention policy - Security notes Configuration: - Updated .gitignore to exclude backup files - Backup locations: backups/dumps/, backups/basebackups/ - Logs: logs/backup.log, logs/restore.log - Retention: 7 daily dumps + 1 weekly basebackup Cron Schedule (Production): - Daily: 2:00 AM (pg_dump) - Weekly: Sundays 3:00 AM (pg_basebackup) - Health Check: 8:00 AM daily Features: ✅ Automated daily and weekly backups ✅ Dual strategy (logical + physical) ✅ Space-efficient (7-day retention, ~50 MB total) ✅ Safety backups before restore ✅ Email alerts on failures ✅ Health monitoring ✅ Docker-compatible ✅ Tested locally Testing Performed: - Daily backup created successfully (77K compressed) - Backup file integrity verified (gzip test passed) - Health check shows "Backup system healthy" - Restore --list command working - All scripts executable and functional Disk Usage Estimate: - Daily dumps: 7 × ~2 MB = ~14 MB - Weekly backup: 1 × ~30 MB = ~30 MB - Total: ~50 MB maximum 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
186 lines
4.8 KiB
Bash
Executable file
186 lines
4.8 KiB
Bash
Executable file
#!/bin/bash
|
|
#
|
|
# Database Backup Health Check Script
|
|
# Verifies backup status and alerts if backups are stale or missing
|
|
#
|
|
# Usage:
|
|
# ./database/check_backups.sh # Check backup health
|
|
# ./database/check_backups.sh --quiet # Minimal output (for cron)
|
|
#
|
|
|
|
# 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"
|
|
|
|
# Colors
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
NC='\033[0m'
|
|
|
|
# Quiet mode
|
|
QUIET=false
|
|
if [ "$1" = "--quiet" ]; then
|
|
QUIET=true
|
|
fi
|
|
|
|
# Output function
|
|
output() {
|
|
if [ "$QUIET" = false ]; then
|
|
echo -e "$1"
|
|
fi
|
|
}
|
|
|
|
# Check if backups exist
|
|
check_daily_backups() {
|
|
output "${GREEN}Daily Backups (pg_dump):${NC}"
|
|
|
|
if [ ! -d "$DUMP_DIR" ]; then
|
|
output "${RED}✗ Backup directory not found: $DUMP_DIR${NC}"
|
|
return 1
|
|
fi
|
|
|
|
local backup_count=$(ls -1 "$DUMP_DIR"/*.sql.gz 2>/dev/null | wc -l)
|
|
|
|
if [ "$backup_count" -eq 0 ]; then
|
|
output "${RED}✗ No daily backups found${NC}"
|
|
return 1
|
|
fi
|
|
|
|
output "${GREEN}✓ Found $backup_count daily backup(s)${NC}"
|
|
|
|
# Find latest backup
|
|
local latest_backup=$(ls -t "$DUMP_DIR"/*.sql.gz 2>/dev/null | head -1)
|
|
|
|
if [ -n "$latest_backup" ]; then
|
|
local backup_age_hours=$(( ($(date +%s) - $(stat -f %m "$latest_backup")) / 3600 ))
|
|
local backup_size=$(du -h "$latest_backup" | cut -f1)
|
|
local backup_name=$(basename "$latest_backup")
|
|
|
|
output " Latest: $backup_name"
|
|
output " Size: $backup_size"
|
|
output " Age: ${backup_age_hours} hours ago"
|
|
|
|
if [ "$backup_age_hours" -gt 25 ]; then
|
|
output "${YELLOW} ⚠️ WARNING: Backup is older than 25 hours${NC}"
|
|
return 1
|
|
else
|
|
output "${GREEN} ✓ Backup is fresh${NC}"
|
|
fi
|
|
fi
|
|
|
|
output ""
|
|
return 0
|
|
}
|
|
|
|
check_weekly_backups() {
|
|
output "${GREEN}Weekly Backups (pg_basebackup):${NC}"
|
|
|
|
if [ ! -d "$BASEBACKUP_DIR" ]; then
|
|
output "${YELLOW}⚠️ Weekly backup directory not found (optional)${NC}"
|
|
return 0
|
|
fi
|
|
|
|
local backup_count=$(ls -1 "$BASEBACKUP_DIR"/*.tar.gz 2>/dev/null | wc -l)
|
|
|
|
if [ "$backup_count" -eq 0 ]; then
|
|
output "${YELLOW}⚠️ No weekly backups found (will be created on Sunday)${NC}"
|
|
return 0
|
|
fi
|
|
|
|
output "${GREEN}✓ Found $backup_count weekly backup(s)${NC}"
|
|
|
|
# Find latest backup
|
|
local latest_backup=$(ls -t "$BASEBACKUP_DIR"/*.tar.gz 2>/dev/null | head -1)
|
|
|
|
if [ -n "$latest_backup" ]; then
|
|
local backup_age_days=$(( ($(date +%s) - $(stat -f %m "$latest_backup")) / 86400 ))
|
|
local backup_size=$(du -h "$latest_backup" | cut -f1)
|
|
local backup_name=$(basename "$latest_backup")
|
|
|
|
output " Latest: $backup_name"
|
|
output " Size: $backup_size"
|
|
output " Age: ${backup_age_days} days ago"
|
|
fi
|
|
|
|
output ""
|
|
return 0
|
|
}
|
|
|
|
# Display disk space
|
|
check_disk_space() {
|
|
output "${GREEN}Disk Space Usage:${NC}"
|
|
|
|
if [ -d "$BACKUP_BASE_DIR" ]; then
|
|
local total_size=$(du -sh "$BACKUP_BASE_DIR" 2>/dev/null | cut -f1)
|
|
output " Total backup size: $total_size"
|
|
else
|
|
output "${YELLOW} Backup directory not yet created${NC}"
|
|
fi
|
|
|
|
output ""
|
|
}
|
|
|
|
# List all backups
|
|
list_all_backups() {
|
|
output "${GREEN}All Available Backups:${NC}"
|
|
output ""
|
|
|
|
if [ -d "$DUMP_DIR" ] && ls "$DUMP_DIR"/*.sql.gz 1> /dev/null 2>&1; then
|
|
output "Daily Backups:"
|
|
ls -lht "$DUMP_DIR"/*.sql.gz | head -10 | awk '{print " " $9 " - " $5 " - " $6 " " $7 " " $8}'
|
|
output ""
|
|
fi
|
|
|
|
if [ -d "$BASEBACKUP_DIR" ] && ls "$BASEBACKUP_DIR"/*.tar.gz 1> /dev/null 2>&1; then
|
|
output "Weekly Backups:"
|
|
ls -lht "$BASEBACKUP_DIR"/*.tar.gz | awk '{print " " $9 " - " $5 " - " $6 " " $7 " " $8}'
|
|
output ""
|
|
fi
|
|
}
|
|
|
|
# Main
|
|
main() {
|
|
if [ "$QUIET" = false ]; then
|
|
echo "========================================="
|
|
echo "Ferrero Database Backup Health Check"
|
|
echo "========================================="
|
|
echo ""
|
|
fi
|
|
|
|
local status=0
|
|
|
|
# Check daily backups
|
|
if ! check_daily_backups; then
|
|
status=1
|
|
fi
|
|
|
|
# Check weekly backups
|
|
check_weekly_backups
|
|
|
|
# Check disk space
|
|
check_disk_space
|
|
|
|
# List all backups if not quiet
|
|
if [ "$QUIET" = false ]; then
|
|
list_all_backups
|
|
fi
|
|
|
|
if [ "$QUIET" = false ]; then
|
|
echo "========================================="
|
|
if [ $status -eq 0 ]; then
|
|
echo -e "${GREEN}✓ Backup system healthy${NC}"
|
|
else
|
|
echo -e "${RED}✗ Backup system needs attention${NC}"
|
|
fi
|
|
echo "========================================="
|
|
fi
|
|
|
|
exit $status
|
|
}
|
|
|
|
# Run main
|
|
main
|