#!/bin/bash ## Set NO_DOCKER_RESTART to prevent restarting docker after upgrade. ## Set ALLOW_DOCKER_CHANGE to allow change your docker version. ## Set TARGET_DOCKER_VERSION to force a version (use cautiously!) exname=${0##*/} if [ "$exname" == "bash" ]; then ## probably launched through ``charm`` if [ -n "$CHARM_NAME" ]; then exname="charm/${CHARM_NAME}" else echo "Error: cannot determine script name" >&2 echo " Please either run this script directly or through \`\`charm\`\`" >&2 exit 1 fi fi for bin in apt-get grep dpkg file lsb_release; do if ! type -p "$bin" >/dev/null; then echo "Error: \`\`$bin\`\` not found" >&2 exit 1 fi done STATIC_DOCKER_URL="https://download.docker.com/linux/static/stable/x86_64" distro=$(lsb_release -is) release=$(lsb_release -rs) unsupported= target_version=${TARGET_DOCKER_VERSION:-24} declare -A distro_supported_version=( [Debian]="9 10 11 12" [Ubuntu]="20.04 22.04" ) if [ "$distro" == "Debian" ]; then release=${release%%.*} fi if [ -z "${distro_supported_version[$distro]}" ] || [[ " ${distro_supported_version[$distro]} " != *" $release "* ]]; then echo "Warning: possibly unsupported distribution/version $distro $release" >&2 echo " Supported distributions and versions:" >&2 for dist in "${!distro_supported_version[@]}"; do echo -n " $dist: " >&2 ## comma separated list versions="${distro_supported_version[$dist]}" versions="${versions// /, }" echo "$versions" >&2 done unsupported=1 fi version_gt() { test "$(echo -e "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; } just_installed= if ! docker_bin=$(type -p docker); then echo "Installing docker..." type -p curl >/dev/null || apt-get install -y curl &2 ## is the script mentioning "/usr/bin/docker" ? if grep -q '/usr/bin/docker' "$docker_bin"; then echo "INFO: docker script is calling '/usr/bin/docker'" >&2 docker_bin=/usr/bin/docker else echo "ERROR: cannot determine docker binary from script '$docker_bin'" >&2 return 1 fi fi [[ "$(ldd "$docker_bin")" == *not\ a\ dynamic\ executable* ]] } is_static_docker_install=0 if is_static_docker "$docker_bin"; then echo "INFO: docker version is static" >&2 is_static_docker_install=1 fi read docker_client_version docker_server_version < <( docker version --format '{{.Client.Version}} {{.Server.Version}}' ) mismatch_client_server= ## issue a warning if client and server versions are different if [ "$docker_client_version" != "$docker_server_version" ]; then echo "WARNING: docker client and server versions differ!" >&2 echo " client: $docker_client_version" >&2 echo " server: $docker_server_version" >&2 mismatch_client_server=1 fi distrib_available_versions=$(apt-cache madison docker-ce | cut -f 2 -d "|" | sort -V) deb_version_to_docker_version() { local deb_version=$1 local docker_version docker_version="${deb_version%%~*}" docker_version="${docker_version%-*}" docker_version="${docker_version#*:}" echo "$docker_version" } declare -A docker_version_to_deb_version=() for deb_version in $distrib_available_versions; do docker_version=$(deb_version_to_docker_version "$deb_version") docker_version_to_deb_version["$docker_version"]="$deb_version" done ## expand docker version to one that is available in deb on the repository candidate_deb_target_version=$( echo "${!docker_version_to_deb_version[@]}" | tr ' ' '\n' | grep "^${target_version}[^\d]" | sort -V | tail -n 1 ) if [ -z "$candidate_deb_target_version" ]; then ## No deb package seems to satisfy the target version # Fetch the HTML content and filter out the versions available_docker_static_versions=$( curl -sL "$STATIC_DOCKER_URL" | grep -oP 'docker-\d+\.\d+\.\d+\.tgz' | cut -d'-' -f2 | cut -d'.' -f1,2,3 | sort -uV ) candidate_target_version=$( echo "$available_docker_static_versions" | grep "^${target_version}" | sort -V | tail -n 1 ) if [ -z "$candidate_target_version" ]; then echo "ERROR: no docker version matching '${target_version}' available" >&2 echo " Available versions for $distro $release:" >&2 all_versions=( $( { echo "${!docker_version_to_deb_version[@]}" | tr ' ' '\n' echo "$available_docker_static_versions" } | cut -f 1,2 -d. | sort -uV ) ) for version in "${all_versions[@]}"; do candidate_deb_target_version=$( echo "${!docker_version_to_deb_version[@]}" | tr ' ' '\n' | grep "^${version}[^\d]" | sort -V | tail -n 1 ) if [ -n "$candidate_deb_target_version" ]; then msg=" with deb" else msg=" with static version" fi printf " - %-10s %s\n" "$version" "$msg" >&2 done echo >&2 echo " 1) You may need to contact your system administrator to upgrade this script." >&2 echo " 2) Meanwhile, if you know what you are doing, you can set yourself a target version" >&2 echo " by setting TARGET_DOCKER_VERSION." >&2 exit 1 fi echo "INFO: target docker version $target_version is not supported by distrib" >&2 echo " Binary static version $candidate_target_version of docker will be used" >&2 max_docker_version_available= for docker_version in ${!docker_version_to_deb_version[@]}; do if version_gt "$docker_version" "$target_version"; then continue fi if version_gt "$docker_version" "$max_docker_version_available"; then max_docker_version_available="$docker_version" fi done if [ -z "$max_docker_version_available" ]; then ## use lowest version available echo "INFO: no inferior docker version available in deb repository" \ "to satisfy target version $target_version" >&2 max_docker_version_available=$( echo "${!docker_version_to_deb_version[@]}" | tr ' ' '\n' | sort -V | head -n 1 ) if [ -z "$max_docker_version_available" ]; then echo "ERROR: no docker version available in deb repository" >&2 exit 1 fi echo "INFO: using lowest docker version available in deb repository" >&2 fi echo "INFO: closest docker version supported by $distro $release:" \ "$(deb_version_to_docker_version "$max_docker_version_available")" >&2 target_deb_version="${docker_version_to_deb_version[$max_docker_version_available]}" target_version=$candidate_target_version echo "INFO: target maximal deb version: '$target_deb_version' (docker version: $( deb_version_to_docker_version "$target_deb_version" ))" >&2 need_static=1 else ## a deb package is available to satisfy the target version target_deb_version="${docker_version_to_deb_version[$candidate_deb_target_version]}" target_version=$candidate_deb_target_version echo "INFO: target docker deb version: '$target_deb_version' " \ "(docker version: $candidate_deb_target_version)" >&2 need_static=0 fi current_docker_ce_package_version=$( dpkg -s docker-ce | grep '^Version:' | cut -f 2 -d ' ' ) current_docker_ce_cli_package_version=$( dpkg -s docker-ce-cli | grep '^Version:' | cut -f 2 -d ' ' ) ## Install deb version if required if [ -z "$mismatch_client_server" ] && [[ "$current_docker_ce_cli_package_version" == "$target_deb_version" ]] && [[ "$current_docker_ce_package_version" == "$target_deb_version" ]]; then if [[ "$need_static" == 0 ]] && [[ "$is_static_docker_install" == 0 ]]; then ## current and target versions match, no upgrade needed echo "INFO: recommended target deb docker version $target_version already installed" >&2 echo " To set a different target version, set TARGET_DOCKER_VERSION" >&2 exit 0 fi ## Check static version and current version matches the target_version if [[ "$need_static" == 1 ]] && [[ "$is_static_docker_install" == 1 ]] && [[ "$target_version" == "$docker_server_version" ]]; then ## current and target versions match, no upgrade needed echo "INFO: recommended target deb and static docker version $target_version already installed" >&2 echo " To set a different target version, set TARGET_DOCKER_VERSION" >&2 exit 0 fi fi ## Should we skip upgrade? if [ -z "$just_installed" ] && [ -z "$ALLOW_DOCKER_CHANGE" ]; then echo "WARN: You are not on the recommended setup (target docker version: $target_version)" >&2 if [[ "$current_docker_ce_package_version" != "$target_deb_version" ]]; then echo "INFO: docker-ce recommended deb version available" >&2 echo " current: $current_docker_ce_package_version" >&2 echo " recommended: $target_deb_version" >&2 fi if [[ "$current_docker_ce_cli_package_version" != "$target_deb_version" ]]; then echo "INFO: docker-ce-cli recommended deb version available" >&2 echo " current: $current_docker_ce_cli_package_version" >&2 echo " recommended: $target_deb_version" >&2 fi if [[ "$need_static" != "$is_static_docker_install" ]]; then if [[ "$need_static" == 1 ]]; then echo "INFO: docker static version recommended to achieved version $target_version" >&2 else echo "INFO: docker deb version recommended, fulfilled by deb version $target_deb_version" >&2 fi fi echo "INFO: To change version, run this script with ALLOW_DOCKER_CHANGE=1" >&2 exit 0 fi install_static_version() { local docker_version="$1" local url="$STATIC_DOCKER_URL/docker-${docker_version}.tgz" local service ## ensure shadowlocal is set up if ! [ -e /etc/profile.d/shadowlocal.sh ]; then cat < /etc/profile.d/shadowlocal.sh || return 1 ## add /opt/merge/bin to PATH if not already there if [[ ":$PATH:" != *":/opt/merge/bin:"* ]]; then export PATH="/opt/merge/bin:\$PATH" fi EOF fi if ! [ -e /etc/systemd/system.conf.d/shadowlocal.sh ]; then mkdir -p /etc/systemd/system.conf.d cat < /etc/systemd/system.conf.d/shadowlocal.sh || return 1 [Manager] DefaultEnvironment=PATH=/opt/merge/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin EOF systemctl daemon-reload fi ## install in /opt/apps/docker local base_install=/opt/apps/docker if [ -d "${base_install}" ]; then if [ -d "${base_install}".old ]; then rm -rfv "${base_install}".old || return 1 fi mv -v "${base_install}"{,.old} || return 1 fi echo "Fetching $url" >&2 mkdir -p "${base_install}"/bin && ( set -o pipefail curl -sSL "$url" | tar -xz -C "${base_install}"/bin --strip-components=1 ) || return 1 for service in docker:dockerd containerd:containerd; do bin=${service#*:} service=${service%%:*} ## get the original ExecStart line if ! orig_exec_start=$(grep -m 1 '^ExecStart=' /lib/systemd/system/${service}.service); then echo "ERROR: cannot find ExecStart in /lib/systemd/system/${service}.service" >&2 return 1 fi ## override the ExecStart line by changing only the binary new_exec_start="$(echo "$orig_exec_start" | sed -r 's|ExecStart=[^ ]+|/opt/merge/bin/'"${bin}"'|')" mkdir -p "${base_install}/etc/systemd/system/${service}.service.d" mkdir -p "/etc/systemd/system/${service}.service.d" cat < "${base_install}/etc/systemd/system/${service}.service.d/docker-static.conf" [Service] ExecStart= ExecStart=$new_exec_start EOF ## override systemd config ln -sf /opt/merge/etc/systemd/system/docker.service.d/docker-static.conf \ /etc/systemd/system/docker.service.d/docker-static.conf done ## Create symlinks to /opt/merge/bin with /opt/apps/docker/bin/* shadowlocal add docker || return 1 ## requires kal-scripts if [ -z "$NO_DOCKER_RESTART" ]; then systemctl daemon-reload && service containerd restart && service docker restart fi } uninstall_static_version() { local base_install=/opt/apps/docker if [ -d "${base_install}" ]; then rm -rfv "${base_install}" || return 1 fi find -L "/opt/merge/bin" -maxdepth 1 -type l -ilname "/opt/apps/docker"/\* -exec echo rm -v {} \; || return 1 for service in docker containerd; do rm -vf "/etc/systemd/system/${service}.service.d/docker-static.conf" || return 1 done } ## if currently static and it doesn't need it anymore, uninstall if [[ "$is_static_docker_install" == 1 ]] && [[ "$need_static" == 0 ]]; then echo "INFO: uninstalling static docker version" >&2 uninstall_static_version || exit 1 fi ## if current docker-ce debian version is not the target debian version, install it if [[ "$current_docker_ce_package_version" != "$target_deb_version" ]]; then echo "INFO: installing docker-ce deb version $target_deb_version" >&2 apt-get install -y --allow-downgrades \ docker-ce="$target_deb_version" &2 apt-get install -y --allow-downgrades \ docker-ce-cli="$target_deb_version" &2 install_static_version "$target_version" || exit 1 fi