scripts v2.0
All checks were successful
Check scripts syntax / check-scripts-syntax (push) Successful in 35s
All checks were successful
Check scripts syntax / check-scripts-syntax (push) Successful in 35s
This commit is contained in:
@@ -1,106 +1,338 @@
|
||||
#!/bin/bash
|
||||
|
||||
NC='\033[0m'
|
||||
LIGHT_GREEN='\033[1;32m'
|
||||
LIGHT_BLUE='\033[1;34m'
|
||||
LIGHT_GREEN='\033[1;32m'
|
||||
LIGHT_GREY='\033[0;37m'
|
||||
# Docker Container Updater
|
||||
#
|
||||
# Description: Automatically updates Docker containers and manages Docker images
|
||||
# Features:
|
||||
# - Self-updating capability
|
||||
# - 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
|
||||
|
||||
### AUTO-UPDATER ###
|
||||
FILE_NAME="docker-updater.sh"
|
||||
SERVER_FILE="https://git.ivanch.me/ivanch/server-scripts/raw/branch/main/$FILE_NAME"
|
||||
SERVER_OK=1
|
||||
set -euo pipefail # Exit on error, undefined vars, and pipe failures
|
||||
|
||||
# Check if the server file exists
|
||||
curl -s --head $SERVER_FILE | head -n 1 | grep -E "HTTP/[12] [23].." > /dev/null
|
||||
#==============================================================================
|
||||
# CONFIGURATION
|
||||
#==============================================================================
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}[x] Error: $SERVER_FILE not found.${NC}" >&2
|
||||
SERVER_OK=0
|
||||
fi
|
||||
# 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'
|
||||
|
||||
if [ $SERVER_OK -eq 1 ]; then
|
||||
echo -e "${LIGHT_BLUE}[i] Running auto-update"
|
||||
# Script configuration
|
||||
readonly SCRIPT_NAME="docker-updater.sh"
|
||||
readonly SERVER_BASE_URL="https://git.ivanch.me/ivanch/server-scripts/raw/branch/main"
|
||||
readonly DOCKER_FOLDER="/root/docker"
|
||||
readonly COMPOSE_FILES=("docker-compose.yml" "docker-compose.yaml" "compose.yaml" "compose.yml")
|
||||
|
||||
# Compare the local and server files sha256sum to check if an update is needed
|
||||
LOCAL_SHA256=$(sha256sum $FILE_NAME | awk '{print $1}')
|
||||
SERVER_SHA256=$(curl -s $SERVER_FILE | sha256sum | awk '{print $1}')
|
||||
# Auto-update configuration
|
||||
readonly AUTO_UPDATE_ENABLED=true
|
||||
|
||||
if [ "$LOCAL_SHA256" != "$SERVER_SHA256" ]; then
|
||||
echo -e "${LIGHT_GREY}[i] Updating $FILE_NAME${NC}"
|
||||
#==============================================================================
|
||||
# UTILITY FUNCTIONS
|
||||
#==============================================================================
|
||||
|
||||
curl -s -o $FILE_NAME $SERVER_FILE
|
||||
chmod +x $FILE_NAME
|
||||
echo -e "${LIGHT_GREEN}[i] $FILE_NAME updated.${NC}"
|
||||
# Print formatted log messages
|
||||
log_info() { echo -e "${LIGHT_GREY}[i] $1${NC}"; }
|
||||
log_success() { echo -e "${LIGHT_GREEN}[✓] $1${NC}"; }
|
||||
log_warning() { echo -e "${YELLOW}[!] $1${NC}"; }
|
||||
log_error() { echo -e "${RED}[x] $1${NC}" >&2; }
|
||||
log_step() { echo -e "${LIGHT_BLUE}[i] $1${NC}"; }
|
||||
log_container() { echo -e "${LIGHT_BLUE}[$1] $2${NC}"; }
|
||||
|
||||
echo -e "${LIGHT_BLUE}[i] Running updated $FILE_NAME...${NC}"
|
||||
./$FILE_NAME
|
||||
exit 0
|
||||
else
|
||||
echo -e "${LIGHT_GREEN}[i] $FILE_NAME is already up to date.${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
####################
|
||||
|
||||
# Navigate to docker folder
|
||||
DOCKER_FOLDER=/root/docker
|
||||
|
||||
if [ -d "$DOCKER_FOLDER" ]; then
|
||||
cd $DOCKER_FOLDER
|
||||
else
|
||||
echo -e "${LIGHT_GREY}[i] Docker folder not found.${NC}"
|
||||
# Exit with error message
|
||||
die() {
|
||||
log_error "$1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Updating Docker containers
|
||||
# Check if a command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
for folder in */; do
|
||||
cd $DOCKER_FOLDER/$folder
|
||||
|
||||
# Remove trailing slash from folder name if it exists
|
||||
folder=${folder%/}
|
||||
echo -e "${LIGHT_BLUE}[$folder] Checking for updates..."
|
||||
|
||||
# if .ignore file exists, skip the folder
|
||||
if [ -f ".ignore" ]; then
|
||||
echo -e "${LIGHT_BLUE}[$folder] Skipping docker container update"
|
||||
cd ..
|
||||
continue
|
||||
# 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"
|
||||
}
|
||||
|
||||
# Check compose files for obsolete version attribute
|
||||
for compose_file in "docker-compose.yml" "docker-compose.yaml" "compose.yaml" "compose.yml"; do
|
||||
if [ -f "$compose_file" ]; then
|
||||
echo -e "${LIGHT_BLUE}[$folder] Checking $compose_file for obsolete version attribute"
|
||||
sed -i '/^version:/d' "$compose_file"
|
||||
# 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 ""
|
||||
}
|
||||
|
||||
# Check if server file is accessible
|
||||
check_server_connectivity() {
|
||||
local url="$1"
|
||||
curl -s --head "$url" | head -n 1 | grep -E "HTTP/[12] [23].." >/dev/null 2>&1
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# AUTO-UPDATE FUNCTIONALITY
|
||||
#==============================================================================
|
||||
|
||||
# Perform self-update if newer version is available
|
||||
perform_self_update() {
|
||||
if [[ "$AUTO_UPDATE_ENABLED" != "true" ]]; then
|
||||
log_info "Auto-update is disabled"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local server_url="$SERVER_BASE_URL/$SCRIPT_NAME"
|
||||
|
||||
log_step "Checking for script updates..."
|
||||
|
||||
# Check if server file is accessible
|
||||
if ! check_server_connectivity "$server_url"; then
|
||||
log_warning "Cannot connect to update server, continuing with current version"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Compare local and server file hashes
|
||||
local local_hash
|
||||
local server_hash
|
||||
|
||||
local_hash=$(get_file_hash "$SCRIPT_NAME")
|
||||
server_hash=$(get_url_hash "$server_url")
|
||||
|
||||
if [[ -z "$local_hash" || -z "$server_hash" ]]; then
|
||||
log_warning "Cannot determine file hashes, skipping update"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$local_hash" != "$server_hash" ]]; then
|
||||
log_info "Update available, downloading new version..."
|
||||
|
||||
# Create backup of current script
|
||||
local backup_file="${SCRIPT_NAME}.backup.$(date +%s)"
|
||||
cp "$SCRIPT_NAME" "$backup_file" || die "Failed to create backup"
|
||||
|
||||
# Download updated script
|
||||
if curl -s -o "$SCRIPT_NAME" "$server_url"; then
|
||||
chmod +x "$SCRIPT_NAME" || die "Failed to set executable permissions"
|
||||
log_success "Script updated successfully"
|
||||
|
||||
log_step "Running updated script..."
|
||||
exec ./"$SCRIPT_NAME" "$@"
|
||||
else
|
||||
# Restore backup on failure
|
||||
mv "$backup_file" "$SCRIPT_NAME"
|
||||
die "Failed to download updated script"
|
||||
fi
|
||||
else
|
||||
log_success "Script is already up to date"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# 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
|
||||
}
|
||||
|
||||
DOCKER_RUNNING=$(docker compose ps -q)
|
||||
# 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
|
||||
}
|
||||
|
||||
if [ -n "$DOCKER_RUNNING" ]; then
|
||||
echo -e "${LIGHT_BLUE}[$folder] Stopping Docker containers"
|
||||
docker compose down > /dev/null
|
||||
else
|
||||
echo -e "${LIGHT_BLUE}[$folder] No Docker containers running, will skip update"
|
||||
continue
|
||||
# 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
|
||||
}
|
||||
|
||||
echo -e "${LIGHT_BLUE}[$folder] Updating images"
|
||||
docker compose pull -q > /dev/null
|
||||
# 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"
|
||||
|
||||
local updated_count=0
|
||||
local failed_count=0
|
||||
local skipped_count=0
|
||||
|
||||
# Process each subdirectory
|
||||
for project_dir in */; do
|
||||
if [[ -d "$project_dir" ]]; then
|
||||
local project_path="$DOCKER_FOLDER/$project_dir"
|
||||
|
||||
if update_docker_project "$project_path"; then
|
||||
if should_skip_container; then
|
||||
((skipped_count++))
|
||||
else
|
||||
((updated_count++))
|
||||
fi
|
||||
else
|
||||
((failed_count++))
|
||||
fi
|
||||
|
||||
# Return to Docker folder for next iteration
|
||||
cd "$DOCKER_FOLDER" || die "Cannot return to Docker folder"
|
||||
fi
|
||||
done
|
||||
|
||||
# Report results
|
||||
log_success "Docker update summary:"
|
||||
log_info " Updated: $updated_count projects"
|
||||
log_info " Skipped: $skipped_count projects"
|
||||
if [[ $failed_count -gt 0 ]]; then
|
||||
log_warning " Failed: $failed_count projects"
|
||||
fi
|
||||
}
|
||||
|
||||
echo -e "${LIGHT_BLUE}[$folder] Starting Docker containers"
|
||||
docker compose up -d > /dev/null
|
||||
#==============================================================================
|
||||
# DOCKER CLEANUP
|
||||
#==============================================================================
|
||||
|
||||
echo -e "${LIGHT_GREEN}[$folder] Updated!"
|
||||
# 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
|
||||
}
|
||||
|
||||
cd $DOCKER_FOLDER
|
||||
done
|
||||
#==============================================================================
|
||||
# MAIN EXECUTION
|
||||
#==============================================================================
|
||||
|
||||
# Run Docker image prune
|
||||
main() {
|
||||
log_step "Starting Docker Container Updater"
|
||||
echo
|
||||
|
||||
# Check requirements
|
||||
check_docker_requirements
|
||||
|
||||
# Perform self-update if enabled
|
||||
perform_self_update "$@"
|
||||
|
||||
# Update all Docker projects
|
||||
update_all_docker_projects
|
||||
|
||||
# Clean up Docker resources
|
||||
cleanup_docker_resources
|
||||
|
||||
echo
|
||||
log_success "Docker container update process completed!"
|
||||
}
|
||||
|
||||
echo -e "${LIGHT_BLUE}Running Docker image prune..."
|
||||
docker image prune -af
|
||||
|
||||
echo -e "${LIGHT_GREEN} All done!"
|
||||
# Execute main function with all arguments
|
||||
main "$@"
|
Reference in New Issue
Block a user