Files
LinuxMirrors/DockerInstallation.sh
2025-05-12 04:55:37 +08:00

1684 lines
66 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
## Author: SuperManito
## Modified: 2025-05-12
## License: MIT
## GitHub: https://github.com/SuperManito/LinuxMirrors
## Website: https://linuxmirrors.cn
## Docker CE 软件源列表
# 格式:"软件源名称@软件源地址"
mirror_list_docker_ce=(
"阿里云@mirrors.aliyun.com/docker-ce"
"腾讯云@mirrors.tencent.com/docker-ce"
"华为云@mirrors.huaweicloud.com/docker-ce"
"微软 Azure 中国@mirror.azure.cn/docker-ce"
"网易@mirrors.163.com/docker-ce"
"火山引擎@mirrors.volces.com/docker"
"清华大学@mirrors.tuna.tsinghua.edu.cn/docker-ce"
"北京大学@mirrors.pku.edu.cn/docker-ce"
"南京大学@mirrors.nju.edu.cn/docker-ce"
"上海交通大学@mirror.sjtu.edu.cn/docker-ce"
"中国科学技术大学@mirrors.ustc.edu.cn/docker-ce"
"中国科学院软件研究所@mirror.iscas.ac.cn/docker-ce"
"官方@download.docker.com"
)
## Docker Registry 仓库列表
# 格式:"软件源名称@软件源地址"
mirror_list_registry=(
"毫秒镜像@docker.1ms.run"
"轩辕镜像@docker.xuanyuan.me"
"Docker Proxy@dockerproxy.net"
"道客 DaoCloud@docker.m.daocloud.io"
"1Panel 镜像@docker.1panel.live"
"阿里云(杭州)@registry.cn-hangzhou.aliyuncs.com"
"阿里云(上海)@registry.cn-shanghai.aliyuncs.com"
"阿里云(青岛)@registry.cn-qingdao.aliyuncs.com"
"阿里云(北京)@registry.cn-beijing.aliyuncs.com"
"阿里云(张家口)@registry.cn-zhangjiakou.aliyuncs.com"
"阿里云(呼和浩特)@registry.cn-huhehaote.aliyuncs.com"
"阿里云(乌兰察布)@registry.cn-wulanchabu.aliyuncs.com"
"阿里云(深圳)@registry.cn-shenzhen.aliyuncs.com"
"阿里云(河源)@registry.cn-heyuan.aliyuncs.com"
"阿里云(广州)@registry.cn-guangzhou.aliyuncs.com"
"阿里云(成都)@registry.cn-chengdu.aliyuncs.com"
"阿里云(香港)@registry.cn-hongkong.aliyuncs.com"
"阿里云(日本-东京)@registry.ap-northeast-1.aliyuncs.com"
"阿里云(新加坡)@registry.ap-southeast-1.aliyuncs.com"
"阿里云(马来西亚-吉隆坡)@registry.ap-southeast-3.aliyuncs.com"
"阿里云(印度尼西亚-雅加达)@registry.ap-southeast-5.aliyuncs.com"
"阿里云(德国-法兰克福)@registry.eu-central-1.aliyuncs.com"
"阿里云(英国-伦敦)@registry.eu-west-1.aliyuncs.com"
"阿里云(美国西部-硅谷)@registry.us-west-1.aliyuncs.com"
"阿里云(美国东部-弗吉尼亚)@registry.us-east-1.aliyuncs.com"
"阿里云(阿联酋-迪拜)@registry.me-east-1.aliyuncs.com"
"腾讯云@mirror.ccs.tencentyun.com"
"谷歌云(北美)@gcr.io"
"谷歌云(亚洲)@asia.gcr.io"
"谷歌云(欧洲)@eu.gcr.io"
"官方 Docker Hub@registry.hub.docker.com"
)
## 定义系统判定变量
SYSTEM_DEBIAN="Debian"
SYSTEM_UBUNTU="Ubuntu"
SYSTEM_KALI="Kali"
SYSTEM_DEEPIN="Deepin"
SYSTEM_LINUX_MINT="Linuxmint"
SYSTEM_ZORIN="Zorin"
SYSTEM_RASPBERRY_PI_OS="Raspberry Pi OS"
SYSTEM_REDHAT="RedHat"
SYSTEM_RHEL="Red Hat Enterprise Linux"
SYSTEM_CENTOS="CentOS"
SYSTEM_CENTOS_STREAM="CentOS Stream"
SYSTEM_ROCKY="Rocky"
SYSTEM_ALMALINUX="AlmaLinux"
SYSTEM_FEDORA="Fedora"
SYSTEM_OPENCLOUDOS="OpenCloudOS"
SYSTEM_OPENCLOUDOS_STREAM="OpenCloudOS Stream"
SYSTEM_OPENEULER="openEuler"
SYSTEM_ANOLISOS="Anolis"
SYSTEM_OPENKYLIN="openKylin"
SYSTEM_OPENSUSE="openSUSE"
SYSTEM_ARCH="Arch"
SYSTEM_ALPINE="Alpine"
SYSTEM_GENTOO="Gentoo"
SYSTEM_NIXOS="NixOS"
## 定义系统版本文件
File_LinuxRelease=/etc/os-release
File_RedHatRelease=/etc/redhat-release
File_DebianVersion=/etc/debian_version
File_ArmbianRelease=/etc/armbian-release
File_RaspberryPiOSRelease=/etc/rpi-issue
File_openEulerRelease=/etc/openEuler-release
File_OpenCloudOSRelease=/etc/opencloudos-release
File_AnolisOSRelease=/etc/anolis-release
File_OracleLinuxRelease=/etc/oracle-release
File_ArchLinuxRelease=/etc/arch-release
File_AlpineRelease=/etc/alpine-release
File_ProxmoxVersion=/etc/pve/.version
## 定义软件源相关文件或目录
File_DebianSourceList=/etc/apt/sources.list
Dir_DebianExtendSource=/etc/apt/sources.list.d
Dir_YumRepos=/etc/yum.repos.d
## 定义 Docker 相关变量
DockerDir=/etc/docker
DockerConfig=$DockerDir/daemon.json
DockerConfigBackup=$DockerDir/daemon.json.bak
DockerVersionFile=docker-version.txt
DockerCEVersionFile=docker-ce-version.txt
DockerCECLIVersionFile=docker-ce-cli-version.txt
## 定义颜色变量
RED='\033[31m'
GREEN='\033[32m'
YELLOW='\033[33m'
BLUE='\033[34m'
PURPLE='\033[35m'
AZURE='\033[36m'
PLAIN='\033[0m'
BOLD='\033[1m'
SUCCESS="\033[1;32m✔${PLAIN}"
COMPLETE="\033[1;32m✔${PLAIN}"
WARN="\033[1;43m 警告 ${PLAIN}"
ERROR="\033[1;31m✘${PLAIN}"
FAIL="\033[1;31m✘${PLAIN}"
TIP="\033[1;44m 提示 ${PLAIN}"
WORKING="\033[1;36m◉${PLAIN}"
function main() {
permission_judgment
collect_system_info
run_start
choose_mirrors
if [[ "${ONLY_REGISTRY}" == "true" ]]; then
only_change_docker_registry_mirror
else
choose_protocol
close_firewall_service
install_dependency_packages
configure_docker_ce_mirror
install_docker_engine
change_docker_registry_mirror
check_installed_result
fi
run_end
}
## 处理命令选项
function handle_command_options() {
## 命令帮助
function output_command_help() {
echo -e "
命令选项(名称/含义/值)
--source 指定 Docker CE 源地址(域名或IP) 地址
--source-registry 指定镜像仓库地址(域名或IP) 地址
--branch 指定 Docker CE 源仓库(路径) 仓库名
--codename 指定 Debian 系操作系统的版本代号 代号名称
--designated-version 指定 Docker CE 安装版本 版本号
--protocol 指定 Docker CE 源的 WEB 协议 http 或 https
--install-latest 是否安装最新版本的 Docker Engine true 或 false
--close-firewall 是否关闭防火墙 true 或 false
--clean-screen 是否在运行前清除屏幕上的所有内容 true 或 false
--only-registry 仅更换镜像仓库模式 无
--ignore-backup-tips 忽略覆盖备份提示 无
--pure-mode 纯净模式,精简打印内容 无
问题报告 https://github.com/SuperManito/LinuxMirrors/issues
"
}
## 判断参数
while [ $# -gt 0 ]; do
case "$1" in
## 指定 Docker CE 软件源地址
--source)
if [ "$2" ]; then
echo "$2" | grep -Eq "\(|\)|\[|\]|\{|\}"
if [ $? -eq 0 ]; then
output_error "命令选项 ${BLUE}$2${PLAIN} 无效,请在该选项后指定有效的地址!"
else
SOURCE="$(echo "$2" | sed -e 's,^http[s]\?://,,g' -e 's,/$,,')"
shift
fi
else
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定软件源地址!"
fi
;;
## 指定 Docker Registry 仓库地址
--source-registry)
if [ "$2" ]; then
echo "$2" | grep -Eq "\(|\)|\[|\]|\{|\}"
if [ $? -eq 0 ]; then
output_error "命令选项 ${BLUE}$2${PLAIN} 无效,请在该选项后指定有效的地址!"
else
SOURCE_REGISTRY="$(echo "$2" | sed -e 's,^http[s]\?://,,g' -e 's,/$,,')"
shift
fi
else
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定镜像仓库地址!"
fi
;;
## 指定 Docker CE 软件源仓库
--branch)
if [ "$2" ]; then
SOURCE_BRANCH="$2"
shift
else
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定软件源仓库!"
fi
;;
## 指定 Debian 版本代号
--codename)
if [ "$2" ]; then
DEBIAN_CODENAME="$2"
shift
else
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定版本代号!"
fi
;;
## 指定 Docker Engine 安装版本
--designated-version)
if [ "$2" ]; then
echo "$2" | grep -Eq "^[0-9][0-9].[0-9]{1,2}.[0-9]{1,2}$"
if [ $? -eq 0 ]; then
DESIGNATED_DOCKER_VERSION="$2"
shift
else
output_error "命令选项 ${BLUE}$2${PLAIN} 无效,请在该选项后指定有效的版本号!"
fi
else
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定版本号!"
fi
;;
## WEB 协议HTTP/HTTPS
--protocol)
if [ "$2" ]; then
case "$2" in
http | https | HTTP | HTTPS)
WEB_PROTOCOL="${2,,}"
shift
;;
*)
output_error "检测到 ${BLUE}$2${PLAIN} 为无效参数值,请在该选项后指定 http 或 https "
;;
esac
else
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定 WEB 协议http/https"
fi
;;
## 安装最新版本
--install-latest | --install-latested)
if [ "$2" ]; then
case "$2" in
[Tt]rue | [Ff]alse)
INSTALL_LATESTED_DOCKER="${2,,}"
shift
;;
*)
output_error "命令选项 ${BLUE}$2${PLAIN} 无效,请在该选项后指定 true 或 false "
;;
esac
else
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定 true 或 false "
fi
;;
## 忽略覆盖备份提示
--ignore-backup-tips)
IGNORE_BACKUP_TIPS="true"
;;
## 关闭防火墙
--close-firewall)
if [ "$2" ]; then
case "$2" in
[Tt]rue | [Ff]alse)
CLOSE_FIREWALL="${2,,}"
shift
;;
*)
output_error "命令选项 ${BLUE}$2${PLAIN} 无效,请在该选项后指定 true 或 false "
;;
esac
else
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定 true 或 false "
fi
;;
## 清除屏幕上的所有内容
--clean-screen)
if [ "$2" ]; then
case "$2" in
[Tt]rue | [Ff]alse)
CLEAN_SCREEN="${2,,}"
shift
;;
*)
output_error "命令选项 ${BLUE}$2${PLAIN} 无效,请在该选项后指定 true 或 false "
;;
esac
else
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定 true 或 false "
fi
;;
## 仅更换镜像仓库模式
--only-registry)
ONLY_REGISTRY="true"
;;
## 纯净模式
--pure-mode)
PURE_MODE="true"
;;
## 命令帮助
--help)
output_command_help
exit
;;
*)
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请确认后重新输入!"
;;
esac
shift
done
## 设置部分功能的默认值
IGNORE_BACKUP_TIPS="${IGNORE_BACKUP_TIPS:-"false"}"
if [[ "${DESIGNATED_DOCKER_VERSION}" ]]; then
INSTALL_LATESTED_DOCKER="false"
fi
PURE_MODE="${PURE_MODE:-"false"}"
}
function run_start() {
if [ -z "${CLEAN_SCREEN}" ]; then
[[ -z "${SOURCE}" || -z "${SOURCE_REGISTRY}" ]] && clear
elif [ "${CLEAN_SCREEN}" == "true" ]; then
clear
fi
if [[ "${PURE_MODE}" == "true" ]]; then
return
fi
echo -e "+-----------------------------------+"
echo -e "| \033[0;1;35;95m⡇\033[0m \033[0;1;33;93m⠄\033[0m \033[0;1;32;92m⣀⡀\033[0m \033[0;1;36;96m⡀\033[0;1;34;94m⢀\033[0m \033[0;1;35;95m⡀⢀\033[0m \033[0;1;31;91m⡷\033[0;1;33;93m⢾\033[0m \033[0;1;32;92m⠄\033[0m \033[0;1;36;96m⡀⣀\033[0m \033[0;1;34;94m⡀\033[0;1;35;95m⣀\033[0m \033[0;1;31;91m⢀⡀\033[0m \033[0;1;33;93m⡀\033[0;1;32;92m⣀\033[0m \033[0;1;36;96m⢀⣀\033[0m |"
echo -e "| \033[0;1;31;91m⠧\033[0;1;33;93m⠤\033[0m \033[0;1;32;92m⠇\033[0m \033[0;1;36;96m⠇⠸\033[0m \033[0;1;34;94m⠣\033[0;1;35;95m⠼\033[0m \033[0;1;31;91m⠜⠣\033[0m \033[0;1;33;93m⠇\033[0;1;32;92m⠸\033[0m \033[0;1;36;96m⠇\033[0m \033[0;1;34;94m⠏\033[0m \033[0;1;35;95m⠏\033[0m \033[0;1;33;93m⠣⠜\033[0m \033[0;1;32;92m⠏\033[0m \033[0;1;34;94m⠭⠕\033[0m |"
echo -e "+-----------------------------------+"
echo -e "欢迎使用 Docker Engine 安装与换源脚本"
}
## 运行结束
function run_end() {
if [[ "${PURE_MODE}" == "true" ]]; then
echo ''
return
fi
local sponsor_ad=(
"1Panel · Linux 面板|极简运维 ➜ https://1panel.cn"
"林枫云 · 专注独立IP高频VPSR9/i9系列定制 ➜ https://www.dkdun.cn"
"乔星欢 · 香港4核4G服务器28元起_香港500Mbps大带宽 ➜ https://www.qiaoxh.com"
"速拓云 · 国内高防云服务器新用户享5折优惠 ➜ https://www.sutuoyun.com"
"云悠YUNYOO · 全球高性价比云服务器低至15.99元起 ➜ https://yunyoo.cc"
"圣道云 · 稳定高效云服务器低至9.9元/月起 ➜ https://www.shengdaoyun.com"
"润信云 · 国内挂机宝海外云服务器低至9.9元/月 ➜ https://www.runxinyun.com"
"新鸟云 · 2核2G云主机特价15元/月 ➜ https://www.xinniaoyun.com"
)
echo -e "\n✨ 脚本运行完毕,更多使用教程详见官网 👉 \033[3mhttps://linuxmirrors.cn\033[0m\n"
for ad in "${sponsor_ad[@]}"; do
sleep 0.1
echo -e " \033[2m${ad} \033[3m【广告】\033[0m\033[0m"
done
echo -e "\n\033[3;1mPowered by \033[34mLinuxMirrors\033[0m\n"
}
## 报错退出
function output_error() {
[ "$1" ] && echo -e "\n$ERROR $1\n"
exit 1
}
## 权限判定
function permission_judgment() {
if [ $UID -ne 0 ]; then
output_error "权限不足,请使用 Root 用户运行本脚本"
fi
}
## 收集系统信息
function collect_system_info() {
## 定义系统名称
SYSTEM_NAME="$(cat $File_LinuxRelease | grep -E "^NAME=" | awk -F '=' '{print$2}' | sed "s/[\'\"]//g")"
grep -q "PRETTY_NAME=" $File_LinuxRelease && SYSTEM_PRETTY_NAME="$(cat $File_LinuxRelease | grep -E "^PRETTY_NAME=" | awk -F '=' '{print$2}' | sed "s/[\'\"]//g")"
## 定义系统版本号
SYSTEM_VERSION_ID="$(cat $File_LinuxRelease | grep -E "^VERSION_ID=" | awk -F '=' '{print$2}' | sed "s/[\'\"]//g")"
SYSTEM_VERSION_ID_MAJOR="${SYSTEM_VERSION_ID%.*}"
SYSTEM_VERSION_ID_MINOR="${SYSTEM_VERSION_ID#*.}"
## 定义系统ID
SYSTEM_ID="$(cat $File_LinuxRelease | grep -E "^ID=" | awk -F '=' '{print$2}' | sed "s/[\'\"]//g")"
## 判定当前系统派系
if [ -s "${File_DebianVersion}" ]; then
SYSTEM_FACTIONS="${SYSTEM_DEBIAN}"
elif [ -s "${File_OracleLinuxRelease}" ]; then
output_error "当前操作系统Oracle Linux不在本脚本的支持范围内请前往官网查看支持列表"
elif [ -s "${File_RedHatRelease}" ]; then
SYSTEM_FACTIONS="${SYSTEM_REDHAT}"
elif [ -s "${File_openEulerRelease}" ]; then
SYSTEM_FACTIONS="${SYSTEM_OPENEULER}"
elif [ -s "${File_OpenCloudOSRelease}" ]; then
# 拦截 OpenCloudOS 9 及以上版本,不支持从 Docker 官方仓库安装
if [[ "${SYSTEM_VERSION_ID_MAJOR}" -ge 9 ]]; then
output_error "不支持当前操作系统,请参考如下命令自行安装:\n\ndnf install -y docker\nsystemctl enable --now docker"
fi
SYSTEM_FACTIONS="${SYSTEM_OPENCLOUDOS}" # 自 9.0 版本起不再基于红帽
elif [ -s "${File_AnolisOSRelease}" ]; then
# 拦截 Anolis OS 8.8 及以上版本,不支持从 Docker 官方仓库安装23 版本支持
if [[ "${SYSTEM_VERSION_ID_MAJOR}" == 8 ]]; then
output_error "不支持当前操作系统,请参考如下命令自行安装:\n\ndnf install -y docker\nsystemctl enable --now docker"
fi
SYSTEM_FACTIONS="${SYSTEM_ANOLISOS}" # 自 8.8 版本起不再基于红帽
else
output_error "当前操作系统不在本脚本的支持范围内,请前往官网查看支持列表!"
fi
## 判定系统类型、版本、版本号
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
if ! command -v lsb_release &>/dev/null; then
apt-get update
apt-get install -y lsb-release
if [ $? -ne 0 ]; then
output_error "lsb-release 软件包安装失败\n\n本脚本依赖 lsb_release 指令判断系统具体类型和版本,当前系统可能为精简安装,请自行安装后重新执行脚本!"
fi
fi
SYSTEM_JUDGMENT="$(lsb_release -is)"
SYSTEM_VERSION_CODENAME="${DEBIAN_CODENAME:-"$(lsb_release -cs)"}"
# Raspberry Pi OS
if [ -s "${File_RaspberryPiOSRelease}" ]; then
SYSTEM_JUDGMENT="${SYSTEM_RASPBERRY_PI_OS}"
SYSTEM_PRETTY_NAME="${SYSTEM_RASPBERRY_PI_OS}"
fi
;;
"${SYSTEM_REDHAT}")
SYSTEM_JUDGMENT="$(awk '{printf $1}' $File_RedHatRelease)"
# 拦截 Anolis OS 8.8 以下版本,不支持从 Docker 官方仓库安装
if [[ "${SYSTEM_JUDGMENT}" == "${SYSTEM_ANOLISOS}" ]]; then
output_error "不支持当前操作系统,请参考如下命令自行安装:\n\ndnf install -y docker\nsystemctl enable --now docker"
fi
## 特殊系统判断
# Red Hat Enterprise Linux
grep -q "${SYSTEM_RHEL}" $File_RedHatRelease && SYSTEM_JUDGMENT="${SYSTEM_RHEL}"
# CentOS Stream
grep -q "${SYSTEM_CENTOS_STREAM}" $File_RedHatRelease && SYSTEM_JUDGMENT="${SYSTEM_CENTOS_STREAM}"
;;
*)
SYSTEM_JUDGMENT="${SYSTEM_FACTIONS}"
;;
esac
## 判定系统处理器架构
DEVICE_ARCH_RAW="$(uname -m)"
case "${DEVICE_ARCH_RAW}" in
x86_64)
DEVICE_ARCH="x86_64"
;;
aarch64)
DEVICE_ARCH="ARM64"
;;
armv8l)
DEVICE_ARCH="ARMv8_32"
;;
armv7l)
DEVICE_ARCH="ARMv7"
;;
armv6l)
DEVICE_ARCH="ARMv6"
;;
armv5tel)
DEVICE_ARCH="ARMv5"
;;
ppc64le)
DEVICE_ARCH="ppc64le"
;;
s390x)
DEVICE_ARCH="s390x"
;;
i386 | i686)
output_error "Docker Engine 不支持安装在 x86_32 架构的环境上!"
;;
*)
output_error "未知的系统架构:${DEVICE_ARCH_RAW}"
;;
esac
## 定义软件源仓库名称
if [[ -z "${SOURCE_BRANCH}" ]]; then
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
case "${SYSTEM_JUDGMENT}" in
"${SYSTEM_DEBIAN}")
SOURCE_BRANCH="debian"
;;
"${SYSTEM_UBUNTU}" | "${SYSTEM_ZORIN}")
SOURCE_BRANCH="ubuntu"
;;
"${SYSTEM_RHEL}")
SOURCE_BRANCH="rhel"
;;
"${SYSTEM_RASPBERRY_PI_OS}")
case "${DEVICE_ARCH_RAW}" in
x86_64 | aarch64)
SOURCE_BRANCH="debian"
;;
*)
SOURCE_BRANCH="raspbian"
;;
esac
;;
*)
# 部分 Debian 系衍生操作系统使用 Debian 12 的 docker ce 源
SOURCE_BRANCH="debian"
SYSTEM_VERSION_CODENAME="bookworm"
;;
esac
;;
"${SYSTEM_REDHAT}")
case "${SYSTEM_JUDGMENT}" in
"${SYSTEM_FEDORA}")
SOURCE_BRANCH="fedora"
;;
"${SYSTEM_RHEL}")
SOURCE_BRANCH="rhel"
;;
*)
SOURCE_BRANCH="centos"
;;
esac
;;
"${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
SOURCE_BRANCH="centos"
;;
esac
fi
## 定义软件源更新文字
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
SYNC_MIRROR_TEXT="更新软件源"
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
SYNC_MIRROR_TEXT="生成软件源缓存"
;;
esac
## 判断是否可以使用高级交互式选择器
CAN_USE_ADVANCED_INTERACTIVE_SELECTION="false"
if command -v tput &>/dev/null; then
CAN_USE_ADVANCED_INTERACTIVE_SELECTION="true"
fi
}
function choose_mirrors() {
## 打印软件源列表
function print_mirrors_list() {
local tmp_mirror_name tmp_mirror_url arr_num default_mirror_name_length tmp_mirror_name_length tmp_spaces_nums a i j
## 计算字符串长度
function StringLength() {
local text=$1
echo "${#text}"
}
echo -e ''
local list_arr=()
local list_arr_sum="$(eval echo \${#$1[@]})"
for ((a = 0; a < $list_arr_sum; a++)); do
list_arr[$a]="$(eval echo \${$1[a]})"
done
if command -v printf &>/dev/null; then
for ((i = 0; i < ${#list_arr[@]}; i++)); do
tmp_mirror_name=$(echo "${list_arr[i]}" | awk -F '@' '{print$1}') # 软件源名称
# tmp_mirror_url=$(echo "${list_arr[i]}" | awk -F '@' '{print$2}') # 软件源地址
arr_num=$((i + 1))
default_mirror_name_length=${2:-"30"} # 默认软件源名称打印长度
## 补齐长度差异中文的引号在等宽字体中占1格而非2格
[[ $(echo "${tmp_mirror_name}" | grep -c "“") -gt 0 ]] && let default_mirror_name_length+=$(echo "${tmp_mirror_name}" | grep -c "“")
[[ $(echo "${tmp_mirror_name}" | grep -c "”") -gt 0 ]] && let default_mirror_name_length+=$(echo "${tmp_mirror_name}" | grep -c "”")
[[ $(echo "${tmp_mirror_name}" | grep -c "") -gt 0 ]] && let default_mirror_name_length+=$(echo "${tmp_mirror_name}" | grep -c "")
[[ $(echo "${tmp_mirror_name}" | grep -c "") -gt 0 ]] && let default_mirror_name_length+=$(echo "${tmp_mirror_name}" | grep -c "")
# 非一般字符长度
tmp_mirror_name_length=$(StringLength $(echo "${tmp_mirror_name}" | sed "s| ||g" | sed "s|[0-9a-zA-Z\.\=\:\_\(\)\'\"-\/\!·]||g;"))
## 填充空格
tmp_spaces_nums=$(($(($default_mirror_name_length - ${tmp_mirror_name_length} - $(StringLength "${tmp_mirror_name}"))) / 2))
for ((j = 1; j <= ${tmp_spaces_nums}; j++)); do
tmp_mirror_name="${tmp_mirror_name} "
done
printf "❖ %-$(($default_mirror_name_length + ${tmp_mirror_name_length}))s %4s\n" "${tmp_mirror_name}" "$arr_num)"
done
else
for ((i = 0; i < ${#list_arr[@]}; i++)); do
tmp_mirror_name="${list_arr[i]%@*}" # 软件源名称
tmp_mirror_url="${list_arr[i]#*@}" # 软件源地址
arr_num=$((i + 1))
echo -e "$arr_num. ${tmp_mirror_url} | ${tmp_mirror_name}"
done
fi
}
function print_title() {
local system_name="${SYSTEM_PRETTY_NAME:-"${SYSTEM_NAME} ${SYSTEM_VERSION_ID}"}"
local arch="${DEVICE_ARCH}"
local date_time time_zone
date_time="$(date "+%Y-%m-%d %H:%M")"
timezone="$(timedatectl status 2>/dev/null | grep "Time zone" | awk -F ':' '{print$2}' | awk -F ' ' '{print$1}')"
echo -e ''
echo -e "运行环境 ${BLUE}${system_name} ${arch}${PLAIN}"
echo -e "系统时间 ${BLUE}${date_time} ${timezone}${PLAIN}"
}
[[ "${PURE_MODE}" != "true" ]] && print_title
local mirror_list_name
if [[ -z "${SOURCE}" ]] && [[ "${ONLY_REGISTRY}" != "true" ]]; then
mirror_list_name="mirror_list_docker_ce"
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
sleep 1 >/dev/null 2>&1
eval "interactive_select_mirror \"\${${mirror_list_name}[@]}\" \"\\n \${BOLD}请选择你想使用的 Docker CE 源:\${PLAIN}\\n\""
SOURCE="${_SELECT_RESULT#*@}"
echo -e "\n${GREEN}${PLAIN} ${BOLD}Docker CE: ${_SELECT_RESULT%@*}${PLAIN}"
else
print_mirrors_list "${mirror_list_name}" 38
local CHOICE_B=$(echo -e "\n${BOLD}└─ 请选择并输入你想使用的 Docker CE 源 [ 1-$(eval echo \${#$mirror_list_name[@]}) ]${PLAIN}")
while true; do
read -p "${CHOICE_B}" INPUT
case "${INPUT}" in
[1-9] | [1-9][0-9] | [1-9][0-9][0-9])
local tmp_source="$(eval echo \${${mirror_list_name}[$(($INPUT - 1))]})"
if [[ -z "${tmp_source}" ]]; then
echo -e "\n$WARN 请输入有效的数字序号!"
else
SOURCE="$(eval echo \${${mirror_list_name}[$(($INPUT - 1))]} | awk -F '@' '{print$2}')"
break
fi
;;
*)
echo -e "\n$WARN 请输入数字序号以选择你想使用的软件源!"
;;
esac
done
fi
fi
if [[ -z "${SOURCE_REGISTRY}" ]]; then
mirror_list_name="mirror_list_registry"
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
sleep 1 >/dev/null 2>&1
eval "interactive_select_mirror \"\${${mirror_list_name}[@]}\" \"\\n \${BOLD}请选择你想使用的 Docker Registry 源:\${PLAIN}\\n\""
SOURCE_REGISTRY="${_SELECT_RESULT#*@}"
echo -e "\n${GREEN}${PLAIN} ${BOLD}Docker Registry: $(echo "${_SELECT_RESULT%@*}" | sed 's|(推荐)||g')${PLAIN}"
else
print_mirrors_list "${mirror_list_name}" 44
local CHOICE_C=$(echo -e "\n${BOLD}└─ 请选择并输入你想使用的 Docker Registry 源 [ 1-$(eval echo \${#$mirror_list_name[@]}) ]${PLAIN}")
while true; do
read -p "${CHOICE_C}" INPUT
case "${INPUT}" in
[1-9] | [1-9][0-9] | [1-9][0-9][0-9])
local tmp_source="$(eval echo \${${mirror_list_name}[$(($INPUT - 1))]})"
if [[ -z "${tmp_source}" ]]; then
echo -e "\n$WARN 请输入有效的数字序号!"
else
SOURCE_REGISTRY="$(eval echo \${${mirror_list_name}[$(($INPUT - 1))]} | awk -F '@' '{print$2}')"
break
fi
;;
*)
echo -e "\n$WARN 请输入数字序号以选择你想使用的软件源!"
;;
esac
done
fi
fi
}
## 选择同步或更新软件源所使用的 WEB 协议( HTTP/HTTPS
function choose_protocol() {
if [[ -z "${WEB_PROTOCOL}" ]]; then
if [[ "${ONLY_HTTP}" == "true" ]]; then
WEB_PROTOCOL="http"
else
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
echo ''
interactive_select_boolean "${BOLD}软件源是否使用 HTTP 协议?${PLAIN}"
if [[ "${_SELECT_RESULT}" == "true" ]]; then
WEB_PROTOCOL="http"
else
WEB_PROTOCOL="https"
fi
else
local CHOICE=$(echo -e "\n${BOLD}└─ 软件源是否使用 HTTP 协议? [Y/n] ${PLAIN}")
read -rp "${CHOICE}" INPUT
[[ -z "${INPUT}" ]] && INPUT=Y
case "${INPUT}" in
[Yy] | [Yy][Ee][Ss])
WEB_PROTOCOL="http"
;;
[Nn] | [Nn][Oo])
WEB_PROTOCOL="https"
;;
*)
echo -e "\n$WARN 输入错误,默认使用 HTTPS 协议!"
WEB_PROTOCOL="https"
;;
esac
fi
fi
fi
WEB_PROTOCOL="${WEB_PROTOCOL,,}"
}
## 关闭防火墙和SELinux
function close_firewall_service() {
if ! command -v systemctl &>/dev/null; then
return
fi
if [[ "$(systemctl is-active firewalld)" == "active" ]]; then
if [[ -z "${CLOSE_FIREWALL}" ]]; then
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
echo ''
interactive_select_boolean "${BOLD}是否关闭系统防火墙和 SELinux ?${PLAIN}"
if [[ "${_SELECT_RESULT}" == "true" ]]; then
CLOSE_FIREWALL="true"
fi
else
local CHOICE=$(echo -e "\n${BOLD}└─ 是否关闭系统防火墙和 SELinux ? [Y/n] ${PLAIN}")
read -rp "${CHOICE}" INPUT
[[ -z "${INPUT}" ]] && INPUT=Y
case "${INPUT}" in
[Yy] | [Yy][Ee][Ss])
CLOSE_FIREWALL="true"
;;
[Nn] | [Nn][Oo]) ;;
*)
echo -e "\n$WARN 输入错误,默认不关闭!"
;;
esac
fi
fi
if [[ "${CLOSE_FIREWALL}" == "true" ]]; then
local SelinuxConfig=/etc/selinux/config
systemctl disable --now firewalld >/dev/null 2>&1
[ -s "${SelinuxConfig}" ] && sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" $SelinuxConfig && setenforce 0 >/dev/null 2>&1
fi
fi
}
## 安装环境包
function install_dependency_packages() {
local commands package_manager
## 删除原有源
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
sed -i '/docker-ce/d' $File_DebianSourceList
rm -rf $Dir_DebianExtendSource/docker.list
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
rm -rf $Dir_YumRepos/*docker*.repo
;;
esac
## 更新软件源
commands=()
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
package_manager="apt-get"
commands+=("${package_manager} update")
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
package_manager="$(get_package_manager)"
commands+=("${package_manager} makecache")
;;
esac
if [[ "${PURE_MODE}" == "true" ]]; then
local exec_cmd=""
for cmd in "${commands[@]}"; do
if [[ -z "${exec_cmd}" ]]; then
exec_cmd="${cmd}"
else
exec_cmd="${exec_cmd} && ${cmd}"
fi
done
echo ''
animate_exec "${exec_cmd}" "${SYNC_MIRROR_TEXT}"
else
echo -e "\n$WORKING ${SYNC_MIRROR_TEXT}...\n"
for cmd in "${commands[@]}"; do
eval "${cmd}"
done
echo -e "\n$COMPLETE ${SYNC_MIRROR_TEXT}结束\n"
fi
if [ $? -ne 0 ]; then
output_error "${SYNC_MIRROR_TEXT}出错,请先解决系统原有软件源错误以确保 ${BLUE}${package_manager}${PLAIN} 软件包管理工具可用!"
fi
commands=()
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
commands+=("${package_manager} install -y ca-certificates curl")
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
# 注:红帽 8 版本才发布了 dnf 包管理工具
case "${SYSTEM_VERSION_ID_MAJOR}" in
7)
commands+=("${package_manager} install -y yum-utils device-mapper-persistent-data lvm2")
;;
*)
commands+=("${package_manager} install -y dnf-plugins-core")
;;
esac
;;
esac
if [[ "${PURE_MODE}" == "true" ]]; then
local exec_cmd=""
for cmd in "${commands[@]}"; do
if [[ -z "${exec_cmd}" ]]; then
exec_cmd="${cmd}"
else
exec_cmd="${exec_cmd} && ${cmd}"
fi
done
echo ''
animate_exec "${exec_cmd}" "安装环境软件包"
else
for cmd in "${commands[@]}"; do
eval "${cmd}"
done
fi
}
## 配置 Docker CE 源
function configure_docker_ce_mirror() {
local commands=()
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
## 处理 GPG 密钥
local file_keyring="/etc/apt/keyrings/docker.asc"
apt-key del 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 >/dev/null 2>&1 # 删除旧的密钥
[ -f "${file_keyring}" ] && rm -rf $file_keyring
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://${SOURCE}/linux/${SOURCE_BRANCH}/gpg -o $file_keyring >/dev/null
if [ $? -ne 0 ]; then
output_error "GPG 密钥下载失败,请检查网络或更换 Docker CE 软件源后重试!"
fi
chmod a+r $file_keyring
## 添加源
echo "deb [arch=$(dpkg --print-architecture) signed-by=${file_keyring}] ${WEB_PROTOCOL}://${SOURCE}/linux/${SOURCE_BRANCH} ${SYSTEM_VERSION_CODENAME} stable" | tee $Dir_DebianExtendSource/docker.list >/dev/null 2>&1
commands+=("apt-get update")
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
case "${SYSTEM_VERSION_ID_MAJOR}" in
7)
yum-config-manager -y --add-repo https://${SOURCE}/linux/${SOURCE_BRANCH}/docker-ce.repo
;;
*)
dnf config-manager -y --add-repo https://${SOURCE}/linux/${SOURCE_BRANCH}/docker-ce.repo
;;
esac
sed -i "s|https://download.docker.com|${WEB_PROTOCOL}://${SOURCE}|g" $Dir_YumRepos/docker-ce.repo
## 兼容处理版本号
if [[ "${SYSTEM_JUDGMENT}" != "${SYSTEM_FEDORA}" ]]; then
local target_version
case "${SYSTEM_VERSION_ID_MAJOR}" in
7 | 8 | 9 | 10)
target_version="${SYSTEM_VERSION_ID_MAJOR}"
;;
*)
## 目前红帽系衍生系统还没有普及 10 版本
target_version="9" # 使用较新的版本
;;
esac
sed -i "s|\$releasever|${target_version}|g" $Dir_YumRepos/docker-ce.repo
local package_manager="$(get_package_manager)"
commands+=("${package_manager} makecache")
fi
;;
esac
echo ''
if [[ "${PURE_MODE}" == "true" ]]; then
local exec_cmd=""
for cmd in "${commands[@]}"; do
if [[ -z "${exec_cmd}" ]]; then
exec_cmd="${cmd}"
else
exec_cmd="${exec_cmd} && ${cmd}"
fi
done
animate_exec "${exec_cmd}" "${SYNC_MIRROR_TEXT}"
else
for cmd in "${commands[@]}"; do
eval "${cmd}"
done
fi
}
## 安装 Docker Engine
function install_docker_engine() {
## 导出可安装的版本列表
function export_version_list() {
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
apt-cache madison docker-ce | awk '{print $3}' | grep -Eo "[0-9][0-9].[0-9]{1,2}.[0-9]{1,2}" >$DockerCEVersionFile
apt-cache madison docker-ce-cli | awk '{print $3}' | grep -Eo "[0-9][0-9].[0-9]{1,2}.[0-9]{1,2}" >$DockerCECLIVersionFile
grep -wf $DockerCEVersionFile $DockerCECLIVersionFile >$DockerVersionFile
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
local package_manager="$(get_package_manager)"
$package_manager list docker-ce --showduplicates | sort -r | awk '{print $2}' | grep -Eo "[0-9][0-9].[0-9]{1,2}.[0-9]{1,2}" >$DockerCEVersionFile
$package_manager list docker-ce-cli --showduplicates | sort -r | awk '{print $2}' | grep -Eo "[0-9][0-9].[0-9]{1,2}.[0-9]{1,2}" >$DockerCECLIVersionFile
grep -wf $DockerCEVersionFile $DockerCECLIVersionFile >$DockerVersionFile
;;
esac
rm -rf $DockerCEVersionFile $DockerCECLIVersionFile
}
## 卸载 Docker Engine 原有版本软件包
function uninstall_original_version() {
if command -v docker &>/dev/null; then
# 先停止并禁用 Docker 服务
systemctl disable --now docker >/dev/null 2>&1
sleep 2s
fi
# 确定需要卸载的软件包
local package_list
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
package_list='docker* podman podman-docker containerd runc'
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
package_list='docker* podman podman-docker runc'
;;
esac
# 卸载软件包并清理残留
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
apt-get remove -y $package_list >/dev/null 2>&1
apt-get autoremove -y >/dev/null 2>&1
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
local package_manager="$(get_package_manager)"
$package_manager remove -y $package_list >/dev/null 2>&1
$package_manager autoremove -y >/dev/null 2>&1
;;
esac
}
## 安装
function install_main() {
local target_docker_version
local commands=()
if [[ "${INSTALL_LATESTED_DOCKER}" == "true" ]]; then
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
commands+=("apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin")
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
local package_manager="$(get_package_manager)"
commands+=("${package_manager} install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin")
;;
esac
else
export_version_list
if [ ! -s "${DockerVersionFile}" ]; then
rm -rf $DockerVersionFile
output_error "查询 Docker Engine 版本列表失败!"
fi
if [[ "${DESIGNATED_DOCKER_VERSION}" ]]; then
cat $DockerVersionFile | grep -Eq "^${DESIGNATED_DOCKER_VERSION}$"
if [ $? -ne 0 ]; then
rm -rf $DockerVersionFile
output_error "指定的 Docker Engine 版本不存在或不支持安装!"
fi
target_docker_version="${DESIGNATED_DOCKER_VERSION}"
else
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
local version_list=(
$(cat $DockerVersionFile | sort -t '.' -k1,1nr -k2,2nr -k3,3nr | tr '\n' ' ' | sed 's/ $//')
)
local mirror_list_name="version_list"
eval "interactive_select_mirror \"\${${mirror_list_name}[@]}\" \"\\n \${BOLD}请选择你想安装的版本:\${PLAIN}\\n\""
target_docker_version="${_SELECT_RESULT}"
echo -e "\n${GREEN}${PLAIN} ${BOLD}指定安装版本:${target_docker_version}${PLAIN}\n"
else
echo -e "\n${GREEN} --------- 请选择你要安装的版本27.4.0 ---------- ${PLAIN}\n"
cat $DockerVersionFile
while true; do
local CHOICE=$(echo -e "\n${BOLD}└─ 请根据上面的列表,选择并输入你想要安装的具体版本号:${PLAIN}\n")
read -p "${CHOICE}" target_docker_version
echo ''
cat $DockerVersionFile | grep -Eqw "${target_docker_version}"
if [ $? -eq 0 ]; then
echo "${target_docker_version}" | grep -Eqw '[0-9][0-9].[0-9]{1,2}.[0-9]{1,2}'
if [ $? -eq 0 ]; then
break
else
echo -e "$ERROR 请输入正确的版本号!"
fi
else
echo -e "$ERROR 输入错误请重新输入!"
fi
done
fi
fi
rm -rf $DockerVersionFile
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
local major_version="$(echo ${target_docker_version} | cut -c1-2)"
local minor_version="$(echo ${target_docker_version} | cut -c4-5)"
case "${major_version}" in
18)
if [ "${minor_version}" == "09" ]; then
INSTALL_JUDGMENT="5:"
else
INSTALL_JUDGMENT=""
fi
;;
*)
INSTALL_JUDGMENT="5:"
;;
esac
commands+=("apt-get install -y docker-ce=${INSTALL_JUDGMENT}${target_docker_version}* docker-ce-cli=5:${target_docker_version}* containerd.io docker-buildx-plugin docker-compose-plugin")
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
local package_manager="$(get_package_manager)"
commands+=("${package_manager} install -y docker-ce-${target_docker_version} docker-ce-cli-${target_docker_version} containerd.io docker-buildx-plugin docker-compose-plugin")
;;
esac
fi
echo ''
if [[ "${PURE_MODE}" == "true" ]]; then
local exec_cmd=""
for cmd in "${commands[@]}"; do
if [[ -z "${exec_cmd}" ]]; then
exec_cmd="${cmd}"
else
exec_cmd="${exec_cmd} && ${cmd}"
fi
done
animate_exec "${exec_cmd}" "安装 Docker Engine"
else
for cmd in "${commands[@]}"; do
eval "${cmd}"
done
fi
[ $? -ne 0 ] && output_error "安装 Docker Engine 失败!"
}
## 判断是否手动选择安装版本
if [[ -z "${INSTALL_LATESTED_DOCKER}" ]]; then
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
echo ''
interactive_select_boolean "${BOLD}是否安装最新版本的 Docker Engine ?${PLAIN}"
if [[ "${_SELECT_RESULT}" == "true" ]]; then
INSTALL_LATESTED_DOCKER="true"
else
INSTALL_LATESTED_DOCKER="false"
fi
else
local CHOICE_A=$(echo -e "\n${BOLD}└─ 是否安装最新版本的 Docker Engine ? [Y/n] ${PLAIN}")
read -p "${CHOICE_A}" INPUT
[[ -z "${INPUT}" ]] && INPUT=Y
case $INPUT in
[Yy] | [Yy][Ee][Ss])
INSTALL_LATESTED_DOCKER="true"
;;
[Nn] | [Nn][Oo])
INSTALL_LATESTED_DOCKER="false"
;;
*)
INSTALL_LATESTED_DOCKER="true"
echo -e "\n$WARN 输入错误,默认安装最新版本!"
;;
esac
fi
fi
## 判定是否已安装
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
dpkg -l | grep docker-ce-cli -q
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
rpm -qa | grep docker-ce-cli -q
;;
esac
if [ $? -eq 0 ]; then
export_version_list
local current_docker_version="$(docker -v | grep -Eo "[0-9][0-9]\.[0-9]{1,2}\.[0-9]{1,2}")"
local latest_docker_version="$(cat $DockerVersionFile | head -n 1)"
rm -rf $DockerVersionFile
if [[ "${current_docker_version}" == "${latest_docker_version}" ]] && [[ "${INSTALL_LATESTED_DOCKER}" == "true" ]]; then
echo -e "\n$TIP 检测到系统已安装 Docker Engine 且是最新版本,跳过安装"
else
uninstall_original_version
install_main
fi
else
uninstall_original_version
install_main
fi
}
## 修改 Docker Registry 镜像仓库源
function change_docker_registry_mirror() {
## 使用官方 Docker Hub
if [[ "${REGISTRY_SOURCEL}" == "registry.hub.docker.com" ]]; then
if [ -s "${DockerConfig}" ]; then
## 安装 jq
local package_manager="$(get_package_manager)"
$package_manager install -y jq
if command -v jq &>/dev/null; then
jq 'del(.["registry-mirrors"])' $DockerConfig >$DockerConfig.tmp && mv $DockerConfig.tmp $DockerConfig
# 重启服务
systemctl daemon-reload
if [[ $(systemctl is-active docker) == "active" ]]; then
systemctl restart docker
fi
else
echo -e "\n${WARN} 请自行删除 $DockerConfig 中的 ${BLUE}registry-mirrors${PLAIN} 配置并重启服务 ${BLUE}systemctl daemon-reload && systemctl restart docker${PLAIN}\n"
fi
fi
return
fi
## 备份原有配置文件
if [ -d "${DockerDir}" ] && [ -e "${DockerConfig}" ]; then
if [ -e "${DockerConfigBackup}" ]; then
if [[ "${IGNORE_BACKUP_TIPS}" == "false" ]]; then
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
echo ''
interactive_select_boolean "${BOLD}检测到已备份的 Docker 配置文件,是否跳过覆盖备份?${PLAIN}"
if [[ "${_SELECT_RESULT}" == "false" ]]; then
echo ''
cp -rvf $DockerConfig $DockerConfigBackup 2>&1
fi
else
local CHOICE_BACKUP=$(echo -e "\n${BOLD}└─ 检测到已备份的 Docker 配置文件,是否跳过覆盖备份? [Y/n] ${PLAIN}")
read -p "${CHOICE_BACKUP}" INPUT
[[ -z "${INPUT}" ]] && INPUT=Y
case $INPUT in
[Yy] | [Yy][Ee][Ss]) ;;
[Nn] | [Nn][Oo])
echo ''
cp -rvf $DockerConfig $DockerConfigBackup 2>&1
;;
*)
echo -e "\n$WARN 输入错误,默认不覆盖!"
;;
esac
fi
fi
else
echo ''
cp -rvf $DockerConfig $DockerConfigBackup 2>&1
echo -e "\n$COMPLETE 已备份原有 Docker 配置文件"
fi
sleep 2s
else
mkdir -p $DockerDir >/dev/null 2>&1
touch $DockerConfig
fi
echo -e '{\n "registry-mirrors": ["https://'"${SOURCE_REGISTRY}"'"]\n}' >$DockerConfig
## 重启服务
systemctl daemon-reload
if [[ $(systemctl is-active docker) == "active" ]]; then
systemctl restart docker
fi
}
## 仅修改 Docker Registry 镜像仓库源模式
function only_change_docker_registry_mirror() {
## 判定是否已安装
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
dpkg -l | grep docker-ce-cli -q
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
rpm -qa | grep docker-ce-cli -q
;;
esac
if [ $? -ne 0 ]; then
## 仅镜像仓库换源模式
if [[ "${ONLY_REGISTRY}" == "true" ]]; then
output_error "当前尚未安装 Docker Engine请取消设置 ${BLUE}--only-registry${PLAIN} 命令选项后重新执行脚本!"
fi
fi
[ -d "${DockerDir}" ] || mkdir -p "${DockerDir}"
if [ -s "${DockerConfig}" ]; then
## 安装 jq
if ! command -v jq &>/dev/null; then
## 更新软件源
local package_manager
local commands=()
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
package_manager="apt-get"
commands+=("${package_manager} update")
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
package_manager="$(get_package_manager)"
commands+=("${package_manager} makecache")
;;
esac
if [[ "${PURE_MODE}" == "true" ]]; then
local exec_cmd=""
for cmd in "${commands[@]}"; do
if [[ -z "${exec_cmd}" ]]; then
exec_cmd="${cmd}"
else
exec_cmd="${exec_cmd} && ${cmd}"
fi
done
echo ''
animate_exec "${exec_cmd}" "${SYNC_MIRROR_TEXT}"
else
echo -e "\n$WORKING ${SYNC_MIRROR_TEXT}...\n"
for cmd in "${commands[@]}"; do
eval "${cmd}"
done
echo -e "\n$COMPLETE ${SYNC_MIRROR_TEXT}结束\n"
fi
if [ $? -ne 0 ]; then
output_error "${SYNC_MIRROR_TEXT}出错,请先解决系统原有软件源错误以确保 ${BLUE}${package_manager}${PLAIN} 软件包管理工具可用!"
fi
$package_manager install -y jq
if ! command -v jq &>/dev/null; then
output_error "软件包 ${BLUE}jq${PLAIN} 安装失败,请自行安装后重新运行脚本!"
fi
fi
[ -s "${DockerConfig}" ] || echo "{}" >$DockerConfig
jq '.["registry-mirrors"] = ["https://'"${SOURCE_REGISTRY}"'"]' $DockerConfig >$DockerConfig.tmp && mv $DockerConfig.tmp $DockerConfig
else
echo -e '{\n "registry-mirrors": ["https://'"${SOURCE_REGISTRY}"'"]\n}' >$DockerConfig
fi
echo -e "\n${BLUE}\$${PLAIN} docker info --format '{{json .RegistryConfig.Mirrors}}'"
docker info --format '{{json .RegistryConfig.Mirrors}}'
## 重启服务
systemctl daemon-reload
if [[ $(systemctl is-active docker) == "active" ]]; then
systemctl restart docker
fi
if [[ "${PURE_MODE}" != "true" ]]; then
echo -e "\n$COMPLETE 已更换镜像仓库"
fi
}
## 查看版本并验证安装结果
function check_installed_result() {
if command -v docker &>/dev/null; then
systemctl enable --now docker >/dev/null 2>&1
echo -en "\n当前安装版本:"
docker -v
if [ $? -eq 0 ]; then
echo -e " $(docker compose version 2>&1)"
# echo -e "\n$COMPLETE 安装完成"
else
echo -e "\n$ERROR 安装失败"
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
echo -e "\n检查源文件cat $Dir_DebianExtendSource/docker.list"
echo -e '请尝试手动执行安装命令apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin\n'
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
local package_manager="$(get_package_manager)"
echo -e "\n检查源文件cat ${Dir_YumRepos}/docker.repo"
echo -e "请尝试手动执行安装命令:${package_manager} install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin\n"
;;
esac
exit 1
fi
if [[ $(systemctl is-active docker) != "active" ]]; then
sleep 2
systemctl disable --now docker >/dev/null 2>&1
sleep 2
systemctl enable --now docker >/dev/null 2>&1
sleep 2
if [[ $(systemctl is-active docker) != "active" ]]; then
echo -e "\n$ERROR 检测到 Docker 服务启动异常,可能由于重复安装导致"
echo -e "\n${YELLOW} 请执行 "systemctl start docker""service docker start" 命令尝试启动,如若报错请尝试重新执行本脚本${PLAIN}"
fi
fi
else
echo -e "\n$ERROR 安装失败"
fi
}
## 选择系统包管理器
function get_package_manager() {
local command="yum"
case "${SYSTEM_JUDGMENT}" in
"${SYSTEM_CENTOS_STREAM}" | "${SYSTEM_ROCKY}" | "${SYSTEM_ALMALINUX}" | "${SYSTEM_RHEL}")
case "${SYSTEM_VERSION_ID_MAJOR}" in
9 | 10)
command="dnf"
;;
esac
;;
"${SYSTEM_FEDORA}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
command="dnf"
;;
esac
echo "${command}"
}
function interactive_select_mirror() {
_SELECT_RESULT=""
local options=("$@")
local message="${options[${#options[@]} - 1]}"
unset options[${#options[@]}-1]
local selected=0
local start=0
local page_size=$(($(tput lines 2>/dev/null) - 3)) # 减去3行用于显示提示信息
function clear_menu() {
tput rc 2>/dev/null
for ((i = 0; i < ${#options[@]} + 1; i++)); do
echo -e "\r\033[K"
done
tput rc 2>/dev/null
}
function cleanup() {
clear_menu
tput rc 2>/dev/null
tput cnorm 2>/dev/null
tput rmcup 2>/dev/null
echo -e "\n\033[1;44m 提示 \033[0m \033[31m操作已取消\033[0m\n"
exit 130
}
function draw_menu() {
tput clear 2>/dev/null
tput cup 0 0 2>/dev/null
echo -e "${message}"
local end=$((start + page_size - 1))
if [ $end -ge ${#options[@]} ]; then
end=${#options[@]}-1
fi
for ((i = start; i <= end; i++)); do
if [ "$i" -eq "$selected" ]; then
echo -e "\e[34;4m➤ ${options[$i]%@*}\e[0m"
else
echo -e " ${options[$i]%@*}"
fi
done
}
function read_key() {
IFS= read -rsn1 key
if [[ $key == $'\x1b' ]]; then
IFS= read -rsn2 key
key="$key"
fi
echo "$key"
}
tput smcup 2>/dev/null # 保存当前屏幕并切换到新屏幕
tput sc 2>/dev/null # 保存光标位置
tput civis 2>/dev/null # 隐藏光标
trap "cleanup" INT TERM # 捕捉脚本结束时恢复光标
draw_menu # 初始化菜单位置
# 处理选择
while true; do
key=$(read_key)
case "$key" in
"[A" | "w" | "W")
# 上箭头 / W
if [ "$selected" -gt 0 ]; then
selected=$((selected - 1))
if [ "$selected" -lt "$start" ]; then
start=$((start - 1))
fi
fi
;;
"[B" | "s" | "S")
# 下箭头 / S
if [ "$selected" -lt $((${#options[@]} - 1)) ]; then
selected=$((selected + 1))
if [ "$selected" -ge $((start + page_size)) ]; then
start=$((start + 1))
fi
fi
;;
"")
# Enter 键
tput rmcup
break
;;
*) ;;
esac
draw_menu
done
# clear_menu # 清除菜单
tput cnorm 2>/dev/null # 恢复光标
tput rmcup 2>/dev/null # 恢复之前的屏幕
# tput rc 2>/dev/null # 恢复光标位置
# 处理结果
_SELECT_RESULT="${options[$selected]}"
}
function interactive_select_boolean() {
_SELECT_RESULT=""
local selected=0
local message="$1"
local menu_height=3 # 菜单总高度(标题行+空行+选项行)
local original_line
function store_position() {
# 保存菜单开始前的行位置
original_line=$(tput lines 2>/dev/null)
}
function clear_menu() {
# 向上移动到菜单开始位置并清除菜单
for ((i = 0; i < ${menu_height}; i++)); do
tput cuu1 2>/dev/null # 光标上移一行
tput el 2>/dev/null # 清除当前行
done
}
function cleanup() {
clear_menu
tput cnorm 2>/dev/null
echo -e "\n\033[1;44m 提示 \033[0m \033[31m操作已取消\033[0m\n"
exit 130
}
function draw_menu() {
# 绘制菜单不改变光标位置
echo -e "╭─ ${message}"
echo -e ""
if [ "$selected" -eq 0 ]; then
echo -e "╰─ \033[34m●\033[0m 是\033[2m / ○ 否\033[0m"
else
echo -e "╰─ \033[2m○ 是 / \033[0m\033[34m●\033[0m 否"
fi
}
function read_key() {
IFS= read -rsn1 key
if [[ $key == $'\x1b' ]]; then
IFS= read -rsn2 key
key="$key"
fi
echo "$key"
}
tput civis 2>/dev/null # 隐藏光标
store_position # 记录当前位置
trap "cleanup" INT TERM
draw_menu # 初始化菜单位置
# 处理选择
while true; do
key=$(read_key)
case "$key" in
"[D" | "a" | "A")
# 左箭头 / A
if [ "$selected" -gt 0 ]; then
selected=$((selected - 1))
clear_menu
draw_menu
fi
;;
"[C" | "d" | "D")
# 右箭头 / D
if [ "$selected" -lt 1 ]; then
selected=$((selected + 1))
clear_menu
draw_menu
fi
;;
"")
# Enter 键
clear_menu # 先清除菜单
break
;;
*) ;;
esac
done
echo -e "╭─ ${message}"
echo -e ""
if [ "$selected" -eq 0 ]; then
echo -e "╰─ \033[32m●\033[0m \033[1m是\033[0m\033[2m / ○ 否\033[0m"
_SELECT_RESULT="true"
else
echo -e "╰─ \033[2m○ 是 / \033[0m\033[32m●\033[0m \033[1m否\033[0m"
_SELECT_RESULT="false"
fi
tput cnorm 2>/dev/null # 恢复光标
}
function animate_exec() {
local cmd="$1"
local title="$2"
local max_lines=${3:-5} # 默认显示5行
local spinner_style="${4:-dots}" # 动画样式
local refresh_rate="${5:-0.1}" # 刷新率
# 动画样式
local -A spinners=([dots]="⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏" [circle]="◐ ◓ ◑ ◒" [classic]="-\\ |/")
local -A recommended_rates=([dots]="0.08" [circle]="0.12" [classic]="0.12")
[[ -z "${spinners[$spinner_style]}" ]] && spinner_style="dots"
[[ "${refresh_rate}" == "0.1" ]] && refresh_rate="${recommended_rates[$spinner_style]}"
# 获取终端宽度
local term_width=$(tput cols 2>/dev/null || echo 80)
local display_width=$((term_width - 2))
# 截断行
function simple_truncate() {
local line="$1"
local truncate_marker="..."
local max_length=$((display_width - 3))
# 快速判断如果ASCII行长度在范围内直接返回
if [[ "${line}" =~ ^[[:ascii:]]*$ && ${#line} -le $display_width ]]; then
echo "${line}"
return
fi
# 1. 计算非ASCII字符数量
local non_ascii_count=$(echo "${line// /}" | sed "s|[0-9a-zA-Z\.\=\:\_\(\)\'\"-\/\!·]||g;" | wc -m)
# 2. 总字符数
local total_length=${#line}
# 3. 实际显示宽度 = 总字符数 + 非ASCII字符数
# 非ASCII字符额外占用1个宽度单位即总共2个
local display_length=$((total_length + non_ascii_count))
# 4. 中文引号特殊处理引号只占1个宽度需要减去额外计算的部分
local quote_count=0
[[ $(echo "${line}" | grep -c "“") -gt 0 ]] && quote_count=$((quote_count + $(echo "${line}" | grep -c "“")))
[[ $(echo "${line}" | grep -c "”") -gt 0 ]] && quote_count=$((quote_count + $(echo "${line}" | grep -c "”")))
[[ $(echo "${line}" | grep -c "") -gt 0 ]] && quote_count=$((quote_count + $(echo "${line}" | grep -c "")))
[[ $(echo "${line}" | grep -c "") -gt 0 ]] && quote_count=$((quote_count + $(echo "${line}" | grep -c "")))
# 5. 调整宽度(减去引号额外计算的部分)
display_length=$((display_length - quote_count))
# 如果计算宽度在显示范围内,直接显示
if [[ $display_length -le $display_width ]]; then
echo "$line"
return
fi
# 需要截断,逐字符构建
local result=""
local current_width=0
local i=0
while [ $i -lt ${#line} ]; do
local char="${line:$i:1}"
local char_width=1
# 是否是中文等宽字符(非ASCII)
if ! [[ "$char" =~ [0-9a-zA-Z\.\=\:\_\(\)\'\"-\/\!·] ]]; then
# 不是中文引号则算2个宽度
if [[ "$char" != "" && "$char" != "" && "$char" != "" && "$char" != "" ]]; then
char_width=2
fi
fi
# 检查添加此字符是否会超出限制
if [[ $((current_width + char_width)) -gt $max_length ]]; then
echo "${result}${truncate_marker}"
return
fi
result+="${char}"
current_width=$((current_width + char_width))
((i++))
done
# 完整遍历未超出限制
echo "${line}"
}
# 清理函数
function cleanup() {
[ -f "${temp_file}" ] && rm -f "${temp_file}"
tput cnorm 2>/dev/null
echo -e "\n\033[1;44m 提示 \033[0m \033[31m操作已取消\033[0m\n"
exit 130
}
# 创建临时文件
function make_temp_file() {
local temp_dirs=("." "/tmp")
local tmp_file=""
for dir in "${temp_dirs[@]}"; do
[[ ! -d "${dir}" || ! -w "${dir}" ]] && continue
tmp_file="${dir}/animate_exec_$$_$(date +%s)"
touch "${tmp_file}" 2>/dev/null || continue
if [[ -f "${tmp_file}" && -w "${tmp_file}" ]]; then
echo "${tmp_file}"
return
fi
done
echo "${tmp_file}"
}
# 更新显示
function update_display() {
local current_size=$(wc -c <"${temp_file}" 2>/dev/null || echo 0)
# 如果文件大小没变,不更新
if [[ $current_size -le $last_size ]]; then
return 1
fi
# 只在必要时读取文件
local -a lines=()
mapfile -t -n "${max_lines}" lines < <(tail -n "$max_lines" "${temp_file}")
# 处理每一行
local -a processed_lines=()
for ((i = 0; i < ${#lines[@]}; i++)); do
processed_lines[i]=$(simple_truncate "${lines[i]}")
done
# 更新显示
tput cud1 2>/dev/null # 移动到标题下
echo -ne "\r\033[K" # 清空当前行
tput cud1 2>/dev/null # 移动到内容区
# 显示处理后的行
for ((i = 0; i < $max_lines; i++)); do
echo -ne "\r\033[K" # 清空当前行
[[ $i -lt ${#processed_lines[@]} ]] && echo -ne "\033[2m${processed_lines[$i]}\033[0m"
[[ $i -lt $((max_lines - 1)) ]] && tput cud1 2>/dev/null
done
# 返回到标题行
for ((i = 0; i < $max_lines + 1; i++)); do
tput cuu1 2>/dev/null
done
# 更新文件大小记录
last_size=$current_size
return 0
}
# 初始化
local spinner_frames=(${spinners[$spinner_style]})
local temp_file="$(make_temp_file)"
trap "cleanup" INT TERM
tput civis 2>/dev/null # 隐藏光标
# 预留显示空间
echo '' # 标题行
echo '' # 空行
for ((i = 0; i < $max_lines; i++)); do
echo ''
done
# 执行命令
eval "${cmd}" >"${temp_file}" 2>&1 &
local cmd_pid=$!
local last_size=0
local spin_idx=0
# 返回到标题行
tput cuu $((max_lines + 2)) 2>/dev/null
# 添加延迟允许命令开始执行
sleep 0.05
# 显示初始状态
echo -ne "\r\033[K◉ ${title} [\033[1m\033[34m${spinner_frames[$spin_idx]}\033[0m]"
spin_idx=$(((spin_idx + 1) % ${#spinner_frames[@]}))
# 检查初始输出
update_display
# 监控命令执行 - 增加自适应刷新
local update_count=0
local adaptive_rate=$refresh_rate
while kill -0 $cmd_pid 2>/dev/null; do
echo -ne "\r\033[K◉ ${title} [\033[1m\033[34m${spinner_frames[$spin_idx]}\033[0m]"
spin_idx=$(((spin_idx + 1) % ${#spinner_frames[@]}))
if update_display; then
update_count=$((update_count + 1))
# 连续更新太频繁则调整刷新率
if [[ $update_count -gt 5 ]]; then
adaptive_rate=$(awk "BEGIN {print $adaptive_rate * 1.5; exit}")
[[ $(awk "BEGIN {print ($adaptive_rate > 0.5); exit}") -eq 1 ]] && adaptive_rate=0.5
update_count=0
fi
else
update_count=0
adaptive_rate=$refresh_rate
fi
sleep $adaptive_rate
done
wait $cmd_pid
local exit_status=$?
# 最后一次更新显示
update_display
# 显示完成状态
if [ $exit_status -eq 0 ]; then
echo -ne "\r\033[K◉ ${title} [\033[1m\033[32m✓\033[0m]\n"
else
echo -ne "\r\033[K◉ ${title} [\033[1m\033[31m✗\033[0m]\n"
fi
# 空行
echo -ne "\r\033[K\n"
# 显示最终输出
local actual_lines=$(wc -l <"${temp_file}" 2>/dev/null || echo 0)
[[ $actual_lines -gt $max_lines ]] && actual_lines=$max_lines
if [[ $actual_lines -gt 0 ]]; then
local -a final_lines=()
mapfile -t -n "$actual_lines" final_lines < <(tail -n "$actual_lines" "${temp_file}")
for ((i = 0; i < actual_lines; i++)); do
local line=$(simple_truncate "${final_lines[$i]}")
echo -ne "\r\033[K\033[2m${line}\033[0m\n"
done
fi
# 清理并返回
tput cnorm 2>/dev/null
rm -f "${temp_file}"
return $exit_status
}
handle_command_options "$@"
main