#!/bin/bash # Description # This script performs a backup of all folders in /srv # For folders corresponding to active Docker containers, # containers are stopped during backup then restarted # Containers listed in EXCLUDED_CONTAINERS are ignored # Backups are kept for 7 days # Configuration SRV_DIR="/srv" BACKUP_DEST="/root/docker-backup" RETENTION_DAYS=7 LOG_FILE="/root/docker-backup.log" # List of containers to exclude from backup (space separated) # Example: EXCLUDED_CONTAINERS="traefik mysql-prod redis-cache" EXCLUDED_CONTAINERS="" # Logging function log_message() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" } # Check if destination directory exists if [ ! -d "$BACKUP_DEST" ]; then log_message "Creating destination directory $BACKUP_DEST" mkdir -p "$BACKUP_DEST" fi # Check available disk space (at least 1GB) available_space=$(df "$BACKUP_DEST" | awk 'NR==2 {print $4}') if [ "$available_space" -lt 1048576 ]; then log_message "WARNING: Low disk space (less than 1GB available)" fi log_message "Starting Docker backup" # Counters for statistics success_count=0 error_count=0 docker_stopped=() docker_errors=() # Function to backup a directory backup_directory() { local source_dir="$1" local folder_name="$2" if [ ! -e "$source_dir" ]; then log_message "WARNING: $source_dir does not exist, skipped" ((error_count++)) return 1 fi # Create destination folder for this service local dest_folder="$BACKUP_DEST/$folder_name" mkdir -p "$dest_folder" local filename="${folder_name}_$(date +%Y-%m-%d_%H-%M-%S).tar.gz" local filepath="$dest_folder/$filename" log_message "Backing up $source_dir to $folder_name/$filename" # Create the archive if tar -czf "$filepath" -C "$(dirname "$source_dir")" "$(basename "$source_dir")" 2>/dev/null; then # Create SHA-256 checksum file sha256sum "$filepath" > "$filepath.sha256" file_size=$(du -h "$filepath" | cut -f1) log_message "SUCCESS: $filename created ($file_size) - Checksum generated" ((success_count++)) return 0 else log_message "ERROR: Unable to create archive for $source_dir" ((error_count++)) return 1 fi } # Function to check if Docker is installed is_docker_available() { command -v docker >/dev/null 2>&1 } # Function to check if a container is excluded is_container_excluded() { local container_name="$1" if [ -n "$EXCLUDED_CONTAINERS" ]; then for excluded in $EXCLUDED_CONTAINERS; do if [ "$container_name" = "$excluded" ]; then return 0 fi done fi return 1 } # Function to check if a Docker container exists and is running is_container_running() { local container_name="$1" if is_docker_available; then docker ps --format "table {{.Names}}" | grep -q "^${container_name}$" 2>/dev/null else return 1 fi } # Function to stop a Docker container stop_container() { local container_name="$1" log_message "Stopping Docker container: $container_name" if docker stop "$container_name" >/dev/null 2>&1; then log_message "Container $container_name stopped successfully" docker_stopped+=("$container_name") return 0 else log_message "ERROR: Unable to stop container $container_name" docker_errors+=("$container_name") return 1 fi } # Function to start a Docker container start_container() { local container_name="$1" log_message "Starting Docker container: $container_name" if docker start "$container_name" >/dev/null 2>&1; then log_message "Container $container_name started successfully" return 0 else log_message "ERROR: Unable to start container $container_name" return 1 fi } # Check if Docker is available if ! is_docker_available; then log_message "WARNING: Docker is not installed or accessible" fi # Backup directories in /srv log_message "=== DOCKER SERVICES BACKUP ===" if [ -d "$SRV_DIR" ]; then # Loop through all folders in /srv for srv_folder in "$SRV_DIR"/*; do if [ -d "$srv_folder" ]; then folder_name=$(basename "$srv_folder") # Check if the container is in the exclusion list if is_container_excluded "$folder_name"; then log_message "EXCLUSION: Container $folder_name skipped (in exclusion list)" continue fi # Check if a Docker container with this name exists and is running if is_container_running "$folder_name"; then log_message "Active Docker container detected: $folder_name" # Stop the container if stop_container "$folder_name"; then # Wait a bit to ensure the container is completely stopped sleep 5 # Perform the backup backup_directory "$srv_folder" "$folder_name" # Restart the container start_container "$folder_name" else log_message "WARNING: Backing up $folder_name without stopping container (risk of inconsistency)" backup_directory "$srv_folder" "$folder_name" fi else # No corresponding Docker container, normal backup log_message "Service without active container: $folder_name" backup_directory "$srv_folder" "$folder_name" fi fi done else log_message "ERROR: Directory $SRV_DIR does not exist" exit 1 fi # Verification and generation of checksums for all archives log_message "=== CHECKSUM VERIFICATION AND GENERATION ===" archives_without_checksum=0 archives_with_checksum=0 find "$BACKUP_DEST" -name "*.tar.gz" -type f | while read -r archive_file; do checksum_file="${archive_file}.sha256" if [ ! -f "$checksum_file" ]; then log_message "Generating missing checksum for $(basename "$archive_file")" sha256sum "$archive_file" > "$checksum_file" ((archives_without_checksum++)) else ((archives_with_checksum++)) fi done # Final archive count total_archives=$(find "$BACKUP_DEST" -name "*.tar.gz" -type f | wc -l) missing_checksums=$(find "$BACKUP_DEST" -name "*.tar.gz" -type f | while read -r archive; do [ ! -f "${archive}.sha256" ] && echo "$archive"; done | wc -l) log_message "Archives found: $total_archives" if [ "$missing_checksums" -eq 0 ]; then log_message "SUCCESS: All archives have a checksum file" else log_message "WARNING: $missing_checksums archives without checksum" fi # Cleanup of old backups log_message "=== OLD BACKUPS CLEANUP ===" for service_dir in "$BACKUP_DEST"/*/; do if [ -d "$service_dir" ]; then service_name=$(basename "$service_dir") log_message "Removing backups older than $RETENTION_DAYS days for $service_name" # Find obsolete files (archives and checksums separately) deleted_files_tar=$(find "$service_dir" -type f -name "*.tar.gz" -mtime +$RETENTION_DAYS -print 2>/dev/null) deleted_files_sha=$(find "$service_dir" -type f -name "*.sha256" -mtime +$RETENTION_DAYS -print 2>/dev/null) if [ -n "$deleted_files_tar" ] || [ -n "$deleted_files_sha" ]; then # Remove and log archives if [ -n "$deleted_files_tar" ]; then echo "$deleted_files_tar" | while read -r file; do log_message "Removing: $service_name/$(basename "$file")" done find "$service_dir" -type f -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete 2>/dev/null fi # Remove and log checksums if [ -n "$deleted_files_sha" ]; then echo "$deleted_files_sha" | while read -r file; do log_message "Removing: $service_name/$(basename "$file")" done find "$service_dir" -type f -name "*.sha256" -mtime +$RETENTION_DAYS -delete 2>/dev/null fi else log_message "No obsolete files to remove for $service_name" fi fi done # Restart containers that failed to restart if [ ${#docker_errors[@]} -gt 0 ]; then log_message "=== RETRY STARTING CONTAINERS IN ERROR ===" for container in "${docker_errors[@]}"; do start_container "$container" done fi # Final statistics log_message "=== BACKUP SUMMARY ===" log_message "Successful backups: $success_count" log_message "Errors: $error_count" if [ ${#docker_stopped[@]} -gt 0 ]; then log_message "Docker containers managed: ${docker_stopped[*]}" fi if [ ${#docker_errors[@]} -gt 0 ]; then log_message "Docker containers in error: ${docker_errors[*]}" fi log_message "Backup completed" # Exit code based on errors if [ $error_count -gt 0 ] || [ ${#docker_errors[@]} -gt 0 ]; then exit 1 else exit 0 fi