🔄 Add Comprehensive Backup Management System

 New Features:
- Complete backup lifecycle management (create, list, download, delete, cleanup)
- Web-based backup interface with real-time status updates
- Individual backup deletion and bulk cleanup for old backups
- Docker-aware backup operations with volume persistence
- Automated backup scheduling and retention policies

📁 Added Files:
- backup.py - Core backup script for creating timestamped archives
- docker_backup.sh - Docker-compatible backup wrapper script
- app/templates/backup.html - Web interface for backup management
- BACKUP_SYSTEM.md - Comprehensive backup system documentation
- BACKUP_GUIDE.md - Quick reference guide for backup operations

🔧 Enhanced Files:
- Dockerfile - Added backup.py copy for container availability
- docker-compose.yml - Added backup volume mount for persistence
- app/routes/api.py - Added backup API endpoints (create, list, delete, cleanup)
- app/routes/main.py - Added backup management route
- app/templates/index.html - Added backup management navigation
- README.md - Updated with backup system overview and quick start

🎯 Key Improvements:
- Fixed backup creation errors in Docker environment
- Added Docker-aware path detection for container operations
- Implemented proper error handling and user confirmation dialogs
- Added real-time backup status updates via JavaScript
- Enhanced data persistence with volume mounting

💡 Use Cases:
- Data protection and disaster recovery
- Environment migration and cloning
- Development data management
- Automated maintenance workflows
This commit is contained in:
2025-08-01 13:01:15 -04:00
parent faaddba185
commit 9e4c21996b
12 changed files with 2515 additions and 1 deletions

407
docker_backup.sh Executable file
View File

