Skip to content

Instantly share code, notes, and snippets.

@mateo08c
Created March 1, 2025 14:03
Show Gist options
  • Save mateo08c/ccebc102d8f990e28c7c24d6352d92b6 to your computer and use it in GitHub Desktop.
Save mateo08c/ccebc102d8f990e28c7c24d6352d92b6 to your computer and use it in GitHub Desktop.
Smart Immich Docker Updater
#!/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