Created
March 1, 2025 14:03
-
-
Save mateo08c/ccebc102d8f990e28c7c24d6352d92b6 to your computer and use it in GitHub Desktop.
Smart Immich Docker Updater
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Smart Immich Docker Updater | |
# An intelligent update script for Immich | |
# | |
# This script checks the current version of your Immich installation, | |
# compares it with the latest version available on GitHub, | |
# and performs the update only if necessary. | |
# | |
# Usage: ./update-immich.sh [options] [path/to/docker-compose.yml] | |
# Options: | |
# -h, --help Display help | |
# -f, --force Force update even if version is up to date | |
# Define bash shell path | |
SHELL=/bin/bash | |
# Colors for logging (Linux format) | |
GREEN="\e[32m" | |
YELLOW="\e[33m" | |
RED="\e[31m" | |
BLUE="\e[34m" | |
RESET="\e[0m" | |
# Logging functions | |
log() { | |
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${RESET}" | |
} | |
info() { | |
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $1${RESET}" | |
} | |
warn() { | |
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1${RESET}" | |
} | |
error() { | |
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1${RESET}" >&2 | |
} | |
# Global variables | |
CURRENT_VERSION="" | |
LATEST_VERSION="" | |
FORCE_UPDATE=false | |
# Check if running as root or sudo | |
if [ "$EUID" -ne 0 ]; then | |
error "This script must be run as root or with sudo" | |
exit 1 | |
fi | |
# Check if Docker is installed | |
if ! command -v docker >/dev/null 2>&1; then | |
error "Docker is not installed" | |
exit 1 | |
fi | |
# Check if curl is installed | |
if ! command -v curl >/dev/null 2>&1; then | |
error "curl is not installed" | |
exit 1 | |
fi | |
# Check if jq is installed | |
if ! command -v jq >/dev/null 2>&1; then | |
warn "jq is not installed - version checking will be limited" | |
JQ_INSTALLED=false | |
else | |
JQ_INSTALLED=true | |
fi | |
# Function to check the latest version on GitHub | |
check_latest_version() { | |
info "Checking latest version on GitHub..." | |
if $JQ_INSTALLED; then | |
# Use GitHub API to get the latest version | |
LATEST_VERSION=$(curl -s "https://api.github.com/repos/immich-app/immich/releases/latest" | jq -r .tag_name) | |
if [ -z "$LATEST_VERSION" ] || [ "$LATEST_VERSION" == "null" ]; then | |
warn "Unable to retrieve latest version via GitHub API" | |
return 1 | |
fi | |
info "Latest version on GitHub: $LATEST_VERSION" | |
return 0 | |
else | |
warn "jq is not installed, unable to automatically check the latest version" | |
warn "Check https://github.com/immich-app/immich/releases for the latest versions" | |
return 1 | |
fi | |
} | |
# Function to check current version | |
check_current_version() { | |
info "Checking current version..." | |
# Check if the immich_server container exists | |
if ! docker ps -a | grep -q immich_server; then | |
warn "immich_server container not found, unable to check current version" | |
return 1 | |
fi | |
# Get current version | |
local version=$(docker inspect immich_server | grep -oP '(?<=IMMICH_VERSION=)[^"]+' || echo "") | |
local build=$(docker inspect immich_server | grep -oP '(?<=IMMICH_BUILD_IMAGE=)[^"]+' || echo "") | |
if [ -z "$version" ] && [ -z "$build" ]; then | |
warn "Unable to determine current Immich version" | |
return 1 | |
fi | |
if [ -n "$version" ]; then | |
info "Current version: $version" | |
fi | |
if [ -n "$build" ]; then | |
CURRENT_VERSION="$build" | |
info "Current build: $CURRENT_VERSION" | |
return 0 | |
elif [ -n "$version" ]; then | |
CURRENT_VERSION="$version" | |
return 0 | |
fi | |
return 1 | |
} | |
# Function to compare versions | |
version_compare() { | |
# Remove initial 'v' if present to normalize comparison | |
local ver1=$(echo "$1" | sed 's/^v//') | |
local ver2=$(echo "$2" | sed 's/^v//') | |
if [ "$ver1" = "$ver2" ]; then | |
# Identical versions | |
return 0 | |
fi | |
# If jq is installed, we can do a more precise comparison | |
if $JQ_INSTALLED; then | |
# Use sort to compare semantic versions | |
if [ "$(printf '%s\n' "$ver1" "$ver2" | sort -V | head -n1)" = "$ver1" ]; then | |
# ver1 is less than ver2 | |
return 1 | |
else | |
# ver1 is greater than ver2 | |
return 2 | |
fi | |
else | |
# Simplified version without jq | |
# If they're the same strings after removing 'v', it's the same version | |
if [ "$ver1" = "$ver2" ]; then | |
return 0 | |
else | |
# If we can't compare precisely, assume an update is needed | |
return 1 | |
fi | |
fi | |
} | |
# Function to create a backup before updating | |
backup_data() { | |
local compose_dir="$1" | |
local backup_dir="${compose_dir}/backup_$(date '+%Y%m%d%H%M%S')" | |
log "Creating a configuration backup..." | |
mkdir -p "$backup_dir" | |
# Copy docker-compose.yml file | |
cp "${compose_dir}/docker-compose.yml" "${backup_dir}/" || { | |
warn "Unable to backup docker-compose.yml" | |
} | |
# Copy .env file if it exists | |
if [ -f "${compose_dir}/.env" ]; then | |
cp "${compose_dir}/.env" "${backup_dir}/" || { | |
warn "Unable to backup .env" | |
} | |
fi | |
log "Backup created in ${backup_dir}" | |
} | |
# Main update function | |
update_containers() { | |
local compose_file="$1" | |
local compose_dir | |
compose_dir=$(dirname "$compose_file") | |
# Move to the docker-compose directory | |
if ! cd "$compose_dir"; then | |
error "Unable to access directory $compose_dir" | |
return 1 | |
fi | |
# Create a backup | |
backup_data "$compose_dir" | |
log "Pulling new images..." | |
docker compose pull || { | |
error "Error while pulling images" | |
return 1 | |
} | |
log "Stopping containers..." | |
docker compose down || { | |
error "Error while stopping containers" | |
return 1 | |
} | |
log "Cleaning stopped containers and unused images..." | |
docker container prune -f | |
docker image prune -f | |
log "Restarting containers..." | |
docker compose up -d || { | |
error "Error while restarting containers" | |
return 1 | |
} | |
log "Container status:" | |
docker compose ps | |
# Post-update verification | |
if docker ps | grep -q immich_server; then | |
log "Immich update successful!" | |
# Wait for the server to stabilize | |
info "Waiting 30 seconds for the server to start..." | |
sleep 30 | |
# Check the new version | |
NEW_VERSION=$(docker inspect immich_server | grep -oP '(?<=IMMICH_VERSION=)[^"]+' || echo "") | |
NEW_BUILD=$(docker inspect immich_server | grep -oP '(?<=IMMICH_BUILD_IMAGE=)[^"]+' || echo "") | |
if [ -n "$NEW_VERSION" ]; then | |
log "New version installed: $NEW_VERSION" | |
fi | |
if [ -n "$NEW_BUILD" ]; then | |
log "New build installed: $NEW_BUILD" | |
fi | |
else | |
error "The immich_server container does not appear to be running after the update" | |
return 1 | |
fi | |
return 0 | |
} | |
# Help function | |
show_help() { | |
echo "Usage: $0 [options] [path/to/docker-compose.yml]" | |
echo | |
echo "Options:" | |
echo " -h, --help Show this help" | |
echo " -f, --force Force update even if version is up to date" | |
echo | |
echo "If no path is specified, the script will look for docker-compose.yml in the current directory." | |
} | |
# Argument processing | |
while [ $# -gt 0 ]; do | |
case "$1" in | |
-h|--help) | |
show_help | |
exit 0 | |
;; | |
-f|--force) | |
FORCE_UPDATE=true | |
shift | |
;; | |
*) | |
if [ -z "$COMPOSE_FILE" ]; then | |
COMPOSE_FILE="$1" | |
else | |
error "Too many arguments. Run '$0 --help' for more information." | |
exit 1 | |
fi | |
shift | |
;; | |
esac | |
done | |
# Define compose file path if not already defined | |
if [ -z "$COMPOSE_FILE" ]; then | |
# No argument, look for docker-compose.yml in current directory | |
COMPOSE_FILE="./docker-compose.yml" | |
fi | |
# Check if file exists and is readable | |
if [ ! -r "$COMPOSE_FILE" ]; then | |
error "File $COMPOSE_FILE does not exist or is not readable" | |
exit 1 | |
fi | |
# Check if the file is an Immich docker-compose | |
if ! grep -q "immich" "$COMPOSE_FILE"; then | |
warn "File $COMPOSE_FILE does not appear to be an Immich docker-compose" | |
read -p "Do you want to continue anyway? (y/n): " choice | |
if [ "$choice" != "y" ] && [ "$choice" != "Y" ]; then | |
info "Update cancelled" | |
exit 0 | |
fi | |
fi | |
# Check available versions | |
check_current_version | |
check_latest_version | |
# Compare versions if both were successfully retrieved | |
if [ -n "$CURRENT_VERSION" ] && [ -n "$LATEST_VERSION" ]; then | |
version_compare "$CURRENT_VERSION" "$LATEST_VERSION" | |
COMPARE_RESULT=$? | |
if [ $COMPARE_RESULT -eq 0 ]; then | |
log "Immich is already up to date (version $CURRENT_VERSION)" | |
if ! $FORCE_UPDATE; then | |
info "No update necessary. Use the -f option to force update." | |
exit 0 | |
else | |
warn "Forced update requested. Continuing despite up-to-date version." | |
fi | |
elif [ $COMPARE_RESULT -eq 2 ]; then | |
warn "Current version ($CURRENT_VERSION) is newer than GitHub version ($LATEST_VERSION)" | |
if ! $FORCE_UPDATE; then | |
read -p "Do you want to update anyway? (y/n): " choice | |
if [ "$choice" != "y" ] && [ "$choice" != "Y" ]; then | |
info "Update cancelled" | |
exit 0 | |
fi | |
fi | |
else | |
log "An update is available: $CURRENT_VERSION -> $LATEST_VERSION" | |
fi | |
else | |
warn "Unable to compare versions. Continuing with update..." | |
fi | |
# Execute the update | |
log "Starting container update for $COMPOSE_FILE" | |
if update_containers "$COMPOSE_FILE"; then | |
log "Update completed successfully" | |
else | |
error "Update failed" | |
exit 1 | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment