Files
LinuxMirrors/DockerInstallation.sh

1571 lines
62 KiB
Bash
Raw Normal View History

2021-10-08 00:03:44 +08:00
#!/bin/bash
## Author: SuperManito
2025-04-10 15:19:46 +08:00
## Modified: 2025-04-10
## License: MIT
2023-06-24 11:06:30 +08:00
## GitHub: https://github.com/SuperManito/LinuxMirrors
2023-05-10 19:39:29 +08:00
## Website: https://linuxmirrors.cn
2023-05-05 22:39:34 +08:00
2023-06-24 11:06:30 +08:00
## Docker CE 软件源列表
# 格式:"软件源名称@软件源地址"
2023-05-10 19:39:29 +08:00
mirror_list_docker_ce=(
"阿里云@mirrors.aliyun.com/docker-ce"
"腾讯云@mirrors.tencent.com/docker-ce"
2025-04-10 15:19:46 +08:00
"华为云@mirrors.huaweicloud.com/docker-ce"
2023-06-24 11:06:30 +08:00
"微软 Azure 中国@mirror.azure.cn/docker-ce"
2023-05-10 19:39:29 +08:00
"网易@mirrors.163.com/docker-ce"
"火山引擎@mirrors.volces.com/docker"
2023-05-10 19:39:29 +08:00
"清华大学@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"
)
2024-06-07 16:33:26 +08:00
## Docker Registry 仓库列表
2023-06-24 11:06:30 +08:00
# 格式:"软件源名称@软件源地址"
2023-05-10 19:39:29 +08:00
mirror_list_registry=(
2024-10-29 22:51:53 +08:00
"Docker Proxy推荐@dockerproxy.net"
2024-06-07 16:33:26 +08:00
"道客 DaoCloud@docker.m.daocloud.io"
2024-07-26 19:05:33 +08:00
"AtomHub 可信镜像中心@hub.atomgit.com"
2023-05-10 19:39:29 +08:00
"阿里云(杭州)@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-2.aliyuncs.com"
"阿里云(马来西亚-吉隆坡)@registry.ap-southeast-3.aliyuncs.com"
"阿里云(印度尼西亚-雅加达)@registry.ap-southeast-5.aliyuncs.com"
"阿里云(印度-孟买)@registry.ap-south-1.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"
2024-06-07 16:33:26 +08:00
"腾讯云@mirror.ccs.tencentyun.com"
2023-06-24 11:06:30 +08:00
"谷歌云@mirror.gcr.io"
2024-07-26 19:05:33 +08:00
"官方 Docker Hub@registry.hub.docker.com"
2023-05-10 19:39:29 +08:00
)
2021-10-08 00:03:44 +08:00
## 定义系统判定变量
SYSTEM_DEBIAN="Debian"
SYSTEM_UBUNTU="Ubuntu"
SYSTEM_KALI="Kali"
SYSTEM_DEEPIN="Deepin"
SYSTEM_LINUX_MINT="Linuxmint"
2024-10-07 08:17:16 +08:00
SYSTEM_ZORIN="Zorin"
SYSTEM_RASPBERRY_PI_OS="Raspberry Pi OS"
2021-10-08 00:03:44 +08:00
SYSTEM_REDHAT="RedHat"
SYSTEM_RHEL="Red Hat Enterprise Linux"
2021-10-08 00:03:44 +08:00
SYSTEM_CENTOS="CentOS"
SYSTEM_CENTOS_STREAM="CentOS Stream"
SYSTEM_ROCKY="Rocky"
2023-05-10 19:39:29 +08:00
SYSTEM_ALMALINUX="AlmaLinux"
2021-10-08 00:03:44 +08:00
SYSTEM_FEDORA="Fedora"
2023-05-10 19:39:29 +08:00
SYSTEM_OPENCLOUDOS="OpenCloudOS"
2025-04-01 23:33:05 +08:00
SYSTEM_OPENCLOUDOS_STREAM="OpenCloudOS Stream"
SYSTEM_OPENEULER="openEuler"
SYSTEM_ANOLISOS="Anolis"
2025-04-01 23:33:05 +08:00
SYSTEM_OPENKYLIN="openKylin"
SYSTEM_OPENSUSE="openSUSE"
SYSTEM_ARCH="Arch"
SYSTEM_ALPINE="Alpine"
2025-04-01 23:33:05 +08:00
SYSTEM_GENTOO="Gentoo"
SYSTEM_NIXOS="NixOS"
2021-10-08 00:03:44 +08:00
## 定义系统版本文件
File_LinuxRelease=/etc/os-release
File_RedHatRelease=/etc/redhat-release
File_DebianVersion=/etc/debian_version
File_ArmbianRelease=/etc/armbian-release
2025-03-16 19:30:40 +08:00
File_RaspberryPiOSRelease=/etc/rpi-issue
File_openEulerRelease=/etc/openEuler-release
File_OpenCloudOSRelease=/etc/opencloudos-release
2024-10-31 02:51:48 +08:00
File_AnolisOSRelease=/etc/anolis-release
2025-04-01 23:33:05 +08:00
File_OracleLinuxRelease=/etc/oracle-release
2024-07-26 19:05:33 +08:00
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
2023-05-10 19:39:29 +08:00
Dir_YumRepos=/etc/yum.repos.d
2021-10-08 00:03:44 +08:00
## 定义 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'
2024-12-06 20:56:39 +08:00
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}"
2025-03-20 23:11:18 +08:00
WORKING="\033[1;36m◉${PLAIN}"
function main() {
permission_judgment
collect_system_info
2024-07-26 19:05:33 +08:00
run_start
choose_mirrors
2025-01-04 19:20:15 +08:00
choose_protocol
close_firewall_service
install_dependency_packages
configure_docker_ce_mirror
install_docker_engine
check_version
run_end
}
2024-07-26 19:05:33 +08:00
## 处理命令选项
function handle_command_options() {
## 命令帮助
function output_command_help() {
echo -e "
2024-11-08 15:02:45 +08:00
命令选项(名称/含义/值)
2024-07-26 19:05:33 +08:00
2025-01-04 19:20:15 +08:00
--source 指定 Docker CE 源地址(域名或IP) 地址
--source-registry 指定镜像仓库地址(域名或IP) 地址
--branch 指定 Docker CE 源仓库(路径) 仓库名
2024-12-06 20:56:39 +08:00
--codename 指定 Debian 系操作系统的版本代号 代号名称
2025-01-04 19:20:15 +08:00
--designated-version 指定 Docker CE 安装版本 版本号
--protocol 指定 Docker CE 源的 WEB 协议 http 或 https
2024-12-06 20:56:39 +08:00
--install-latest 是否安装最新版本的 Docker Engine truefalse
--close-firewall 是否关闭防火墙 truefalse
--clean-screen 是否在运行前清除屏幕上的所有内容 truefalse
--ignore-backup-tips 忽略覆盖备份提示 无
2025-04-05 17:21:37 +08:00
--pure-mode 纯净模式,精简打印内容 无
2024-07-26 19:05:33 +08:00
问题报告 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
2024-08-22 18:19:49 +08:00
output_error "命令选项 ${BLUE}$2${PLAIN} 无效,请在该选项后指定有效的地址!"
2024-07-26 19:05:33 +08:00
else
SOURCE="$(echo "$2" | sed -e 's,^http[s]\?://,,g' -e 's,/$,,')"
shift
fi
else
2024-08-22 18:19:49 +08:00
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定软件源地址!"
2024-07-26 19:05:33 +08:00
fi
;;
## 指定 Docker Registry 仓库地址
--source-registry)
if [ "$2" ]; then
echo "$2" | grep -Eq "\(|\)|\[|\]|\{|\}"
if [ $? -eq 0 ]; then
2024-08-22 18:19:49 +08:00
output_error "命令选项 ${BLUE}$2${PLAIN} 无效,请在该选项后指定有效的地址!"
2024-07-26 19:05:33 +08:00
else
SOURCE_REGISTRY="$(echo "$2" | sed -e 's,^http[s]\?://,,g' -e 's,/$,,')"
shift
fi
else
2024-08-22 18:19:49 +08:00
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定镜像仓库地址!"
2024-07-26 19:05:33 +08:00
fi
;;
2025-01-04 19:20:15 +08:00
## 指定 Docker CE 软件源仓库
--branch)
if [ "$2" ]; then
SOURCE_BRANCH="$2"
shift
else
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定软件源仓库!"
fi
;;
2024-07-26 19:05:33 +08:00
## 指定 Debian 版本代号
--codename)
if [ "$2" ]; then
DEBIAN_CODENAME="$2"
shift
else
2024-08-22 18:19:49 +08:00
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定版本代号!"
2024-07-26 19:05:33 +08:00
fi
;;
2025-01-04 19:20:15 +08:00
## 指定 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
;;
2024-07-26 19:05:33 +08:00
## 安装最新版本
2024-10-24 12:58:52 +08:00
--install-latest | --install-latested)
2024-07-26 19:05:33 +08:00
if [ "$2" ]; then
case "$2" in
[Tt]rue | [Ff]alse)
INSTALL_LATESTED_DOCKER="${2,,}"
shift
;;
*)
2024-08-22 18:19:49 +08:00
output_error "命令选项 ${BLUE}$2${PLAIN} 无效,请在该选项后指定 true 或 false "
2024-07-26 19:05:33 +08:00
;;
esac
else
2024-08-22 18:19:49 +08:00
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请在该选项后指定 true 或 false "
2024-07-26 19:05:33 +08:00
fi
;;
## 忽略覆盖备份提示
--ignore-backup-tips)
IGNORE_BACKUP_TIPS="true"
;;
2024-12-06 20:56:39 +08:00
## 关闭防火墙
--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
;;
2025-04-05 17:21:37 +08:00
## 纯净模式
--pure-mode)
PURE_MODE="true"
;;
2024-07-26 19:05:33 +08:00
## 命令帮助
--help)
output_command_help
exit
;;
*)
2024-08-22 18:19:49 +08:00
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,请确认后重新输入!"
2024-07-26 19:05:33 +08:00
;;
esac
shift
done
2025-04-05 17:21:37 +08:00
## 设置部分功能的默认值
2024-07-26 19:05:33 +08:00
IGNORE_BACKUP_TIPS="${IGNORE_BACKUP_TIPS:-"false"}"
2025-01-04 19:20:15 +08:00
if [[ "${DESIGNATED_DOCKER_VERSION}" ]]; then
INSTALL_LATESTED_DOCKER="false"
fi
2025-04-05 17:21:37 +08:00
PURE_MODE="${PURE_MODE:-"false"}"
2024-07-26 19:05:33 +08:00
}
function run_start() {
if [ -z "${CLEAN_SCREEN}" ]; then
[[ -z "${SOURCE}" || -z "${SOURCE_REGISTRY}" ]] && clear
elif [ "${CLEAN_SCREEN}" == "true" ]; then
clear
fi
2025-04-05 17:21:37 +08:00
if [[ "${PURE_MODE}" == "true" ]]; then
return
fi
echo -e "+-----------------------------------+"
2024-12-06 20:56:39 +08:00
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 |"
2025-04-05 17:21:37 +08:00
echo -e "+-----------------------------------+"
echo -e "欢迎使用 Docker Engine 安装与换源脚本"
2024-07-26 19:05:33 +08:00
}
## 运行结束
function run_end() {
2025-04-05 17:21:37 +08:00
if [[ "${PURE_MODE}" == "true" ]]; then
echo ''
return
fi
2025-04-08 02:28:46 +08:00
local sponsor_ad=(
2025-04-22 12:08:51 +08:00
"🔥 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"
2025-04-22 18:12:18 +08:00
"🔥 云悠YUNYOO · 全球高性价比云服务器低至15.99元起 ➜ https://yunyoo.cc"
2025-04-08 02:28:46 +08:00
)
echo -e "\n✨ 脚本运行完毕,更多使用教程详见官网 👉 \033[3mhttps://linuxmirrors.cn\033[0m\n"
for ad in "${sponsor_ad[@]}"; do
2025-04-22 12:08:51 +08:00
echo -e " ${ad} \033[3;2m【广告】\033[0m"
2025-04-08 02:28:46 +08:00
done
echo -e "\n\033[3;1mPowered by \033[34mLinuxMirrors\033[0m\n"
2023-05-10 19:39:29 +08:00
}
## 报错退出
function output_error() {
2023-05-10 19:39:29 +08:00
[ "$1" ] && echo -e "\n$ERROR $1\n"
exit 1
}
## 权限判定
function permission_judgment() {
2023-05-10 19:39:29 +08:00
if [ $UID -ne 0 ]; then
output_error "权限不足,请使用 Root 用户运行本脚本"
2023-05-10 19:39:29 +08:00
fi
}
## 收集系统信息
function collect_system_info() {
## 定义系统名称
SYSTEM_NAME="$(cat $File_LinuxRelease | grep -E "^NAME=" | awk -F '=' '{print$2}' | sed "s/[\'\"]//g")"
2024-07-26 19:05:33 +08:00
grep -q "PRETTY_NAME=" $File_LinuxRelease && SYSTEM_PRETTY_NAME="$(cat $File_LinuxRelease | grep -E "^PRETTY_NAME=" | awk -F '=' '{print$2}' | sed "s/[\'\"]//g")"
## 定义系统版本号
2025-04-01 23:33:05 +08:00
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")"
2024-01-18 12:15:13 +08:00
## 判定当前系统派系
2025-03-16 19:30:40 +08:00
if [ -s "${File_DebianVersion}" ]; then
SYSTEM_FACTIONS="${SYSTEM_DEBIAN}"
2025-04-01 23:33:05 +08:00
elif [ -s "${File_OracleLinuxRelease}" ]; then
output_error "当前操作系统Oracle Linux不在本脚本的支持范围内请前往官网查看支持列表"
2025-03-16 19:30:40 +08:00
elif [ -s "${File_RedHatRelease}" ]; then
2024-07-26 19:05:33 +08:00
SYSTEM_FACTIONS="${SYSTEM_REDHAT}"
2025-03-16 19:30:40 +08:00
elif [ -s "${File_openEulerRelease}" ]; then
SYSTEM_FACTIONS="${SYSTEM_OPENEULER}"
2025-03-16 19:30:40 +08:00
elif [ -s "${File_OpenCloudOSRelease}" ]; then
2025-01-04 19:20:15 +08:00
# 拦截 OpenCloudOS 9 及以上版本,不支持从 Docker 官方仓库安装
2025-04-01 23:33:05 +08:00
if [[ "${SYSTEM_VERSION_ID_MAJOR}" -ge 9 ]]; then
2024-10-31 02:51:48 +08:00
output_error "不支持当前操作系统,请参考如下命令自行安装:\n\ndnf install -y docker\nsystemctl enable --now docker"
fi
SYSTEM_FACTIONS="${SYSTEM_OPENCLOUDOS}" # 自 9.0 版本起不再基于红帽
2025-03-16 19:30:40 +08:00
elif [ -s "${File_AnolisOSRelease}" ]; then
2025-01-04 19:20:15 +08:00
# 拦截 Anolis OS 8.8 及以上版本,不支持从 Docker 官方仓库安装23 版本支持
2025-04-01 23:33:05 +08:00
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 版本起不再基于红帽
2021-10-17 09:09:00 +08:00
else
output_error "当前操作系统不在本脚本的支持范围内,请前往官网查看支持列表!"
2021-10-08 00:03:44 +08:00
fi
2024-07-26 19:05:33 +08:00
## 判定系统类型、版本、版本号
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
2025-03-20 10:18:12 +08:00
if ! command -v lsb_release &>/dev/null; then
2025-04-01 23:33:05 +08:00
apt-get update
2023-05-10 19:39:29 +08:00
apt-get install -y lsb-release
if [ $? -ne 0 ]; then
2024-07-26 19:05:33 +08:00
output_error "lsb-release 软件包安装失败\n\n本脚本依赖 lsb_release 指令判断系统具体类型和版本,当前系统可能为精简安装,请自行安装后重新执行脚本!"
2023-05-10 19:39:29 +08:00
fi
fi
SYSTEM_JUDGMENT="$(lsb_release -is)"
2023-06-20 06:04:56 +08:00
SYSTEM_VERSION_CODENAME="${DEBIAN_CODENAME:-"$(lsb_release -cs)"}"
# Raspberry Pi OS
2025-03-16 19:30:40 +08:00
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)"
2025-01-04 19:20:15 +08:00
# 拦截 Anolis OS 8.8 以下版本,不支持从 Docker 官方仓库安装
if [[ "${SYSTEM_JUDGMENT}" == "${SYSTEM_ANOLISOS}" ]]; then
output_error "不支持当前操作系统,请参考如下命令自行安装:\n\ndnf install -y docker\nsystemctl enable --now docker"
fi
2024-07-26 19:05:33 +08:00
## 特殊系统判断
# Red Hat Enterprise Linux
grep -q "${SYSTEM_RHEL}" $File_RedHatRelease && SYSTEM_JUDGMENT="${SYSTEM_RHEL}"
2024-07-26 19:05:33 +08:00
# CentOS Stream
grep -q "${SYSTEM_CENTOS_STREAM}" $File_RedHatRelease && SYSTEM_JUDGMENT="${SYSTEM_CENTOS_STREAM}"
;;
*)
SYSTEM_JUDGMENT="${SYSTEM_FACTIONS}"
2021-10-08 00:03:44 +08:00
;;
esac
## 判定系统处理器架构
DEVICE_ARCH_RAW="$(uname -m)"
case "${DEVICE_ARCH_RAW}" in
2021-10-08 00:03:44 +08:00
x86_64)
DEVICE_ARCH="x86_64"
2021-10-08 00:03:44 +08:00
;;
aarch64)
DEVICE_ARCH="ARM64"
;;
armv8l)
DEVICE_ARCH="ARMv8_32"
2021-10-08 00:03:44 +08:00
;;
armv7l)
DEVICE_ARCH="ARMv7"
2021-10-08 00:03:44 +08:00
;;
armv6l)
DEVICE_ARCH="ARMv6"
;;
armv5tel)
DEVICE_ARCH="ARMv5"
2021-10-08 00:03:44 +08:00
;;
2024-07-26 19:05:33 +08:00
ppc64le)
DEVICE_ARCH="ppc64le"
;;
s390x)
DEVICE_ARCH="s390x"
;;
i386 | i686)
output_error "Docker Engine 不支持安装在 x86_32 架构的环境上!"
2021-10-08 00:03:44 +08:00
;;
*)
output_error "未知的系统架构:${DEVICE_ARCH_RAW}"
2021-10-08 00:03:44 +08:00
;;
esac
2024-12-13 21:50:49 +08:00
## 定义软件源仓库名称
2025-01-04 19:20:15 +08:00
if [[ -z "${SOURCE_BRANCH}" ]]; then
case "${SYSTEM_FACTIONS}" in
2024-07-26 19:05:33 +08:00
"${SYSTEM_DEBIAN}")
2025-01-04 19:20:15 +08:00
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
;;
2025-01-04 19:20:15 +08:00
*)
# 部分 Debian 系衍生操作系统使用 Debian 12 的 docker ce 源
SOURCE_BRANCH="debian"
SYSTEM_VERSION_CODENAME="bookworm"
;;
esac
2024-07-26 19:05:33 +08:00
;;
2025-01-04 19:20:15 +08:00
"${SYSTEM_REDHAT}")
case "${SYSTEM_JUDGMENT}" in
"${SYSTEM_FEDORA}")
SOURCE_BRANCH="fedora"
;;
"${SYSTEM_RHEL}")
SOURCE_BRANCH="rhel"
;;
*)
SOURCE_BRANCH="centos"
;;
esac
;;
2025-01-04 19:20:15 +08:00
"${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2024-07-26 19:05:33 +08:00
SOURCE_BRANCH="centos"
;;
esac
2025-01-04 19:20:15 +08:00
fi
2024-01-18 12:15:13 +08:00
## 定义软件源更新文字
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
2024-01-18 12:15:13 +08:00
SYNC_MIRROR_TEXT="更新软件源"
2021-10-08 00:03:44 +08:00
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2024-01-18 12:15:13 +08:00
SYNC_MIRROR_TEXT="生成软件源缓存"
2021-10-08 00:03:44 +08:00
;;
esac
## 判断是否可以使用高级交互式选择器
CAN_USE_ADVANCED_INTERACTIVE_SELECTION="false"
2025-03-20 10:18:12 +08:00
if command -v tput &>/dev/null; then
CAN_USE_ADVANCED_INTERACTIVE_SELECTION="true"
fi
2021-10-08 00:03:44 +08:00
}
function choose_mirrors() {
2023-05-10 19:39:29 +08:00
## 打印软件源列表
function print_mirrors_list() {
2023-05-10 19:39:29 +08:00
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
2025-03-20 10:18:12 +08:00
if command -v printf &>/dev/null; then
2023-05-10 19:39:29 +08:00
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
2024-12-06 20:56:39 +08:00
printf "❖ %-$(($default_mirror_name_length + ${tmp_mirror_name_length}))s %4s\n" "${tmp_mirror_name}" "$arr_num)"
2023-05-10 19:39:29 +08:00
done
else
for ((i = 0; i < ${#list_arr[@]}; i++)); do
tmp_mirror_name="${list_arr[i]%@*}" # 软件源名称
tmp_mirror_url="${list_arr[i]#*@}" # 软件源地址
2023-05-10 19:39:29 +08:00
arr_num=$((i + 1))
echo -e "$arr_num. ${tmp_mirror_url} | ${tmp_mirror_name}"
done
fi
}
function print_title() {
2025-04-01 23:33:05 +08:00
local system_name="${SYSTEM_PRETTY_NAME:-"${SYSTEM_NAME} ${SYSTEM_VERSION_ID}"}"
local arch="${DEVICE_ARCH}"
2024-11-14 21:57:28 +08:00
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}')"
2023-05-10 19:39:29 +08:00
echo -e ''
2024-12-06 20:56:39 +08:00
echo -e "运行环境 ${BLUE}${system_name} ${arch}${PLAIN}"
echo -e "系统时间 ${BLUE}${date_time} ${timezone}${PLAIN}"
2023-05-10 19:39:29 +08:00
}
2025-04-05 17:21:37 +08:00
[[ "${PURE_MODE}" != "true" ]] && print_title
local mirror_list_name
if [[ -z "${SOURCE}" ]]; then
mirror_list_name="mirror_list_docker_ce"
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
2024-12-06 21:06:23 +08:00
sleep 1 >/dev/null 2>&1
eval "interactive_select_mirror \"\${${mirror_list_name}[@]}\" \"\\n \${BOLD}请选择你想使用的 Docker CE 源:\${PLAIN}\\n\""
SOURCE="${_SELECT_RESULT#*@}"
2025-01-04 19:20:15 +08:00
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
2023-05-10 19:39:29 +08:00
fi
if [[ -z "${SOURCE_REGISTRY}" ]]; then
mirror_list_name="mirror_list_registry"
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
2024-12-06 21:06:23 +08:00
sleep 1 >/dev/null 2>&1
eval "interactive_select_mirror \"\${${mirror_list_name}[@]}\" \"\\n \${BOLD}请选择你想使用的 Docker Registry 源:\${PLAIN}\\n\""
SOURCE_REGISTRY="${_SELECT_RESULT#*@}"
2025-04-01 23:33:05 +08:00
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
2023-05-10 19:39:29 +08:00
fi
}
2025-01-04 19:20:15 +08:00
## 选择同步或更新软件源所使用的 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,,}"
}
2023-05-10 19:39:29 +08:00
## 关闭防火墙和SELinux
function close_firewall_service() {
2025-03-20 10:18:12 +08:00
if ! command -v systemctl &>/dev/null; then
2024-01-18 12:15:13 +08:00
return
fi
if [[ "$(systemctl is-active firewalld)" == "active" ]]; then
if [[ -z "${CLOSE_FIREWALL}" ]]; then
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
echo ''
2024-12-06 20:56:39 +08:00
interactive_select_boolean "${BOLD}是否关闭系统防火墙和 SELinux ?${PLAIN}"
if [[ "${_SELECT_RESULT}" == "true" ]]; then
CLOSE_FIREWALL="true"
fi
else
2024-12-06 20:56:39 +08:00
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
2024-01-18 12:15:13 +08:00
fi
if [[ "${CLOSE_FIREWALL}" == "true" ]]; then
local SelinuxConfig=/etc/selinux/config
systemctl disable --now firewalld >/dev/null 2>&1
2025-03-16 19:30:40 +08:00
[ -s "${SelinuxConfig}" ] && sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" $SelinuxConfig && setenforce 0 >/dev/null 2>&1
fi
2021-10-08 00:03:44 +08:00
fi
}
## 安装环境包
function install_dependency_packages() {
2025-04-05 17:21:37 +08:00
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}")
2023-05-10 19:39:29 +08:00
rm -rf $Dir_YumRepos/*docker*.repo
2021-10-08 00:03:44 +08:00
;;
esac
2025-04-05 17:21:37 +08:00
## 更新软件源
commands=()
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
2024-11-02 03:29:54 +08:00
package_manager="apt-get"
2025-04-05 17:21:37 +08:00
commands+=("${package_manager} update")
2021-10-08 00:03:44 +08:00
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2024-11-02 03:29:54 +08:00
package_manager="$(get_package_manager)"
2025-04-05 17:21:37 +08:00
commands+=("${package_manager} makecache")
2021-10-08 00:03:44 +08:00
;;
esac
2025-04-05 17:21:37 +08:00
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
2024-11-02 03:29:54 +08:00
output_error "${SYNC_MIRROR_TEXT}出错,请先解决系统原有软件源错误以确保 ${BLUE}${package_manager}${PLAIN} 软件包管理工具可用!"
2021-10-08 00:03:44 +08:00
fi
2025-04-05 17:21:37 +08:00
commands=()
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
2025-04-05 17:21:37 +08:00
commands+=("${package_manager} install -y ca-certificates curl")
2021-10-08 00:03:44 +08:00
;;
2024-10-24 01:55:14 +08:00
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2025-04-05 17:21:37 +08:00
# 注:红帽 8 版本才发布了 dnf 包管理工具
2025-04-01 23:33:05 +08:00
case "${SYSTEM_VERSION_ID_MAJOR}" in
2023-05-10 19:39:29 +08:00
7)
2025-04-05 17:21:37 +08:00
commands+=("${package_manager} install -y yum-utils device-mapper-persistent-data lvm2")
2023-05-10 19:39:29 +08:00
;;
*)
2025-04-05 17:21:37 +08:00
commands+=("${package_manager} install -y dnf-plugins-core")
2023-05-10 19:39:29 +08:00
;;
esac
;;
2021-10-08 00:03:44 +08:00
esac
2025-04-05 17:21:37 +08:00
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
2021-10-08 00:03:44 +08:00
}
2024-10-31 02:51:48 +08:00
## 选择系统包管理器
function get_package_manager() {
local command="yum"
case "${SYSTEM_JUDGMENT}" in
"${SYSTEM_CENTOS_STREAM}" | "${SYSTEM_ROCKY}" | "${SYSTEM_ALMALINUX}" | "${SYSTEM_RHEL}")
2025-04-01 23:33:05 +08:00
case "${SYSTEM_VERSION_ID_MAJOR}" in
2025-03-14 18:57:40 +08:00
9 | 10)
2024-10-31 02:51:48 +08:00
command="dnf"
;;
esac
;;
"${SYSTEM_FEDORA}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2024-10-31 02:51:48 +08:00
command="dnf"
;;
esac
echo "${command}"
}
## 卸载 Docker Engine 原有版本软件包
function uninstall_original_version() {
2025-03-20 10:18:12 +08:00
if command -v docker &>/dev/null; then
2025-01-04 19:20:15 +08:00
# 先停止并禁用 Docker 服务
systemctl disable --now docker >/dev/null 2>&1
sleep 2s
fi
2024-07-26 19:05:33 +08:00
# 确定需要卸载的软件包
local package_list
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
2025-01-04 19:20:15 +08:00
package_list='docker* podman podman-docker containerd runc'
2024-07-26 19:05:33 +08:00
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2025-01-04 19:20:15 +08:00
package_list='docker* podman podman-docker runc'
2024-07-26 19:05:33 +08:00
;;
esac
# 卸载软件包并清理残留
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
2024-10-31 02:51:48 +08:00
apt-get remove -y $package_list >/dev/null 2>&1
2021-10-08 00:03:44 +08:00
apt-get autoremove -y >/dev/null 2>&1
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2024-10-31 02:51:48 +08:00
local package_manager="$(get_package_manager)"
$package_manager remove -y $package_list >/dev/null 2>&1
$package_manager autoremove -y >/dev/null 2>&1
2021-10-08 00:03:44 +08:00
;;
esac
}
## 配置 Docker CE 源
function configure_docker_ce_mirror() {
2025-04-05 17:21:37 +08:00
local commands=()
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
2024-07-26 19:05:33 +08:00
## 处理 GPG 密钥
local file_keyring="/etc/apt/keyrings/docker.asc"
2024-01-18 12:15:13 +08:00
apt-key del 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 >/dev/null 2>&1 # 删除旧的密钥
2025-03-16 19:30:40 +08:00
[ -f "${file_keyring}" ] && rm -rf $file_keyring
2023-06-19 19:37:01 +08:00
install -m 0755 -d /etc/apt/keyrings
2024-10-07 08:17:16 +08:00
curl -fsSL https://${SOURCE}/linux/${SOURCE_BRANCH}/gpg -o $file_keyring >/dev/null
if [ $? -ne 0 ]; then
output_error "GPG 密钥下载失败,请检查网络或更换 Docker CE 软件源后重试!"
fi
2024-07-26 19:05:33 +08:00
chmod a+r $file_keyring
2023-06-19 19:37:01 +08:00
## 添加源
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
2025-04-05 17:21:37 +08:00
commands+=("apt-get update")
2021-10-08 00:03:44 +08:00
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2025-04-05 17:21:37 +08:00
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
2025-01-04 19:20:15 +08:00
sed -i "s|https://download.docker.com|${WEB_PROTOCOL}://${SOURCE}|g" $Dir_YumRepos/docker-ce.repo
2023-05-10 19:39:29 +08:00
## 兼容处理版本号
if [[ "${SYSTEM_JUDGMENT}" != "${SYSTEM_FEDORA}" ]]; then
2024-07-26 19:05:33 +08:00
local target_version
2025-04-01 23:33:05 +08:00
case "${SYSTEM_VERSION_ID_MAJOR}" in
2025-03-16 20:16:18 +08:00
7 | 8 | 9 | 10)
2025-04-01 23:33:05 +08:00
target_version="${SYSTEM_VERSION_ID_MAJOR}"
2023-05-10 19:39:29 +08:00
;;
*)
2025-03-16 20:16:18 +08:00
## 目前红帽系衍生系统还没有普及 10 版本
target_version="9" # 使用较新的版本
2023-05-10 19:39:29 +08:00
;;
esac
2024-07-26 19:05:33 +08:00
sed -i "s|\$releasever|${target_version}|g" $Dir_YumRepos/docker-ce.repo
2024-10-31 02:51:48 +08:00
local package_manager="$(get_package_manager)"
2025-04-05 17:21:37 +08:00
commands+=("${package_manager} makecache")
2023-05-10 19:39:29 +08:00
fi
2021-10-08 00:03:44 +08:00
;;
esac
2025-04-05 17:21:37 +08:00
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
2021-10-08 00:03:44 +08:00
}
## 安装 Docker Engine
function install_docker_engine() {
2022-02-22 06:33:11 +08:00
## 导出可安装的版本列表
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
2022-02-22 06:33:11 +08:00
grep -wf $DockerCEVersionFile $DockerCECLIVersionFile >$DockerVersionFile
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2025-01-04 19:20:15 +08:00
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
2022-02-22 06:33:11 +08:00
grep -wf $DockerCEVersionFile $DockerCECLIVersionFile >$DockerVersionFile
;;
esac
rm -rf $DockerCEVersionFile $DockerCECLIVersionFile
}
## 安装
function install_main() {
2025-01-04 19:20:15 +08:00
local target_docker_version
2025-04-05 17:21:37 +08:00
local commands=()
2023-05-10 19:39:29 +08:00
if [[ "${INSTALL_LATESTED_DOCKER}" == "true" ]]; then
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
2025-04-05 17:21:37 +08:00
commands+=("apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin")
2022-02-22 06:33:11 +08:00
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2024-10-31 02:51:48 +08:00
local package_manager="$(get_package_manager)"
2025-04-05 17:21:37 +08:00
commands+=("${package_manager} install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin")
2022-02-22 06:33:11 +08:00
;;
esac
else
export_version_list
2025-03-16 19:30:40 +08:00
if [ ! -s "${DockerVersionFile}" ]; then
2025-01-04 19:20:15 +08:00
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"
2022-02-22 06:33:11 +08:00
else
2025-01-04 19:20:15 +08:00
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
2022-02-22 06:33:11 +08:00
fi
2025-01-04 19:20:15 +08:00
fi
rm -rf $DockerVersionFile
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
2025-04-01 23:33:05 +08:00
local major_version="$(echo ${target_docker_version} | cut -c1-2)"
local minor_version="$(echo ${target_docker_version} | cut -c4-5)"
case "${major_version}" in
2022-02-22 06:33:11 +08:00
18)
2025-04-01 23:33:05 +08:00
if [ "${minor_version}" == "09" ]; then
2022-02-22 06:33:11 +08:00
INSTALL_JUDGMENT="5:"
else
INSTALL_JUDGMENT=""
fi
;;
*)
INSTALL_JUDGMENT="5:"
2022-02-22 06:33:11 +08:00
;;
esac
2025-04-05 17:21:37 +08:00
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")
2022-02-22 06:33:11 +08:00
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2024-10-31 02:51:48 +08:00
local package_manager="$(get_package_manager)"
2025-04-05 17:21:37 +08:00
commands+=("${package_manager} install -y docker-ce-${target_docker_version} docker-ce-cli-${target_docker_version} containerd.io docker-buildx-plugin docker-compose-plugin")
2022-02-22 06:33:11 +08:00
;;
esac
fi
2025-04-05 17:21:37 +08:00
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 失败!"
2022-02-22 06:33:11 +08:00
}
## 修改 Docker Registry 镜像仓库源
function change_docker_registry_mirror() {
if [[ "${REGISTRY_SOURCEL}" == "registry.hub.docker.com" ]]; then
return
fi
2025-03-16 19:30:40 +08:00
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
2023-05-10 19:39:29 +08:00
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
2022-02-22 06:33:11 +08:00
fi
else
echo ''
cp -rvf $DockerConfig $DockerConfigBackup 2>&1
2025-04-01 23:33:05 +08:00
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"]\n}' >$DockerConfig
sed -i "s|SOURCE|${SOURCE_REGISTRY}|g" $DockerConfig
systemctl daemon-reload
if [[ $(systemctl is-active docker) == "active" ]]; then
systemctl restart docker
2022-02-22 06:33:11 +08:00
fi
}
2025-01-04 19:20:15 +08:00
## 判断是否手动选择安装版本
if [[ -z "${INSTALL_LATESTED_DOCKER}" ]]; then
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
2025-01-04 19:20:15 +08:00
echo ''
interactive_select_boolean "${BOLD}是否安装最新版本的 Docker Engine ?${PLAIN}"
if [[ "${_SELECT_RESULT}" == "true" ]]; then
2025-01-04 19:20:15 +08:00
INSTALL_LATESTED_DOCKER="true"
2021-10-08 00:03:44 +08:00
else
2025-01-04 19:20:15 +08:00
INSTALL_LATESTED_DOCKER="false"
2021-10-08 00:03:44 +08:00
fi
2025-01-04 19:20:15 +08:00
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])
2025-01-04 19:20:15 +08:00
INSTALL_LATESTED_DOCKER="true"
;;
[Nn] | [Nn][Oo])
INSTALL_LATESTED_DOCKER="false"
;;
*)
2025-01-04 19:20:15 +08:00
INSTALL_LATESTED_DOCKER="true"
echo -e "\n$WARN 输入错误,默认安装最新版本!"
;;
esac
2021-10-08 00:03:44 +08:00
fi
2025-01-04 19:20:15 +08:00
fi
2025-01-04 19:20:15 +08:00
## 判定是否已安装
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)"
2021-10-08 00:03:44 +08:00
rm -rf $DockerVersionFile
2025-01-04 19:20:15 +08:00
if [[ "${current_docker_version}" == "${latest_docker_version}" ]] && [[ "${INSTALL_LATESTED_DOCKER}" == "true" ]]; then
2025-04-01 23:33:05 +08:00
echo -e "\n$TIP 检测到系统已安装 Docker Engine 且是最新版本,跳过安装"
2025-01-04 19:20:15 +08:00
change_docker_registry_mirror
return
fi
2021-10-08 00:03:44 +08:00
fi
2025-01-04 19:20:15 +08:00
uninstall_original_version
install_main
change_docker_registry_mirror
2021-10-08 00:03:44 +08:00
}
## 查看版本并验证安装结果
function check_version() {
2025-03-20 10:18:12 +08:00
if command -v docker &>/dev/null; then
2025-01-04 19:20:15 +08:00
systemctl enable --now docker >/dev/null 2>&1
2024-07-26 19:05:33 +08:00
echo -en "\n当前安装版本"
2023-05-10 19:39:29 +08:00
docker -v
2024-12-13 21:50:49 +08:00
if [ $? -eq 0 ]; then
2025-01-04 19:20:15 +08:00
echo -e " $(docker compose version 2>&1)"
2025-04-01 23:33:05 +08:00
# echo -e "\n$COMPLETE 安装完成"
2023-05-10 19:39:29 +08:00
else
echo -e "\n$ERROR 安装失败"
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
echo -e "\n检查源文件cat $Dir_DebianExtendSource/docker.list"
2024-11-02 03:29:54 +08:00
echo -e '请尝试手动执行安装命令apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin\n'
2023-05-10 19:39:29 +08:00
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}")
2024-10-31 02:51:48 +08:00
local package_manager="$(get_package_manager)"
2025-04-05 17:21:37 +08:00
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"
2023-05-10 19:39:29 +08:00
;;
esac
exit 1
fi
2022-11-02 06:31:59 +08:00
if [[ $(systemctl is-active docker) != "active" ]]; then
2023-05-10 19:39:29 +08:00
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
2023-05-12 22:00:40 +08:00
echo -e "\n$ERROR 检测到 Docker 服务启动异常,可能由于重复安装导致"
echo -e "\n${YELLOW} 请执行 "systemctl start docker" 或 "service docker start" 命令尝试启动,如若报错请尝试重新执行本脚本${PLAIN}"
2023-05-10 19:39:29 +08:00
fi
2021-10-08 00:03:44 +08:00
fi
2023-05-10 19:39:29 +08:00
else
2025-04-01 23:33:05 +08:00
echo -e "\n$ERROR 安装失败"
2021-10-08 00:03:44 +08:00
fi
}
function interactive_select_mirror() {
_SELECT_RESULT=""
local options=("$@")
local message="${options[${#options[@]} - 1]}"
unset options[${#options[@]}-1]
local selected=0
local start=0
2025-03-20 23:11:18 +08:00
local page_size=$(($(tput lines 2>/dev/null) - 3)) # 减去3行用于显示提示信息
function clear_menu() {
2025-03-20 23:11:18 +08:00
tput rc 2>/dev/null
for ((i = 0; i < ${#options[@]} + 1; i++)); do
echo -e "\r\033[K"
done
2025-03-20 23:11:18 +08:00
tput rc 2>/dev/null
}
function cleanup() {
clear_menu
2025-03-20 23:11:18 +08:00
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"
2025-03-17 01:01:49 +08:00
exit 130
}
function draw_menu() {
2025-03-20 23:11:18 +08:00
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"
}
2025-03-20 23:11:18 +08:00
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
2024-12-13 21:50:49 +08:00
"[A" | "w" | "W")
# 上箭头 / W
if [ "$selected" -gt 0 ]; then
selected=$((selected - 1))
if [ "$selected" -lt "$start" ]; then
start=$((start - 1))
fi
fi
;;
2024-12-13 21:50:49 +08:00
"[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 # 清除菜单
2025-03-20 23:11:18 +08:00
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"
2025-03-17 00:02:32 +08:00
local menu_height=3 # 菜单总高度(标题行+空行+选项行)
local original_line
function store_position() {
# 保存菜单开始前的行位置
2025-03-20 23:11:18 +08:00
original_line=$(tput lines 2>/dev/null)
2025-03-17 00:02:32 +08:00
}
function clear_menu() {
2025-03-17 00:02:32 +08:00
# 向上移动到菜单开始位置并清除菜单
for ((i = 0; i < ${menu_height}; i++)); do
2025-03-20 23:11:18 +08:00
tput cuu1 2>/dev/null # 光标上移一行
tput el 2>/dev/null # 清除当前行
done
}
function cleanup() {
clear_menu
2025-03-20 23:11:18 +08:00
tput cnorm 2>/dev/null
echo -e "\n\033[1;44m 提示 \033[0m \033[31m操作已取消\033[0m\n"
2025-03-17 01:01:49 +08:00
exit 130
}
function draw_menu() {
2025-03-17 00:02:32 +08:00
# 绘制菜单不改变光标位置
echo -e "╭─ ${message}"
echo -e "│"
if [ "$selected" -eq 0 ]; then
2025-03-20 23:11:18 +08:00
echo -e "╰─ \033[34m●\033[0m 是\033[2m / ○ 否\033[0m"
else
2025-03-20 23:11:18 +08:00
echo -e "╰─ \033[2m○ 是 / \033[0m\033[34m●\033[0m 否"
fi
}
2025-03-17 00:02:32 +08:00
function read_key() {
IFS= read -rsn1 key
if [[ $key == $'\x1b' ]]; then
IFS= read -rsn2 key
key="$key"
fi
echo "$key"
}
2025-03-20 23:11:18 +08:00
tput civis 2>/dev/null # 隐藏光标
store_position # 记录当前位置
2025-03-17 00:02:32 +08:00
trap "cleanup" INT TERM
draw_menu # 初始化菜单位置
# 处理选择
while true; do
key=$(read_key)
case "$key" in
2024-12-13 21:50:49 +08:00
"[D" | "a" | "A")
# 左箭头 / A
if [ "$selected" -gt 0 ]; then
selected=$((selected - 1))
2025-03-17 00:02:32 +08:00
clear_menu
draw_menu
fi
;;
2024-12-13 21:50:49 +08:00
"[C" | "d" | "D")
# 右箭头 / D
if [ "$selected" -lt 1 ]; then
selected=$((selected + 1))
2025-03-17 00:02:32 +08:00
clear_menu
draw_menu
fi
;;
"")
# Enter 键
2025-03-17 00:02:32 +08:00
clear_menu # 先清除菜单
break
;;
*) ;;
esac
done
2025-03-17 00:02:32 +08:00
echo -e "╭─ ${message}"
echo -e "│"
if [ "$selected" -eq 0 ]; then
2025-03-17 00:02:32 +08:00
echo -e "╰─ \033[32m●\033[0m \033[1m是\033[0m\033[2m / ○ 否\033[0m"
_SELECT_RESULT="true"
else
2025-03-17 00:02:32 +08:00
echo -e "╰─ \033[2m○ 是 / \033[0m\033[32m●\033[0m \033[1m否\033[0m"
_SELECT_RESULT="false"
fi
2025-03-20 23:11:18 +08:00
tput cnorm 2>/dev/null # 恢复光标
}
2025-04-05 17:21:37 +08:00
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