Files
qr-code_manager/docker_backup.sh
ske087 9e4c21996b 🔄 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
2025-08-01 13:01:15 -04:00

408 lines
12 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 "$@"