You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

396 lines
15 KiB

  1. #!/bin/bash
  2. ## Set NO_DOCKER_RESTART to prevent restarting docker after upgrade.
  3. ## Set ALLOW_DOCKER_CHANGE to allow change your docker version.
  4. ## Set TARGET_DOCKER_VERSION to force a version (use cautiously!)
  5. exname=${0##*/}
  6. if [ "$exname" == "bash" ]; then
  7. ## probably launched through ``charm``
  8. if [ -n "$CHARM_NAME" ]; then
  9. exname="charm/${CHARM_NAME}"
  10. else
  11. echo "Error: cannot determine script name" >&2
  12. echo " Please either run this script directly or through \`\`charm\`\`" >&2
  13. exit 1
  14. fi
  15. fi
  16. for bin in apt-get grep dpkg file lsb_release yq; do
  17. if ! type -p "$bin" >/dev/null; then
  18. echo "Error: \`\`$bin\`\` not found" >&2
  19. exit 1
  20. fi
  21. done
  22. ##
  23. ## Install docker
  24. ##
  25. STATIC_DOCKER_URL="https://download.docker.com/linux/static/stable/x86_64"
  26. distro=$(lsb_release -is)
  27. release=$(lsb_release -rs)
  28. unsupported=
  29. target_version=${TARGET_DOCKER_VERSION:-24}
  30. declare -A distro_supported_version=(
  31. [Debian]="9 10 11 12"
  32. [Ubuntu]="20.04 22.04"
  33. )
  34. if [ "$distro" == "Debian" ]; then
  35. release=${release%%.*}
  36. fi
  37. if [ -z "${distro_supported_version[$distro]}" ] ||
  38. [[ " ${distro_supported_version[$distro]} " != *" $release "* ]]; then
  39. echo "Warning: possibly unsupported distribution/version $distro $release" >&2
  40. echo " Supported distributions and versions:" >&2
  41. for dist in "${!distro_supported_version[@]}"; do
  42. echo -n " $dist: " >&2
  43. ## comma separated list
  44. versions="${distro_supported_version[$dist]}"
  45. versions="${versions// /, }"
  46. echo "$versions" >&2
  47. done
  48. unsupported=1
  49. fi
  50. version_gt() { test "$(echo -e "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; }
  51. just_installed=
  52. if ! docker_bin=$(type -p docker); then
  53. echo "Installing docker..."
  54. type -p curl >/dev/null ||
  55. apt-get install -y curl </dev/null
  56. curl -sSL https://get.docker.io | sh || exit 1
  57. just_installed=1
  58. fi
  59. is_static_docker() {
  60. local docker_bin=$1
  61. # check if this is a link
  62. [[ -L "$docker_bin" ]] && docker_bin=$(readlink -f "$docker_bin")
  63. ## XXXvlab: We need to handle a special case that happens on some systems
  64. ## having installed 0k-docker package that divert the docker
  65. ## binary to a shell script that then calls docker
  66. ## check if this a shell script (using file)
  67. if file "$docker_bin" | grep -q shell; then
  68. echo "INFO: using a shell script for docker in '$docker_bin'" >&2
  69. ## is the script mentioning "/usr/bin/docker" ?
  70. if grep -q '/usr/bin/docker' "$docker_bin"; then
  71. echo "INFO: docker script is calling '/usr/bin/docker'" >&2
  72. docker_bin=/usr/bin/docker
  73. else
  74. echo "ERROR: cannot determine docker binary from script '$docker_bin'" >&2
  75. return 1
  76. fi
  77. fi
  78. [[ "$(ldd "$docker_bin")" == *not\ a\ dynamic\ executable* ]]
  79. }
  80. is_static_docker_install=0
  81. if is_static_docker "$docker_bin"; then
  82. echo "INFO: docker version is static" >&2
  83. is_static_docker_install=1
  84. fi
  85. read docker_client_version docker_server_version < <(
  86. docker version --format '{{.Client.Version}} {{.Server.Version}}'
  87. )
  88. mismatch_client_server=
  89. ## issue a warning if client and server versions are different
  90. if [ "$docker_client_version" != "$docker_server_version" ]; then
  91. echo "WARNING: docker client and server versions differ!" >&2
  92. echo " client: $docker_client_version" >&2
  93. echo " server: $docker_server_version" >&2
  94. mismatch_client_server=1
  95. fi
  96. distrib_available_versions=$(apt-cache madison docker-ce | cut -f 2 -d "|" | sort -V)
  97. deb_version_to_docker_version() {
  98. local deb_version=$1
  99. local docker_version
  100. docker_version="${deb_version%%~*}"
  101. docker_version="${docker_version%-*}"
  102. docker_version="${docker_version#*:}"
  103. echo "$docker_version"
  104. }
  105. declare -A docker_version_to_deb_version=()
  106. for deb_version in $distrib_available_versions; do
  107. docker_version=$(deb_version_to_docker_version "$deb_version")
  108. docker_version_to_deb_version["$docker_version"]="$deb_version"
  109. done
  110. ## expand docker version to one that is available in deb on the repository
  111. candidate_deb_target_version=$(
  112. echo "${!docker_version_to_deb_version[@]}" |
  113. tr ' ' '\n' | grep "^${target_version}[^\d]" | sort -V | tail -n 1
  114. )
  115. if [ -z "$candidate_deb_target_version" ]; then
  116. ## No deb package seems to satisfy the target version
  117. # Fetch the HTML content and filter out the versions
  118. available_docker_static_versions=$(
  119. curl -sL "$STATIC_DOCKER_URL" | grep -oP 'docker-\d+\.\d+\.\d+\.tgz' |
  120. cut -d'-' -f2 | cut -d'.' -f1,2,3 | sort -uV
  121. )
  122. candidate_target_version=$(
  123. echo "$available_docker_static_versions" |
  124. grep "^${target_version}" | sort -V | tail -n 1
  125. )
  126. if [ -z "$candidate_target_version" ]; then
  127. echo "ERROR: no docker version matching '${target_version}' available" >&2
  128. echo " Available versions for $distro $release:" >&2
  129. all_versions=(
  130. $(
  131. {
  132. echo "${!docker_version_to_deb_version[@]}" | tr ' ' '\n'
  133. echo "$available_docker_static_versions"
  134. } | cut -f 1,2 -d. | sort -uV
  135. )
  136. )
  137. for version in "${all_versions[@]}"; do
  138. candidate_deb_target_version=$(
  139. echo "${!docker_version_to_deb_version[@]}" |
  140. tr ' ' '\n' | grep "^${version}[^\d]" | sort -V | tail -n 1
  141. )
  142. if [ -n "$candidate_deb_target_version" ]; then
  143. msg=" with deb"
  144. else
  145. msg=" with static version"
  146. fi
  147. printf " - %-10s %s\n" "$version" "$msg" >&2
  148. done
  149. echo >&2
  150. echo " 1) You may need to contact your system administrator to upgrade this script." >&2
  151. echo " 2) Meanwhile, if you know what you are doing, you can set yourself a target version" >&2
  152. echo " by setting TARGET_DOCKER_VERSION." >&2
  153. exit 1
  154. fi
  155. echo "INFO: target docker version $target_version is not supported by distrib" >&2
  156. echo " Binary static version $candidate_target_version of docker will be used" >&2
  157. max_docker_version_available=
  158. for docker_version in ${!docker_version_to_deb_version[@]}; do
  159. if version_gt "$docker_version" "$target_version"; then
  160. continue
  161. fi
  162. if version_gt "$docker_version" "$max_docker_version_available"; then
  163. max_docker_version_available="$docker_version"
  164. fi
  165. done
  166. if [ -z "$max_docker_version_available" ]; then
  167. ## use lowest version available
  168. echo "INFO: no inferior docker version available in deb repository" \
  169. "to satisfy target version $target_version" >&2
  170. max_docker_version_available=$(
  171. echo "${!docker_version_to_deb_version[@]}" |
  172. tr ' ' '\n' |
  173. sort -V |
  174. head -n 1
  175. )
  176. if [ -z "$max_docker_version_available" ]; then
  177. echo "ERROR: no docker version available in deb repository" >&2
  178. exit 1
  179. fi
  180. echo "INFO: using lowest docker version available in deb repository" >&2
  181. fi
  182. echo "INFO: closest docker version supported by $distro $release:" \
  183. "$(deb_version_to_docker_version "$max_docker_version_available")" >&2
  184. target_deb_version="${docker_version_to_deb_version[$max_docker_version_available]}"
  185. target_version=$candidate_target_version
  186. echo "INFO: target maximal deb version: '$target_deb_version' (docker version: $(
  187. deb_version_to_docker_version "$target_deb_version"
  188. ))" >&2
  189. need_static=1
  190. else
  191. ## a deb package is available to satisfy the target version
  192. target_deb_version="${docker_version_to_deb_version[$candidate_deb_target_version]}"
  193. target_version=$candidate_deb_target_version
  194. echo "INFO: target docker deb version: '$target_deb_version' " \
  195. "(docker version: $candidate_deb_target_version)" >&2
  196. need_static=0
  197. fi
  198. current_docker_ce_package_version=$(
  199. dpkg -s docker-ce | grep '^Version:' | cut -f 2 -d ' '
  200. )
  201. current_docker_ce_cli_package_version=$(
  202. dpkg -s docker-ce-cli | grep '^Version:' | cut -f 2 -d ' '
  203. )
  204. ## Install deb version if required
  205. if [ -z "$mismatch_client_server" ] &&
  206. [[ "$current_docker_ce_cli_package_version" == "$target_deb_version" ]] &&
  207. [[ "$current_docker_ce_package_version" == "$target_deb_version" ]]; then
  208. if [[ "$need_static" == 0 ]] && [[ "$is_static_docker_install" == 0 ]]; then
  209. ## current and target versions match, no upgrade needed
  210. echo "INFO: recommended target deb docker version $target_version already installed" >&2
  211. echo " To set a different target version, set TARGET_DOCKER_VERSION" >&2
  212. exit 0
  213. fi
  214. ## Check static version and current version matches the target_version
  215. if [[ "$need_static" == 1 ]] && [[ "$is_static_docker_install" == 1 ]] &&
  216. [[ "$target_version" == "$docker_server_version" ]]; then
  217. ## current and target versions match, no upgrade needed
  218. echo "INFO: recommended target deb and static docker version $target_version already installed" >&2
  219. echo " To set a different target version, set TARGET_DOCKER_VERSION" >&2
  220. exit 0
  221. fi
  222. fi
  223. ## Should we skip upgrade?
  224. if [ -z "$just_installed" ] && [ -z "$ALLOW_DOCKER_CHANGE" ]; then
  225. echo "WARN: You are not on the recommended setup (target docker version: $target_version)" >&2
  226. if [[ "$current_docker_ce_package_version" != "$target_deb_version" ]]; then
  227. echo "INFO: docker-ce recommended deb version available" >&2
  228. echo " current: $current_docker_ce_package_version" >&2
  229. echo " recommended: $target_deb_version" >&2
  230. fi
  231. if [[ "$current_docker_ce_cli_package_version" != "$target_deb_version" ]]; then
  232. echo "INFO: docker-ce-cli recommended deb version available" >&2
  233. echo " current: $current_docker_ce_cli_package_version" >&2
  234. echo " recommended: $target_deb_version" >&2
  235. fi
  236. if [[ "$need_static" != "$is_static_docker_install" ]]; then
  237. if [[ "$need_static" == 1 ]]; then
  238. echo "INFO: docker static version recommended to achieved version $target_version" >&2
  239. else
  240. echo "INFO: docker deb version recommended, fulfilled by deb version $target_deb_version" >&2
  241. fi
  242. fi
  243. echo "INFO: To change version, run this script with ALLOW_DOCKER_CHANGE=1" >&2
  244. exit 0
  245. fi
  246. install_static_version() {
  247. local docker_version="$1"
  248. local url="$STATIC_DOCKER_URL/docker-${docker_version}.tgz"
  249. local service
  250. ## ensure shadowlocal is set up
  251. if ! [ -e /etc/profile.d/shadowlocal.sh ]; then
  252. cat <<EOF > /etc/profile.d/shadowlocal.sh || return 1
  253. ## add /opt/merge/bin to PATH if not already there
  254. if [[ ":$PATH:" != *":/opt/merge/bin:"* ]]; then
  255. export PATH="/opt/merge/bin:\$PATH"
  256. fi
  257. EOF
  258. fi
  259. if ! [ -e /etc/systemd/system.conf.d/shadowlocal.sh ]; then
  260. mkdir -p /etc/systemd/system.conf.d
  261. cat <<EOF > /etc/systemd/system.conf.d/shadowlocal.sh || return 1
  262. [Manager]
  263. DefaultEnvironment=PATH=/opt/merge/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  264. EOF
  265. systemctl daemon-reload
  266. fi
  267. ## install in /opt/apps/docker
  268. local base_install=/opt/apps/docker
  269. if [ -d "${base_install}" ]; then
  270. if [ -d "${base_install}".old ]; then
  271. rm -rfv "${base_install}".old || return 1
  272. fi
  273. mv -v "${base_install}"{,.old} || return 1
  274. fi
  275. echo "Fetching $url" >&2
  276. mkdir -p "${base_install}"/bin &&
  277. (
  278. set -o pipefail
  279. curl -sSL "$url" | tar -xz -C "${base_install}"/bin --strip-components=1
  280. ) || return 1
  281. for service in docker:dockerd containerd:containerd; do
  282. bin=${service#*:}
  283. service=${service%%:*}
  284. ## get the original ExecStart line
  285. if ! orig_exec_start=$(grep -m 1 '^ExecStart=' /lib/systemd/system/${service}.service); then
  286. echo "ERROR: cannot find ExecStart in /lib/systemd/system/${service}.service" >&2
  287. return 1
  288. fi
  289. ## override the ExecStart line by changing only the binary
  290. new_exec_start="$(echo "$orig_exec_start" | sed -r 's|ExecStart=[^ ]+|/opt/merge/bin/'"${bin}"'|')"
  291. mkdir -p "${base_install}/etc/systemd/system/${service}.service.d"
  292. mkdir -p "/etc/systemd/system/${service}.service.d"
  293. cat <<EOF > "${base_install}/etc/systemd/system/${service}.service.d/docker-static.conf"
  294. [Service]
  295. ExecStart=
  296. ExecStart=$new_exec_start
  297. EOF
  298. ## override systemd config
  299. ln -sf /opt/merge/etc/systemd/system/docker.service.d/docker-static.conf \
  300. /etc/systemd/system/docker.service.d/docker-static.conf
  301. done
  302. ## Create symlinks to /opt/merge/bin with /opt/apps/docker/bin/*
  303. shadowlocal add docker || return 1 ## requires kal-scripts
  304. if [ -z "$NO_DOCKER_RESTART" ]; then
  305. systemctl daemon-reload &&
  306. service containerd restart &&
  307. service docker restart
  308. fi
  309. }
  310. uninstall_static_version() {
  311. local base_install=/opt/apps/docker
  312. if [ -d "${base_install}" ]; then
  313. rm -rfv "${base_install}" || return 1
  314. fi
  315. find -L "/opt/merge/bin" -maxdepth 1 -type l -ilname "/opt/apps/docker"/\* -exec echo rm -v {} \; || return 1
  316. for service in docker containerd; do
  317. rm -vf "/etc/systemd/system/${service}.service.d/docker-static.conf" || return 1
  318. done
  319. }
  320. ## if currently static and it doesn't need it anymore, uninstall
  321. if [[ "$is_static_docker_install" == 1 ]] && [[ "$need_static" == 0 ]]; then
  322. echo "INFO: uninstalling static docker version" >&2
  323. uninstall_static_version || exit 1
  324. fi
  325. ## if current docker-ce debian version is not the target debian version, install it
  326. if [[ "$current_docker_ce_package_version" != "$target_deb_version" ]]; then
  327. echo "INFO: installing docker-ce deb version $target_deb_version" >&2
  328. apt-get install -y --allow-downgrades \
  329. docker-ce="$target_deb_version" </dev/null || exit 1
  330. if [ -z "$NO_DOCKER_RESTART" ]; then
  331. systemctl daemon-reload &&
  332. service containerd restart &&
  333. service docker restart
  334. fi
  335. fi
  336. ## if current docker-ce-cli debian version is not the target debian version, install it
  337. if [[ "$current_docker_ce_cli_package_version" != "$target_deb_version" ]]; then
  338. echo "INFO: installing docker-ce-cli deb version $target_deb_version" >&2
  339. apt-get install -y --allow-downgrades \
  340. docker-ce-cli="$target_deb_version" </dev/null || exit 1
  341. fi
  342. ## if we need a static version and it is not installed, install it
  343. ## and if we need a static version and it is installed but not the target version, install it
  344. if [[ "$need_static" == 1 ]] && [[ "$is_static_docker_install" == 0 || "$target_version" != "$docker_server_version" ]]; then
  345. echo "INFO: installing static docker version $target_version" >&2
  346. install_static_version "$target_version" || exit 1
  347. fi