#!/bin/bash ##Script to update Docker container images occasionally and alert when update is done. source /root/.bash_profile arg0=$(basename "$0") #This is where containers live CONTAINER_DIR="/var/Red-Vol/Media/Containers/" #Show help if arguments are misused usage() { exec 1>2 # Send standard output to standard error help exit 1 } flag_error() { echo -e "$arg0: $*." >&2 help exit 1 } help() { echo "$arg0 - show how long a process has been running" echo " {-s|--stack} [name] -- Run update process just for specified stack (ex: vikunja)" echo " {-v|--verbose|verbose} -- Print debug messages" echo " {-h|--help} -- Print this help message and exit" exit 0 } #Pass arguments to the script flags() { while test $# -gt 0 do case "$1" in #If a stack is specified, run the process for that stack only (-s|--stack) shift if [[ -d $CONTAINER_DIR/$1 ]]; then export CONTAINER_PATHS="$CONTAINER_DIR/$1" else CONTAINER_PATHS="$(find /var/Red-Vol/Media/Containers/ -maxdepth 1 -type d -name "*$1*" | head -1)" if [[ -n $CONTAINER_PATHS ]]; then export CONTAINER_PATHS fi fi shift;; (-v|--verbose|verbose) export VERBOSE="1" # Okiru looks for^ this variable shift;; (-h|--help) help;; (*) help;; esac done } flags "$@" if [[ -n $verbose ]]; then source /home/shmick/Scripts/Okiru "$verbose" else source /home/shmick/Scripts/Okiru fi source /etc/environment #Remember where you are to change back to later LOCAL_DIR=`pwd` declare -a CONTAINER_PATHS declare -a OUTDATED_CONTAINERS declare -a FAILED_CONTAINERS declare -a UPDATED_CONTAINERS #Remove log from last runs, if present if [[ -f /tmp/docker-updated ]]; then rm /tmp/docker-updated fi #Start counting how many containers fail; appears in final mail subject. OUTDATED_COUNT="0" #Check if path is already set by user specified stack; otherwise, find all containers. if [[ -z $CONTAINER_PATHS ]]; then CONTAINER_PATHS=$(find $CONTAINER_DIR -maxdepth 2 -type f -name docker-compose.yml ! -path '*Archive*' | xargs dirname ) fi #Find containers in ^ base dir ^ in base container path ^ by finding compose files ^ (not here) ^ and getting their directory name. for container_path in ${CONTAINER_PATHS[@]}; do cd $container_path debug "Working on container directory" "$container_path" container_stack=$(basename $container_path) info "Working on stack" "$container_stack" #It's deadly to update tagless database images; this line is safe because it only catches tagged images. container_images="$(cat $container_path/docker-compose.yml | grep -E "image: ([a-z]+)((/)|(:))([a-z]+)?(:)?([a-z0-9].*$)?" | awk '{print $2}')" # search for a pattern of something:something with optional :tag print ^ image name for container_image in $container_images; do debug "$container_stack has image" "$container_image" debug "echo $container_image | awk -F/ '{print \$2}' | sed 's/\:.*//'" container_name="$(echo $container_image | awk -F/ '{print $2}' | sed "s/\:.*//")" # remove everything after the : ^ if [[ -z $container_name ]]; then #&& [[ -n $(echo $container_image | grep -Ev 'postgres|mariadb') ]]; then export container_name="$container_image" fi debug "$container_image has name" "$container_name" if [[ -n $(echo $container_image | grep -E "(.*:[a-z0-9].*$)") ]]; then # check if there is a :tag present ^ image_tag=":$(echo $container_image | awk -F: '{print $NF}')" # !! Add : ^ before image !! so it is only added to later commands if there is an image at all debug "$container_image has tag" "$image_tag" export container_image=$(echo $container_image | awk -F: '{print $1}') # If the container does have a tag, keep the base name ^ without it (before the :) export container_name=$(echo $container_name | awk -F: '{print $1}') fi debug "Fetching local image checksum with:" "docker inspect \"$container_image$image_tag\" | grep -Eo \"($container_image@)?sha256:([0-9a-zA-Z].*)(\\\")\" | sed -e 's/\"//g' | awk -F@ '{print \$2}" local_image=$(docker inspect "$container_image$image_tag" | grep -Eo "($container_image@)?sha256:([0-9a-zA-Z].*)(\")" | sed -e 's/"//g' -e 's/\s+//g' | awk -F@ '{print $2}') # remember, this bit ^ is empty without an image ^ this is the main image checksum remove ^ " and whitespace and^ get the checksum after the @ if [[ -z $local_image ]]; then error "Error fetching local image checksum for container $container_name!" #The script will complain about failed containers later on FAILED_CONTAINERS+=("(local) $container_name") continue 2 else debug "Local SHA256 for $container_image is" "$local_image" fi debug "Fetching remote image with:" "skopeo inspect --creds \"dkd6:Vulthuryol569\" docker://docker.io/$container_image$image_tag | grep Digest | head -1 | grep -Eo 'sha256:([0-9a-zA-Z].*)(\")' | sed -e 's/\"//g'" #Use Skopeo, a Red Hat tool, with my Docker Hub account to register the remote image checksum remote_image=$(skopeo inspect --creds "dkd6:Vulthuryol569" docker://docker.io/$container_image$image_tag | grep Digest | head -1 | grep -Eo 'sha256:([0-9a-zA-Z].*)(")' | sed -e 's/"//g' -e 's/\s+//g' ) #Sometimes; Docker hub hangs up; try again if you failed if [[ -z $remote_image ]]; then remote_image=$(skopeo inspect --creds "dkd6:Vulthuryol569" docker://docker.io/$container_image$image_tag | grep Digest | head -1 | grep -Eo 'sha256:([0-9a-zA-Z].*)(")' | sed -e 's/"//g') fi #Now, if you still don't have an image after the second try, something's fuckey. if [[ -z $remote_image ]]; then error "Error fetching remote image checksum for container" "$container_name!" FAILED_CONTAINERS+=("(remote) $container_name") continue 2 else debug "Remote SHA256 for $container_image is" "$remote_image" fi #If we have both checksums, compare them; they should be identical, or the container is outdated. if [[ -n $local_image ]] && [[ -n $remote_image ]] && [[ "$local_image" =~ "$remote_image" ]]; then ok "$container_name" "is up to date!" else warn "$container_name" "is out of date!" debug "cat \"$container_path/docker-compose.yml\" | grep -B1 \"image: $container_image\" | head -1 | sed -e 's/^[ \t]*//' -e 's/://g' | awk '{print \$NF}')" service=$(cat "$container_path/docker-compose.yml" | grep -B1 "image: $container_image" | head -1 | sed -e 's/^[ \t]*//' -e 's/://g' | grep -v 'container_name') # get container service name (1 line above image) ^ print service name^ ^ omit tabs and : and ^omit container_name info "Attempting to update service" "$service" if docker-compose pull $service; then info "Pulled latest image for" "$container_name" if docker-compose up -d --remove-orphans; then ok "$container_stack" "has been updated sucessfully!" UPDATED_CONTAINERS+=("($container_stack) $container_name") else error "Failed to update" "$container_name!" FAILED_CONTAINERS+=("($container_stack|update) $container_name") #Add to array for mail report OUTDATED_CONTAINERS+=("($container_stack) $container_name") export OUTDATED_COUNT=$(($OUTDATED_COUNT+1)) fi else error "Failed to pull image for" "$container_name!" FAILED_CONTAINERS+=("($container_stack|pull) $container_name") #Add to array for mail report OUTDATED_CONTAINERS+=("($container_stack) $container_name") export OUTDATED_COUNT=$(($OUTDATED_COUNT+1)) fi fi #If you found an image tag, reset it before moving on to another container image_tag="" done cd $LOCAL_DIR done echo "All done!" #Iterate over the arrays and output results to a file; if needed, it will be neatly mailed. if [[ ${#OUTDATED_CONTAINERS[@]} != 0 ]] || [[ ${#FAILED_CONTAINERS[@]} != 0 ]] || [[ ${#UPDATED_CONTAINERS[@]} != 0 ]]; then if [[ ${#UPDATED_CONTAINERS[@]} != 0 ]]; then printf "The following containers have updated succefully:\n" > /tmp/docker-updated printf "%s\n" "${UPDATED_CONTAINERS[@]}" >> /tmp/docker-updated fi if [[ ${#OUTDATED_CONTAINERS[@]} != 0 ]]; then printf "The following containers are out of date:\n" >> /tmp/docker-updated printf "%s\n" "${OUTDATED_CONTAINERS[@]}" >> /tmp/docker-updated fi if [[ ${#FAILED_CONTAINERS[@]} != 0 ]]; then printf "The following containers failed the update process:\n" >> /tmp/docker-updated printf "%s\n" "${FAILED_CONTAINERS[@]}" >> /tmp/docker-updated fi cat /tmp/docker-updated | mail -s "Docker containers update report - `date +"%B %d %Y"`" -r "Takahe@pukeko.xyz" matanhorovitz@protonmail.com rm /tmp/docker-updated fi