@@ -0,0 +1,407 @@
#!/bin/bash
"""
QR Code Manager - Docker Backup Script
This script provides backup and restore functionality for dockerized QR Code Manager.
It handles both data volumes and complete application backups.
Usage:
./docker_backup.sh [command] [options]
Commands:
backup-data - Backup data volumes only
backup-full - Full backup including application and data
restore - Restore from backup
list - List available backups
cleanup - Remove old backups
schedule - Set up automated backups
Examples:
./docker_backup.sh backup-data
./docker_backup.sh backup-full
./docker_backup.sh restore backup_20240801_120000.tar.gz
./docker_backup.sh list
"""
set -e # Exit on any error
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKUP_DIR="${SCRIPT_DIR}/backups"
CONTAINER_NAME="qr-code-manager"
COMPOSE_FILE="${SCRIPT_DIR}/docker-compose.yml"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Helper functions
log_info() {
echo -e "${BLUE} $1${NC}"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
# Ensure backup directory exists
mkdir -p "${BACKUP_DIR}"
# Check if Docker and docker-compose are available
check_dependencies() {
if ! command -v docker &> /dev/null; then
log_error "Docker is not installed or not in PATH"
exit 1
fi
if command -v docker &> /dev/null && docker compose version &> /dev/null 2>&1; then
# Use docker compose (newer syntax)
COMPOSE_CMD="docker compose"
elif command -v docker-compose &> /dev/null; then
# Fallback to docker-compose (older syntax)
COMPOSE_CMD="docker-compose"
else
log_error "Neither 'docker compose' nor 'docker-compose' is available"
exit 1
fi
}
# Get timestamp for backup naming
get_timestamp() {
date +"%Y%m%d_%H%M%S"
}
# Check if container is running
is_container_running() {
docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"
}
# Backup data volumes only
backup_data() {
local timestamp=$(get_timestamp)
local backup_name="qr_data_backup_${timestamp}.tar.gz"
local backup_path="${BACKUP_DIR}/${backup_name}"
log_info "Creating data backup: ${backup_name}"
if is_container_running; then
log_info "Container is running, creating hot backup..."
# Create backup from running container
docker exec "${CONTAINER_NAME}" tar czf /tmp/data_backup.tar.gz \
-C /app data/ || {
log_error "Failed to create backup inside container"
return 1
}
# Copy backup from container to host
docker cp "${CONTAINER_NAME}:/tmp/data_backup.tar.gz" "${backup_path}" || {
log_error "Failed to copy backup from container"
return 1
}
# Cleanup temp file in container
docker exec "${CONTAINER_NAME}" rm -f /tmp/data_backup.tar.gz
else
log_info "Container is not running, creating backup from volumes..."
# Create backup using temporary container
docker run --rm \
-v "${SCRIPT_DIR}/data:/source/data:ro" \
-v "${BACKUP_DIR}:/backup" \
alpine:latest tar czf "/backup/${backup_name}" -C /source data/ || {
log_error "Failed to create backup from volumes"
return 1
}
fi
# Add metadata
local metadata_file="${BACKUP_DIR}/${backup_name%.tar.gz}.json"
cat > "${metadata_file}" << EOF
{
"backup_type": "data",
"timestamp": "$(date -Iseconds)",
"container_name": "${CONTAINER_NAME}",
"backup_method": "$(if is_container_running; then echo 'hot'; else echo 'cold'; fi)",
"backup_size": "$(stat -f%z "${backup_path}" 2>/dev/null || stat -c%s "${backup_path}" 2>/dev/null || echo 'unknown')"
}
EOF
log_success "Data backup created: ${backup_path}"
log_info "Backup size: $(du -h "${backup_path}" | cut -f1)"
}
# Full backup including application
backup_full() {
local timestamp=$(get_timestamp)
local backup_name="qr_full_backup_${timestamp}.tar.gz"
local backup_path="${BACKUP_DIR}/${backup_name}"
log_info "Creating full backup: ${backup_name}"
# Stop container for consistent backup
local was_running=false
if is_container_running; then
log_warning "Stopping container for consistent backup..."
${COMPOSE_CMD} -f "${COMPOSE_FILE}" down
was_running=true
fi
# Create full backup
tar czf "${backup_path}" \
--exclude="backups" \
--exclude=".git" \
--exclude="__pycache__" \
--exclude="*.pyc" \
--exclude="*.log" \
-C "${SCRIPT_DIR}/.." \
"$(basename "${SCRIPT_DIR}")" || {
log_error "Failed to create full backup"
return 1
}
# Restart container if it was running
if [ "$was_running" = true ]; then
log_info "Restarting container..."
${COMPOSE_CMD} -f "${COMPOSE_FILE}" up -d
fi
# Add metadata
local metadata_file="${BACKUP_DIR}/${backup_name%.tar.gz}.json"
cat > "${metadata_file}" << EOF
{
"backup_type": "full",
"timestamp": "$(date -Iseconds)",
"container_name": "${CONTAINER_NAME}",
"backup_method": "cold",
"backup_size": "$(stat -f%z "${backup_path}" 2>/dev/null || stat -c%s "${backup_path}" 2>/dev/null || echo 'unknown')"
}
EOF
log_success "Full backup created: ${backup_path}"
log_info "Backup size: $(du -h "${backup_path}" | cut -f1)"
}
# List available backups
list_backups() {
log_info "Available backups:"
echo "$(printf '%s' '─%.0s' {1..80})"
if [ ! "$(ls -A "${BACKUP_DIR}"/*.tar.gz 2>/dev/null)" ]; then
echo "No backups found"
return
fi
for backup_file in "${BACKUP_DIR}"/*.tar.gz; do
if [ -f "$backup_file" ]; then
local basename=$(basename "$backup_file")
local size=$(du -h "$backup_file" | cut -f1)
local date=$(stat -f%Sm -t "%Y-%m-%d %H:%M" "$backup_file" 2>/dev/null || \
stat -c%y "$backup_file" 2>/dev/null | cut -d' ' -f1-2)
# Determine backup type
local type="Unknown"
if [[ "$basename" == *"data"* ]]; then
type="📄 Data"
elif [[ "$basename" == *"full"* ]]; then
type="📦 Full"
fi
printf " %-12s | %-40s | %8s | %s\n" "$type" "$basename" "$size" "$date"
fi
done
}
# Restore from backup
restore_backup() {
local backup_file="$1"
if [ -z "$backup_file" ]; then
log_error "Please specify backup file to restore"
echo "Available backups:"
list_backups
return 1
fi
# Check if backup file exists
local backup_path="${backup_file}"
if [ ! -f "$backup_path" ]; then
backup_path="${BACKUP_DIR}/${backup_file}"
if [ ! -f "$backup_path" ]; then
log_error "Backup file not found: $backup_file"
return 1
fi
fi
log_warning "Restoring from backup: $(basename "$backup_path")"
# Confirm restoration
echo -n "This will overwrite current data. Continue? (y/N): "
read -r confirm
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
log_info "Restoration cancelled"
return 0
fi
# Stop container
if is_container_running; then
log_info "Stopping container..."
${COMPOSE_CMD} -f "${COMPOSE_FILE}" down
fi
# Determine backup type and restore accordingly
if [[ "$(basename "$backup_path")" == *"data"* ]]; then
log_info "Restoring data backup..."
# Backup current data
if [ -d "${SCRIPT_DIR}/data" ]; then
mv "${SCRIPT_DIR}/data" "${SCRIPT_DIR}/data.backup.$(get_timestamp)"
fi
# Extract data backup
tar xzf "$backup_path" -C "${SCRIPT_DIR}" || {
log_error "Failed to extract data backup"
return 1
}
elif [[ "$(basename "$backup_path")" == *"full"* ]]; then
log_info "Restoring full backup..."
# Create restoration directory
local restore_dir="${SCRIPT_DIR}/../qr-restore-$(get_timestamp)"
mkdir -p "$restore_dir"
# Extract full backup
tar xzf "$backup_path" -C "$restore_dir" || {
log_error "Failed to extract full backup"
return 1
}
log_warning "Full backup extracted to: $restore_dir"
log_warning "Please manually review and copy files as needed"
return 0
fi
# Restart container
log_info "Starting container..."
${COMPOSE_CMD} -f "${COMPOSE_FILE}" up -d
log_success "Restoration completed successfully"
}
# Cleanup old backups
cleanup_backups() {
local keep_count=${1:-10}
log_info "Cleaning up old backups (keeping $keep_count most recent)"
# Get list of backup files sorted by modification time (newest first)
local backup_files=($(ls -t "${BACKUP_DIR}"/*.tar.gz 2>/dev/null || true))
if [ ${#backup_files[@]} -le $keep_count ]; then
log_info "No cleanup needed (${#backup_files[@]} backups found)"
return
fi
# Remove old backups
for ((i=$keep_count; i<${#backup_files[@]}; i++)); do
local backup_file="${backup_files[$i]}"
local metadata_file="${backup_file%.tar.gz}.json"
log_info "Removing old backup: $(basename "$backup_file")"
rm -f "$backup_file" "$metadata_file"
done
log_success "Cleanup completed"
}
# Set up automated backups
schedule_backups() {
log_info "Setting up automated backups with cron..."
# Create backup script for cron
local cron_script="${SCRIPT_DIR}/automated_backup.sh"
cat > "$cron_script" << 'EOF'
#!/bin/bash
# Automated backup script for QR Code Manager
cd "$(dirname "$0")"
./docker_backup.sh backup-data
if [ $(date +%u) -eq 1 ]; then # Monday
./docker_backup.sh backup-full
fi
./docker_backup.sh cleanup 15
EOF
chmod +x "$cron_script"
# Suggest cron entries
echo
log_info "Automated backup script created: $cron_script"
echo
echo "Add this to your crontab (crontab -e) for daily backups:"
echo "# QR Code Manager daily backup at 2 AM"
echo "0 2 * * * $cron_script >> ${SCRIPT_DIR}/backup.log 2>&1"
echo
echo "Or for hourly data backups:"
echo "# QR Code Manager hourly data backup"
echo "0 * * * * ${SCRIPT_DIR}/docker_backup.sh backup-data >> ${SCRIPT_DIR}/backup.log 2>&1"
}
# Main script logic
main() {
check_dependencies
case "${1:-}" in
"backup-data")
backup_data
;;
"backup-full")
backup_full
;;
"restore")
restore_backup "$2"
;;
"list")
list_backups
;;
"cleanup")
cleanup_backups "$2"
;;
"schedule")
schedule_backups
;;
*)
echo "QR Code Manager - Docker Backup Script"
echo
echo "Usage: $0 [command] [options]"
echo
echo "Commands:"
echo " backup-data - Backup data volumes only"
echo " backup-full - Full backup including application and data"
echo " restore <file> - Restore from backup"
echo " list - List available backups"
echo " cleanup [n] - Remove old backups (keep n most recent, default: 10)"
echo " schedule - Set up automated backups"
echo
echo "Examples:"
echo " $0 backup-data"
echo " $0 backup-full"
echo " $0 restore qr_data_backup_20240801_120000.tar.gz"
echo " $0 list"
echo " $0 cleanup 5"
;;
esac
}
main "$@"