#!/bin/bash # backup-docker.sh - Comprehensive Docker volumes backup script # Author: Shell Repository # Description: Backup Docker container volumes with proper error handling, logging, and metrics set -e # Load the unified backup metrics library SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LIB_DIR="$SCRIPT_DIR/lib" if [[ -f "$LIB_DIR/unified-backup-metrics.sh" ]]; then # shellcheck source=lib/unified-backup-metrics.sh source "$LIB_DIR/unified-backup-metrics.sh" METRICS_ENABLED=true else echo "Warning: Unified backup metrics library not found at $LIB_DIR/unified-backup-metrics.sh" METRICS_ENABLED=false fi # Colors for output GREEN='\033[0;32m' YELLOW='\033[0;33m' RED='\033[0;31m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration BACKUP_ROOT="/home/acedanger/backup/docker-data" LOG_FILE="$SCRIPT_DIR/logs/docker-backup.log" NOTIFICATION_URL="https://notify.peterwood.rocks/lab" # Container definitions: container_name:volume_path:description declare -A CONTAINERS=( ["vaultwarden"]="/var/lib/docker/volumes/vaultwarden_data/_data:Password manager data" ["uptime-kuma"]="/var/lib/docker/volumes/uptime-kuma/_data:Uptime monitoring data" # ["paperless-ng"]="/var/lib/docker/volumes/paperless-ng_data/_data:Document management data" # ["paperless-media"]="/var/lib/docker/volumes/paperless-ng_media/_data:Document media files" # ["paperless-pgdata"]="/var/lib/docker/volumes/paperless-ng_pgdata/_data:PostgreSQL database" ) # Ensure directories exist mkdir -p "$(dirname "$LOG_FILE")" mkdir -p "$BACKUP_ROOT" # Logging function log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" } # Cleanup function for metrics finalization cleanup() { if [[ "$METRICS_ENABLED" == "true" ]]; then if [[ -n "$1" && "$1" == "error" ]]; then metrics_backup_complete "failed" "Docker backup failed during execution" else metrics_backup_complete "success" "Docker volumes backup completed successfully" fi fi } # Set up cleanup trap trap 'cleanup error' ERR # Check if container is running check_container_running() { local container="$1" if docker ps --format "table {{.Names}}" | grep -q "^${container}$"; then return 0 else return 1 fi } # Stop container safely stop_container() { local container="$1" log "Stopping container: $container" if [[ "$METRICS_ENABLED" == "true" ]]; then metrics_status_update "stopping_service" "Stopping container: $container" fi if ! docker stop "$container" >/dev/null 2>&1; then log "Warning: Failed to stop container $container or container not running" return 1 fi # Wait for container to fully stop local max_wait=30 local wait_count=0 while [ $wait_count -lt $max_wait ]; do if ! docker ps -q --filter "name=$container" | grep -q .; then log "Container $container stopped successfully" return 0 fi wait_count=$((wait_count + 1)) sleep 1 done log "Warning: Container $container may not have stopped completely" return 1 } # Start container safely start_container() { local container="$1" log "Starting container: $container" if [[ "$METRICS_ENABLED" == "true" ]]; then metrics_status_update "starting_service" "Starting container: $container" fi if ! docker start "$container" >/dev/null 2>&1; then log "Error: Failed to start container $container" return 1 fi # Wait for container to be running local max_wait=30 local wait_count=0 while [ $wait_count -lt $max_wait ]; do if docker ps -q --filter "name=$container" | grep -q .; then log "Container $container started successfully" return 0 fi wait_count=$((wait_count + 1)) sleep 1 done log "Warning: Container $container may not have started properly" return 1 } # Backup container volume backup_container_volume() { local container="$1" local volume_path="$2" local description="$3" local backup_file="$BACKUP_ROOT/${container}-data-bk-$(date +%Y%m%d).tar.gz" log "Starting backup for $container ($description)" # Check if volume path exists if [ ! -d "$volume_path" ]; then log "Error: Volume path does not exist: $volume_path" return 1 fi # Check if container was running local was_running=false if check_container_running "$container"; then was_running=true if ! stop_container "$container"; then log "Error: Failed to stop container $container" return 1 fi else log "Container $container is not running, proceeding with backup" fi # Create backup log "Creating backup archive: $(basename "$backup_file")" if [[ "$METRICS_ENABLED" == "true" ]]; then metrics_status_update "backing_up" "Creating archive for $container" fi if tar -czf "$backup_file" -C "$(dirname "$volume_path")" "$(basename "$volume_path")" 2>/dev/null; then local backup_size backup_size=$(du -h "$backup_file" | cut -f1) log "Backup completed successfully: $(basename "$backup_file") ($backup_size)" # Track file completion in metrics if [[ "$METRICS_ENABLED" == "true" ]]; then local file_size_bytes file_size_bytes=$(stat -c%s "$backup_file" 2>/dev/null || echo "0") metrics_file_backup_complete "$(basename "$backup_file")" "$file_size_bytes" "created" fi else log "Error: Failed to create backup for $container" # Try to restart container even if backup failed if [ "$was_running" = true ]; then start_container "$container" || true fi return 1 fi # Restart container if it was running if [ "$was_running" = true ]; then if ! start_container "$container"; then log "Error: Failed to restart container $container after backup" return 1 fi fi return 0 } # Send notification send_notification() { local status="$1" local message="$2" local failed_containers="$3" local tags="backup,docker,${HOSTNAME}" local priority="default" if [ "$status" = "failed" ]; then priority="high" tags="${tags},error" fi # Add successful container names to tags for container in "${!CONTAINERS[@]}"; do if [[ ! " $failed_containers " =~ " $container " ]]; then tags="${tags},$container" fi done curl -s \ -H "priority:$priority" \ -H "tags:$tags" \ -d "$message" \ "$NOTIFICATION_URL" || log "Warning: Failed to send notification" } # Check dependencies check_dependencies() { local missing_deps=() if ! command -v docker >/dev/null 2>&1; then missing_deps+=("docker") fi if ! command -v tar >/dev/null 2>&1; then missing_deps+=("tar") fi if ! command -v curl >/dev/null 2>&1; then missing_deps+=("curl") fi if [ ${#missing_deps[@]} -ne 0 ]; then log "Error: Missing required dependencies: ${missing_deps[*]}" exit 1 fi # Check if Docker daemon is running if ! docker info >/dev/null 2>&1; then log "Error: Docker daemon is not running or not accessible" exit 1 fi } # Main backup function main() { log "=== Docker Volumes Backup Started ===" # Initialize metrics if enabled if [[ "$METRICS_ENABLED" == "true" ]]; then metrics_backup_start "docker-volumes" "Docker container volumes backup" "$BACKUP_ROOT" metrics_status_update "initializing" "Preparing Docker volumes backup" fi # Check dependencies check_dependencies # Check backup directory space local available_space_gb available_space_gb=$(df -BG "$BACKUP_ROOT" | awk 'NR==2 {print $4}' | sed 's/G//') if [ "$available_space_gb" -lt 5 ]; then log "Warning: Low disk space in backup directory: ${available_space_gb}GB available" fi local successful_backups=0 local failed_backups=0 local failed_containers=() # Update metrics for backup phase if [[ "$METRICS_ENABLED" == "true" ]]; then metrics_status_update "backing_up" "Backing up Docker container volumes" fi # Backup each container for container in "${!CONTAINERS[@]}"; do local volume_info="${CONTAINERS[$container]}" local volume_path="${volume_info%%:*}" local description="${volume_info##*:}" if backup_container_volume "$container" "$volume_path" "$description"; then ((successful_backups++)) else ((failed_backups++)) failed_containers+=("$container") fi done # Update metrics for completion if [[ "$METRICS_ENABLED" == "true" ]]; then if [ $failed_backups -eq 0 ]; then metrics_status_update "completed" "All Docker backups completed successfully" else metrics_status_update "completed_with_errors" "Docker backup completed with $failed_backups failures" fi fi # Summary log "=== Docker Volumes Backup Summary ===" log "Successful backups: $successful_backups" log "Failed backups: $failed_backups" if [ ${#failed_containers[@]} -gt 0 ]; then log "Failed containers: ${failed_containers[*]}" fi # Send notification if [ $failed_backups -eq 0 ]; then log "All backups completed successfully!" send_notification "success" "Completed backup of all Docker containers ($successful_backups services)" "" else log "Some backups failed!" send_notification "failed" "Docker backup completed with errors: $failed_backups failed, $successful_backups succeeded" "${failed_containers[*]}" fi # Finalize metrics if [[ "$METRICS_ENABLED" == "true" ]]; then cleanup fi log "=== Docker Volumes Backup Finished ===" # Exit with error code if any backups failed exit $failed_backups } # Run main function main "$@"