#!/bin/bash # Docker Container Updater # # Description: Automatically updates Docker containers and manages Docker images # Features: # - Updates all Docker Compose projects in /root/docker # - Skips containers with .ignore file # - Removes obsolete Docker Compose version attributes # - Cleans up unused Docker images # Author: ivanch # Version: 2.0 set -euo pipefail # Exit on error, undefined vars, and pipe failures HOSTNAME=$(cat /etc/hostname) NOTIFY_URL_ERROR="http://notify.haven/template/notify/error" NOTIFY_URL_UPDATE="http://notify.haven/template/notify/update" send_error_notification() { local message="$1" local critical="$2" curl -s -X POST "$NOTIFY_URL_ERROR" \ -H "Content-Type: application/json" \ -d "{\"caller\": \"$HOSTNAME\", \"message\": \"$message\", \"critical\": $critical}" } send_update_notification() { local script_time="$1" curl -s -X POST "$NOTIFY_URL_UPDATE" \ -H "Content-Type: application/json" \ -d "{\"host\": \"$HOSTNAME\", \"asset\": \"Docker containers\", \"time\": $script_time}" } #============================================================================== # CONFIGURATION #============================================================================== # Color definitions for output formatting readonly NC='\033[0m' readonly RED='\033[1;31m' readonly GREEN='\033[1;32m' readonly LIGHT_GREEN='\033[1;32m' readonly LIGHT_BLUE='\033[1;34m' readonly LIGHT_GREY='\033[0;37m' readonly YELLOW='\033[1;33m' # Script configuration readonly DOCKER_FOLDER="/root/docker" readonly COMPOSE_FILES=("docker-compose.yml" "docker-compose.yaml" "compose.yaml" "compose.yml") # Auto-update configuration readonly AUTO_UPDATE_ENABLED=true #============================================================================== # UTILITY FUNCTIONS #============================================================================== # Print formatted log messages log_info() { echo -e "${LIGHT_GREY}[i] $1${NC}"; } log_success() { echo -e "${LIGHT_GREEN}[✓] $1${NC}"; } log_step() { echo -e "${LIGHT_BLUE}[i] $1${NC}"; } log_container() { echo -e "${LIGHT_BLUE}[$1] $2${NC}"; } log_warning() { echo -e "${YELLOW}[!] $1${NC}"; send_error_notification "$1" false } log_error() { echo -e "${RED}[x] $1${NC}" >&2; send_error_notification "$1" true } # Exit with error message die() { log_error "$1" exit 1 } # Check if a command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Check if Docker and Docker Compose are available check_docker_requirements() { log_info "Checking Docker requirements..." if ! command_exists docker; then die "Docker is not installed or not in PATH" fi if ! docker compose version >/dev/null 2>&1; then die "Docker Compose is not available" fi log_success "Docker requirements satisfied" } # Get SHA256 hash of a file get_file_hash() { local file="$1" sha256sum "$file" 2>/dev/null | awk '{print $1}' || echo "" } # Get SHA256 hash from URL content get_url_hash() { local url="$1" curl -s "$url" 2>/dev/null | sha256sum | awk '{print $1}' || echo "" } #============================================================================== # DOCKER COMPOSE MANAGEMENT #============================================================================== # Find the active Docker Compose file in current directory find_compose_file() { for compose_file in "${COMPOSE_FILES[@]}"; do if [[ -f "$compose_file" ]]; then echo "$compose_file" return 0 fi done return 1 } # Remove obsolete version attribute from Docker Compose files clean_compose_files() { local container_name="$1" for compose_file in "${COMPOSE_FILES[@]}"; do if [[ -f "$compose_file" ]]; then log_container "$container_name" "Cleaning obsolete version attribute from $compose_file" sed -i '/^version:/d' "$compose_file" || log_warning "Failed to clean $compose_file" fi done } # Check if container should be skipped should_skip_container() { [[ -f ".ignore" ]] } # Check if any containers are running in current directory has_running_containers() { local running_containers running_containers=$(docker compose ps -q 2>/dev/null || echo "") [[ -n "$running_containers" ]] } # Update a single Docker Compose project update_docker_project() { local project_dir="$1" local container_name container_name=$(basename "$project_dir") log_container "$container_name" "Checking for updates..." # Change to project directory cd "$project_dir" || { log_error "Cannot access directory: $project_dir" return 1 } # Check if container should be skipped if should_skip_container; then log_container "$container_name" "Skipping (found .ignore file)" return 0 fi # Verify compose file exists local compose_file if ! compose_file=$(find_compose_file); then log_container "$container_name" "No Docker Compose file found, skipping" return 0 fi # Clean compose files clean_compose_files "$container_name" # Check if containers are running if ! has_running_containers; then log_container "$container_name" "No running containers, skipping update" return 0 fi # Stop containers log_container "$container_name" "Stopping containers..." if ! docker compose down >/dev/null 2>&1; then log_error "Failed to stop containers in $container_name" return 1 fi # Pull updated images log_container "$container_name" "Pulling updated images..." if ! docker compose pull -q >/dev/null 2>&1; then log_warning "Failed to pull images for $container_name, attempting to restart anyway" fi # Start containers log_container "$container_name" "Starting containers..." if ! docker compose up -d >/dev/null 2>&1; then log_error "Failed to start containers in $container_name" return 1 fi log_container "$container_name" "Update completed successfully!" return 0 } # Update all Docker Compose projects update_all_docker_projects() { log_step "Starting Docker container updates..." # Check if Docker folder exists if [[ ! -d "$DOCKER_FOLDER" ]]; then die "Docker folder not found: $DOCKER_FOLDER" fi # Change to Docker folder cd "$DOCKER_FOLDER" || die "Cannot access Docker folder: $DOCKER_FOLDER" # Process each subdirectory for project_dir in */; do if [[ -d "$project_dir" ]]; then local project_path="$DOCKER_FOLDER/$project_dir" update_docker_project "$project_path" # Return to Docker folder for next iteration cd "$DOCKER_FOLDER" || die "Cannot return to Docker folder" fi done } #============================================================================== # DOCKER CLEANUP #============================================================================== # Clean up unused Docker resources cleanup_docker_resources() { log_step "Cleaning up unused Docker resources..." # Remove unused images log_info "Removing unused Docker images..." if docker image prune -af >/dev/null 2>&1; then log_success "Docker image cleanup completed" else log_warning "Docker image cleanup failed" fi } #============================================================================== # MAIN EXECUTION #============================================================================== main() { START_TIME=$(date +%s) log_step "Starting Docker Container Updater" echo # Check requirements check_docker_requirements # Update all Docker projects update_all_docker_projects # Clean up Docker resources cleanup_docker_resources echo log_success "Docker container update process completed!" END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) log_info "Total duration: $DURATION seconds" send_update_notification $DURATION } # Execute main function with all arguments main "$@"