Files
LinuxMirrors/DockerInstallation.sh
Super Manito ab64dd5ced 优化
2025-09-04 15:03:31 +08:00

1840 lines
71 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-09-04
## 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"
"网易@mirrors.163.com/docker-ce"
"火山引擎@mirrors.volces.com/docker"
"微软 Azure 中国@mirror.azure.cn/docker-ce"
"清华大学@mirrors.tuna.tsinghua.edu.cn/docker-ce"
"北京大学@mirrors.pku.edu.cn/docker-ce"
"浙江大学@mirrors.zju.edu.cn/docker-ce"
"南京大学@mirrors.nju.edu.cn/docker-ce"
"上海交通大学@mirror.sjtu.edu.cn/docker-ce"
"重庆邮电大学@mirrors.cqupt.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 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"
)
## 配置需要区分公网地址和内网地址的软件源(不分地域)
# 需要同时在两个数组变量中分别定义软件源地址,并且保证排列顺序一致
# 软件源公网地址列表
mirror_list_extranet=(
"mirrors.aliyun.com/docker-ce"
"mirrors.tencent.com/docker-ce"
"mirrors.huaweicloud.com/docker-ce"
"mirrors.volces.com/docker-ce"
)
# 软件源内网地址列表
mirror_list_intranet=(
"mirrors.cloud.aliyuncs.com/docker-ce"
"mirrors.tencentyun.com/docker-ce"
"mirrors.myhuaweicloud.com/docker-ce"
"mirrors.ivolces.com/docker-ce"
)
## 赞助商广告
SPONSOR_ADS=(
"1Panel · Linux 面板|极简运维 ➜ \033[3mhttps://1panel.cn\033[0m"
"多途云 · 智能化防护,每一次连接皆在安全之下 ➜ \033[3mhttps://www.duotuyun.com\033[0m"
"毫秒镜像 · 专为中国开发者提供Docker镜像加速下载服务 ➜ \033[3mhttps://1ms.run\033[0m"
"不死鸟CDN · 香港日本高防CDN免实名/免备案轻松阻断DDOS/CC攻击 ➜ \033[3mhttps://www.bsncdn.org\033[0m"
"青叶云 · 香港1T高防自助防火墙无视CC大带宽回国优化线路 ➜ \033[3mhttps://www.qingyeyun.com\033[0m"
"速拓云 · 国内高防云28元/月香港云100M优化线路9元/月 ➜ \033[3mhttps://www.sutuoyun.com\033[0m"
"林枫云 · 专注独立IP高频VPSR9/i9系列定制 ➜ \033[3mhttps://www.dkdun.cn\033[0m"
"云悠YUNYOO · 全球高性价比云服务器低至15.99元起 ➜ \033[3mhttps://yunyoo.cc\033[0m"
"语鹿云盾 · 专业CDN加速、防御亚太百兆三网优化CDN低至9元起 ➜ \033[3mhttps://www.lucdn.cn\033[0m"
"不二云 · 国内外建站快响应服务器的不二之选 ➜ \033[3mhttps://cb2.cn\033[0m"
"HKGserver · 全球家宽双ISP住宅原生云服务器54元/月起 ➜ \033[3mhttps://www.hkgserver.com\033[0m"
"圣道云 · 海外特价云服务器29元8核8G、9.9元2核2G免预存6折 ➜ \033[3mhttps://www.shengdaoyun.com\033[0m"
"浪浪云 · BGP网络让每一次连接都纵享丝滑明码标价、无套路续费 ➜ \033[3mhttps://langlangy.cn\033[0m"
)
##############################################################################
## 定义系统判定变量
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_ORACLE="Oracle Linux"
SYSTEM_OPENCLOUDOS="OpenCloudOS"
SYSTEM_OPENCLOUDOS_STREAM="OpenCloudOS Stream"
SYSTEM_TENCENTOS="TencentOS"
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_HuaweiCloudEulerOSRelease=/etc/hce-release
File_OpenCloudOSRelease=/etc/opencloudos-release
File_TencentOSServerRelease=/etc/tlinux-release
File_AnolisOSRelease=/etc/anolis-release
File_AlibabaCloudLinuxRelease=/etc/alinux-release
File_OracleLinuxRelease=/etc/oracle-release
File_ArchLinuxRelease=/etc/arch-release
File_ManjaroRelease=/etc/manjaro-release
File_AlpineRelease=/etc/alpine-release
File_GentooRelease=/etc/gentoo-release
File_openKylinVersion=/etc/kylin-version/kylin-system-version.conf
## 定义软件源相关文件或目录
File_AptSourceList=/etc/apt/sources.list
Dir_AptAdditionalSources=/etc/apt/sources.list.d
Dir_YumRepos=/etc/yum.repos.d
## 定义 Docker 相关变量
Dir_Docker=/etc/docker
File_DockerConfig=$Dir_Docker/daemon.json
File_DockerConfigBackup=$Dir_Docker/daemon.json.bak
File_DockerVersionTmp=docker-version.txt
File_DockerCEVersionTmp=docker-ce-version.txt
File_DockerCECliVersionTmp=docker-ce-cli-version.txt
File_DockerSourceList=$Dir_AptAdditionalSources/docker.list
File_DockerRepo=$Dir_YumRepos/docker-ce.repo
## 定义颜色和样式变量
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 指定 Docker 镜像仓库地址(域名或IP) 地址
--branch 指定 Docker CE 软件源仓库(路径) 仓库名
--codename 指定 Debian 系操作系统的版本代号 代号名称
--designated-version 指定 Docker CE 安装版本 版本号
--protocol 指定 Docker CE 软件源的 WEB 协议 http 或 https
--use-intranet-source 是否优先使用内网 Docker CE 软件源地址 true 或 false
--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
command_error "$2" "有效的地址"
else
SOURCE="$(echo "$2" | sed -e 's,^http[s]\?://,,g' -e 's,/$,,')"
shift
fi
else
command_error "$1" "软件源地址"
fi
;;
## 指定 Docker Registry 仓库地址
--source-registry)
if [ "$2" ]; then
echo "$2" | grep -Eq "\(|\)|\[|\]|\{|\}"
if [ $? -eq 0 ]; then
command_error "$2" "有效的地址"
else
SOURCE_REGISTRY="$(echo "$2" | sed -e 's,^http[s]\?://,,g' -e 's,/$,,')"
shift
fi
else
command_error "$1" "镜像仓库地址"
fi
;;
## 指定 Docker CE 软件源仓库
--branch)
if [ "$2" ]; then
SOURCE_BRANCH="$2"
shift
else
command_error "$1" "软件源仓库"
fi
;;
## 指定 Debian 版本代号
--codename)
if [ "$2" ]; then
DEBIAN_CODENAME="$2"
shift
else
command_error "$1" "版本代号"
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
command_error "$2" "有效的版本号"
fi
else
command_error "$1" "版本号"
fi
;;
## WEB 协议HTTP/HTTPS
--protocol)
if [ "$2" ]; then
case "$2" in
http | https | HTTP | HTTPS)
WEB_PROTOCOL="${2,,}"
shift
;;
*)
command_error "$2" " http 或 https "
;;
esac
else
ocommand_error "$1" " WEB 协议 (http/https) "
fi
;;
## 使用内网地址
--use-intranet-source)
if [ "$2" ]; then
case "$2" in
[Tt]rue | [Ff]alse)
USE_INTRANET_SOURCE="${2,,}"
shift
;;
*)
command_error "$2" " true 或 false "
;;
esac
else
command_error "$1" " true 或 false "
fi
;;
## 安装最新版本
--install-latest | --install-latested)
if [ "$2" ]; then
case "$2" in
[Tt]rue | [Ff]alse)
INSTALL_LATESTED_DOCKER="${2,,}"
shift
;;
*)
command_error "$2" " true 或 false "
;;
esac
else
command_error "$1" " 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
;;
*)
command_error "$2" " true 或 false "
;;
esac
else
command_error "$1" " true 或 false "
fi
;;
## 清除屏幕上的所有内容
--clean-screen)
if [ "$2" ]; then
case "$2" in
[Tt]rue | [Ff]alse)
CLEAN_SCREEN="${2,,}"
shift
;;
*)
command_error "$2" " true 或 false "
;;
esac
else
command_error "$1" " true 或 false "
fi
;;
## 仅更换镜像仓库模式
--only-registry)
ONLY_REGISTRY="true"
;;
## 纯净模式
--pure-mode)
PURE_MODE="true"
;;
## 命令帮助
--help)
output_command_help
exit
;;
*)
command_error "$1"
;;
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
echo -e "\n✨ 脚本运行完毕,更多使用教程详见官网 👉 \033[3mhttps://linuxmirrors.cn\033[0m"
if [[ "${#SPONSOR_ADS[@]}" -gt 0 ]]; then
echo -e "\n\033[2m【赞助商广告】\033[0m"
for ad in "${SPONSOR_ADS[@]}"; do
sleep 0.1
echo -e " \033[2m${ad}\033[0m"
done
fi
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 command_error() {
local tmp_text="请确认后重新输入"
if [[ "${2}" ]]; then
tmp_text="请在该选项后指定${2}"
fi
output_error "命令选项 ${BLUE}$1${PLAIN} 无效,${tmp_text}"
}
function unsupport_system_error() {
local tmp_text=""
if [[ "${2}" ]]; then
tmp_text=",请参考如下命令自行安装:\n\n${BLUE}$2${PLAIN}"
fi
output_error "不支持当前操作系统($1${tmp_text}"
}
function input_error() {
echo -e "\n$WARN 输入错误,$1"
}
function command_exists() {
command -v "$@" &>/dev/null
}
function permission_judgment() {
if [ $UID -ne 0 ]; then
output_error "权限不足,请使用 Root 用户运行本脚本"
fi
}
function get_os_release_value() {
grep -E "^${1}=" $File_LinuxRelease | cut -d= -f2- | sed "s/[\'\"]//g"
}
function collect_system_info() {
## 定义系统名称
SYSTEM_NAME="$(get_os_release_value NAME)"
SYSTEM_PRETTY_NAME="$(get_os_release_value PRETTY_NAME)"
## 定义系统版本号
SYSTEM_VERSION_ID="$(get_os_release_value VERSION_ID)"
SYSTEM_VERSION_ID_MAJOR="${SYSTEM_VERSION_ID%.*}"
SYSTEM_VERSION_ID_MINOR="${SYSTEM_VERSION_ID#*.}"
## 定义系统ID
SYSTEM_ID="$(get_os_release_value ID)"
## 判定当前系统派系
if [ -s "${File_DebianVersion}" ]; then
SYSTEM_FACTIONS="${SYSTEM_DEBIAN}"
elif [ -s "${File_RedHatRelease}" ]; then
SYSTEM_FACTIONS="${SYSTEM_REDHAT}"
elif [ -s "${File_openEulerRelease}" ] || [ -s "${File_HuaweiCloudEulerOSRelease}" ]; then
SYSTEM_FACTIONS="${SYSTEM_OPENEULER}"
elif [ -s "${File_OpenCloudOSRelease}" ]; then
SYSTEM_FACTIONS="${SYSTEM_OPENCLOUDOS}" # 自 9.0 版本起不再基于红帽
elif [ -s "${File_AnolisOSRelease}" ]; then
SYSTEM_FACTIONS="${SYSTEM_ANOLISOS}" # 自 8.8 版本起不再基于红帽
elif [ -s "${File_TencentOSServerRelease}" ]; then
SYSTEM_FACTIONS="${SYSTEM_TENCENTOS}" # 自 4 版本起不再基于红帽
elif [ -s "${File_openKylinVersion}" ]; then
[[ "${ONLY_REGISTRY}" != "true" ]] && unsupport_system_error "openKylin" "apt-get install -y docker\nsystemctl enable --now docker"
elif [ -f "${File_ArchLinuxRelease}" ]; then
[[ "${ONLY_REGISTRY}" != "true" ]] && unsupport_system_error "Arch Linux" "pacman -S docker\nsystemctl enable --now docker"
elif [ -f "${File_GentooRelease}" ]; then
[[ "${ONLY_REGISTRY}" != "true" ]] && unsupport_system_error "Gentoo"
elif [[ "${SYSTEM_NAME}" == *"openSUSE"* ]]; then
[[ "${ONLY_REGISTRY}" != "true" ]] && unsupport_system_error "openSUSE" "zypper install docker\nsystemctl enable --now docker"
elif [[ "${SYSTEM_NAME}" == *"NixOS"* ]]; then
[[ "${ONLY_REGISTRY}" != "true" ]] && unsupport_system_error "NixOS"
else
unsupport_system_error "未知的系统"
fi
## 判定系统类型、版本、版本号
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
if command_exists lsb_release; then
SYSTEM_JUDGMENT="$(lsb_release -is)"
SYSTEM_VERSION_CODENAME="${DEBIAN_CODENAME:-"$(lsb_release -cs)"}"
else
## https://codeberg.org/gioele/lsb-release-minimal
SYSTEM_JUDGMENT="${SYSTEM_ID^}"
if [ "${SYSTEM_NAME}" ]; then
if [[ "${SYSTEM_ID,,}" == "${SYSTEM_NAME,,}" ]]; then
SYSTEM_JUDGMENT="${SYSTEM_NAME}"
fi
fi
SYSTEM_VERSION_CODENAME="${DEBIAN_CODENAME:-"$(get_os_release_value VERSION_CODENAME)"}"
fi
# 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)"
## 特殊系统判断
# 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}"
# Oracle Linux
[ -s "${File_OracleLinuxRelease}" ] && SYSTEM_JUDGMENT="${SYSTEM_ORACLE}"
;;
*)
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_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}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}" | "${SYSTEM_TENCENTOS}")
case "${SYSTEM_JUDGMENT}" in
"${SYSTEM_FEDORA}")
SOURCE_BRANCH="fedora"
;;
"${SYSTEM_RHEL}")
SOURCE_BRANCH="rhel"
# RHEL 10
if [[ "${SYSTEM_VERSION_ID_MAJOR}" == 10 ]]; then
echo -e "\n$WARN 当前采用 centos 分支(红帽衍生操作系统安装方式)进行安装,可能存在某些无法预料的兼容性问题!"
echo -e "\n$TIP Docker 官方尚未支持 RHEL 10 且红帽官方已将 Docker 从注册软件源中移除并默认使用 Podman。"
SOURCE_BRANCH="centos"
fi
;;
*)
SOURCE_BRANCH="centos"
;;
esac
if [[ "${DEVICE_ARCH_RAW}" == "s390x" ]]; then
output_error "请查阅 RHEL 发行版声明以了解 s390x 支持"
fi
;;
esac
fi
## 定义软件源更新文字
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
SYNC_MIRROR_TEXT="更新软件源"
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}" | "${SYSTEM_TENCENTOS}")
SYNC_MIRROR_TEXT="生成软件源缓存"
;;
esac
## 判断是否可以使用高级交互式选择器
CAN_USE_ADVANCED_INTERACTIVE_SELECTION="false"
if command_exists tput; 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_length tmp_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_exists printf; 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_length=${2:-"30"}
[[ $(echo "${tmp_mirror_name}" | grep -c "“") -gt 0 ]] && let default_length+=$(echo "${tmp_mirror_name}" | grep -c "“")
[[ $(echo "${tmp_mirror_name}" | grep -c "”") -gt 0 ]] && let default_length+=$(echo "${tmp_mirror_name}" | grep -c "”")
[[ $(echo "${tmp_mirror_name}" | grep -c "") -gt 0 ]] && let default_length+=$(echo "${tmp_mirror_name}" | grep -c "")
[[ $(echo "${tmp_mirror_name}" | grep -c "") -gt 0 ]] && let default_length+=$(echo "${tmp_mirror_name}" | grep -c "")
tmp_length=$(StringLength $(echo "${tmp_mirror_name}" | sed "s| ||g" | sed "s|[0-9a-zA-Z\.\=\:\_\(\)\'\"-\/\!·]||g;"))
tmp_spaces_nums=$(($(($default_length - ${tmp_length} - $(StringLength "${tmp_mirror_name}"))) / 2))
for ((j = 1; j <= ${tmp_spaces_nums}; j++)); do
tmp_mirror_name="${tmp_mirror_name} "
done
printf "❖ %-$(($default_length + ${tmp_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 choose_use_intranet_address() {
local ask_text="默认使用软件源的公网地址,是否继续?"
local intranet_source
for ((i = 0; i < ${#mirror_list_extranet[@]}; i++)); do
if [[ "${SOURCE}" == "${mirror_list_extranet[i]}" ]]; then
intranet_source="${mirror_list_intranet[i]}"
ONLY_HTTP="true" # 强制使用 HTTP 协议
break
else
continue
fi
done
if [[ -z "${USE_INTRANET_SOURCE}" ]]; then
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
echo ''
interactive_select_boolean "${BOLD}${ask_text}${PLAIN}"
if [[ "${_SELECT_RESULT}" == "false" ]]; then
SOURCE="${intranet_source}"
[[ "${PURE_MODE}" != "true" ]] && echo -e "\n$WARN 已切换至内网专用地址,仅限在特定环境下使用!"
fi
else
local CHOICE="$(echo -e "\n${BOLD}└─ ${ask_text} [Y/n] ${PLAIN}")"
read -rp "${CHOICE}" INPUT
[[ -z "${INPUT}" ]] && INPUT=Y
case "${INPUT}" in
[Yy] | [Yy][Ee][Ss]) ;;
[Nn] | [Nn][Oo])
SOURCE="${intranet_source}"
[[ "${PURE_MODE}" != "true" ]] && echo -e "\n$WARN 已切换至内网专用地址,仅限在特定环境下使用!"
;;
*)
input_error "默认不使用内网地址"
;;
esac
fi
elif [[ "${USE_INTRANET_SOURCE}" == "true" ]]; then
SOURCE="${intranet_source}"
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 [[ "${mirror_list_extranet[*]}" =~ (^|[^[:alpha:]])"${SOURCE}"([^[:alpha:]]|$) ]]; then
choose_use_intranet_address
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
local ask_text="软件源是否使用 HTTP 协议?"
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
echo ''
interactive_select_boolean "${BOLD}${ask_text}${PLAIN}"
if [[ "${_SELECT_RESULT}" == "true" ]]; then
WEB_PROTOCOL="http"
else
WEB_PROTOCOL="https"
fi
else
local CHOICE="$(echo -e "\n${BOLD}└─ ${ask_text} [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"
;;
*)
input_error "默认使用 HTTPS 协议"
WEB_PROTOCOL="https"
;;
esac
fi
fi
fi
WEB_PROTOCOL="${WEB_PROTOCOL,,}"
}
## 关闭防火墙和SELinux
function close_firewall_service() {
if ! command_exists systemctl; then
return
fi
if [[ "$(systemctl is-active firewalld)" == "active" ]]; then
if [[ -z "${CLOSE_FIREWALL}" ]]; then
local ask_text="是否关闭系统防火墙和 SELinux ?"
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
echo ''
interactive_select_boolean "${BOLD}${ask_text}${PLAIN}"
if [[ "${_SELECT_RESULT}" == "true" ]]; then
CLOSE_FIREWALL="true"
fi
else
local CHOICE="$(echo -e "\n${BOLD}└─ ${ask_text} [Y/n] ${PLAIN}")"
read -rp "${CHOICE}" INPUT
[[ -z "${INPUT}" ]] && INPUT=Y
case "${INPUT}" in
[Yy] | [Yy][Ee][Ss])
CLOSE_FIREWALL="true"
;;
[Nn] | [Nn][Oo]) ;;
*)
input_error "默认不关闭"
;;
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_AptSourceList
rm -rf $File_DockerSourceList
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}" | "${SYSTEM_TENCENTOS}")
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}" | "${SYSTEM_TENCENTOS}")
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}" | "${SYSTEM_TENCENTOS}")
# 注:红帽 8 版本才发布了 dnf 包管理工具
case "${SYSTEM_VERSION_ID_MAJOR}" in
7)
commands+=("${package_manager} install -y yum-utils device-mapper-persistent-data lvm2")
;;
*)
if [[ "${package_manager}" == "dnf" ]]; then
commands+=("${package_manager} install -y dnf-plugins-core")
else
commands+=("${package_manager} install -y yum-utils device-mapper-persistent-data lvm2")
fi
;;
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 "${WEB_PROTOCOL}://${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
## 添加源
local source_content="deb [arch=$(dpkg --print-architecture) signed-by=${file_keyring}] ${WEB_PROTOCOL}://${SOURCE}/linux/${SOURCE_BRANCH} ${SYSTEM_VERSION_CODENAME} stable"
echo "${source_content}" | tee $File_DockerSourceList >/dev/null 2>&1
commands+=("apt-get update")
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}" | "${SYSTEM_TENCENTOS}")
local repo_file_url="${WEB_PROTOCOL}://${SOURCE}/linux/${SOURCE_BRANCH}/docker-ce.repo"
local package_manager="$(get_package_manager)"
case "${SYSTEM_VERSION_ID_MAJOR}" in
7)
yum-config-manager -y --add-repo "${repo_file_url}"
;;
*)
if [[ "${SYSTEM_JUDGMENT}" == "${SYSTEM_FEDORA}" ]]; then
dnf-3 config-manager -y --add-repo "${repo_file_url}"
else
if [[ "${package_manager}" == "dnf" ]]; then
dnf config-manager -y --add-repo "${repo_file_url}"
else
yum-config-manager -y --add-repo "${repo_file_url}"
fi
fi
;;
esac
sed -e "s|https://download.docker.com|${WEB_PROTOCOL}://${SOURCE}|g" \
-e "s|http[s]\?://.*/linux/${SOURCE_BRANCH}/|${WEB_PROTOCOL}://${SOURCE}/linux/${SOURCE_BRANCH}/|g" \
-i \
$File_DockerRepo
## 兼容处理版本号
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}"
;;
*)
target_version="8" # 注部分系统使用9版本分支会有兼容性问题
## 适配国产操作系统
# OpenCloudOS、Anolis OS 的 23 版本
if [[ "${SYSTEM_JUDGMENT}" == "${SYSTEM_OPENCLOUDOS}" || "${SYSTEM_JUDGMENT}" == "${SYSTEM_ANOLISOS}" ]]; then
if [[ "${SYSTEM_VERSION_ID_MAJOR}" == 23 ]]; then
target_version="9"
fi
fi
if [[ "${SYSTEM_JUDGMENT}" == "${SYSTEM_OPENEULER}" ]]; then
if [ -s "${File_HuaweiCloudEulerOSRelease}" ]; then
# Huawei Cloud EulerOS
case "${SYSTEM_VERSION_ID_MAJOR}" in
1)
target_version="8" # openEuler 20
;;
2)
target_version="9" # openEuler 22
;;
esac
else
# openEuler
if [[ "${SYSTEM_VERSION_ID_MAJOR}" -ge 22 ]]; then
target_version="9"
fi
fi
fi
# TencentOS Server
if [ -s "${File_TencentOSServerRelease}" ]; then
case "${SYSTEM_VERSION_ID_MAJOR}" in
4)
target_version="9"
;;
3)
target_version="8"
;;
2)
target_version="7"
;;
esac
fi
# Alibaba Cloud Linux
if [ -s "${File_AnolisOSRelease}" ] && [ -s "${File_AlibabaCloudLinuxRelease}" ]; then
case "${SYSTEM_VERSION_ID_MAJOR}" in
3)
target_version="8"
;;
2)
target_version="7"
;;
esac
fi
;;
esac
sed -i "s|\$releasever|${target_version}|g" $File_DockerRepo
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}" >$File_DockerCEVersionTmp
apt-cache madison docker-ce-cli | awk '{print $3}' | grep -Eo "[0-9][0-9].[0-9]{1,2}.[0-9]{1,2}" >$File_DockerCECliVersionTmp
grep -wf $File_DockerCEVersionTmp $File_DockerCECliVersionTmp >$File_DockerVersionTmp
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}" | "${SYSTEM_TENCENTOS}")
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}" >$File_DockerCEVersionTmp
$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}" >$File_DockerCECliVersionTmp
grep -wf $File_DockerCEVersionTmp $File_DockerCECliVersionTmp >$File_DockerVersionTmp
;;
esac
rm -rf $File_DockerCEVersionTmp $File_DockerCECliVersionTmp
}
## 卸载 Docker Engine 原有版本软件包
function uninstall_original_version() {
if command_exists docker; 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}" | "${SYSTEM_TENCENTOS}")
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}" | "${SYSTEM_TENCENTOS}")
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 pkgs=""
local commands=()
if [[ "${INSTALL_LATESTED_DOCKER}" == "true" ]]; then
pkgs="docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin"
else
export_version_list
if [ ! -s "${File_DockerVersionTmp}" ]; then
rm -rf $File_DockerVersionTmp
output_error "查询 Docker Engine 版本列表失败!"
fi
if [[ "${DESIGNATED_DOCKER_VERSION}" ]]; then
cat $File_DockerVersionTmp | grep -Eq "^${DESIGNATED_DOCKER_VERSION}$"
if [ $? -ne 0 ]; then
rm -rf $File_DockerVersionTmp
output_error "指定的 Docker Engine 版本不存在或不支持安装!"
fi
target_docker_version="${DESIGNATED_DOCKER_VERSION}"
else
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
local version_list=(
$(cat $File_DockerVersionTmp | 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} --------- 请选择你要安装的版本28.3.0 ---------- ${PLAIN}\n"
cat $File_DockerVersionTmp
while true; do
local CHOICE=$(echo -e "\n${BOLD}└─ 请根据上面的列表,选择并输入你想要安装的具体版本号:${PLAIN}\n")
read -p "${CHOICE}" target_docker_version
echo ''
cat $File_DockerVersionTmp | 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 $File_DockerVersionTmp
local major_version="$(echo ${target_docker_version} | cut -d'.' -f1)"
local minor_version="$(echo ${target_docker_version} | cut -d'.' -f2)"
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
if [[ $major_version -gt 18 ]] || [[ $major_version -eq 18 && $minor_version -ge 9 ]]; then
local tmp_version="$(apt-cache madison docker-ce-cli | grep "${target_docker_version}" | head -1 | awk '{print $3}' | awk -F "${target_docker_version}" '{print$1}')"
pkgs="docker-ce=${tmp_version}${target_docker_version}* docker-ce-cli=${tmp_version}${target_docker_version}*"
else
pkgs="docker-ce=${target_docker_version}* docker-ce-cli=${target_docker_version}*"
fi
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}" | "${SYSTEM_TENCENTOS}")
pkgs="docker-ce-${target_docker_version}"
if [[ $major_version -gt 18 ]] || [[ $major_version -eq 18 && $minor_version -ge 9 ]]; then
pkgs="${pkgs} docker-ce-cli-${target_docker_version}"
fi
;;
esac
pkgs="${pkgs} containerd.io"
if [[ $major_version -gt 20 ]] || [[ $major_version -eq 20 && $minor_version -ge 10 ]]; then
pkgs="${pkgs} docker-compose-plugin"
fi
if [[ $major_version -ge 23 ]]; then
pkgs="${pkgs} docker-buildx-plugin"
fi
fi
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
commands+=("apt-get install -y ${pkgs}")
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}" | "${SYSTEM_TENCENTOS}")
commands+=("$(get_package_manager) install -y ${pkgs}")
;;
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
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
local ask_text="是否安装最新版本的 Docker Engine ?"
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
echo ''
interactive_select_boolean "${BOLD}${ask_text}${PLAIN}"
if [[ "${_SELECT_RESULT}" == "true" ]]; then
INSTALL_LATESTED_DOCKER="true"
else
INSTALL_LATESTED_DOCKER="false"
fi
else
local CHOICE_A="$(echo -e "\n${BOLD}└─ ${ask_text} [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"
input_error "默认安装最新版本"
;;
esac
fi
echo ''
fi
## 判定是否已安装
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
dpkg -l | grep docker-ce-cli -q
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}" | "${SYSTEM_TENCENTOS}")
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 $File_DockerVersionTmp | head -n 1)"
rm -rf $File_DockerVersionTmp
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 "${File_DockerConfig}" ]; then
## 安装 jq
local package_manager="$(get_package_manager)"
$package_manager install -y jq
if command_exists jq; then
jq 'del(.["registry-mirrors"])' $File_DockerConfig >$File_DockerConfig.tmp && mv $File_DockerConfig.tmp $File_DockerConfig
# 重启服务
systemctl daemon-reload
if [[ "$(systemctl is-active docker 2>/dev/null)" == "active" ]]; then
systemctl restart docker
fi
else
echo -e "\n${WARN} 请自行删除 $File_DockerConfig 中的 ${BLUE}registry-mirrors${PLAIN} 配置并重启服务 ${BLUE}systemctl daemon-reload && systemctl restart docker${PLAIN}\n"
fi
fi
return
fi
## 备份原有配置文件
if [ -d "${Dir_Docker}" ] && [ -e "${File_DockerConfig}" ]; then
if [ -e "${File_DockerConfigBackup}" ]; then
if [[ "${IGNORE_BACKUP_TIPS}" == "false" ]]; then
local ask_text="检测到已备份的 Docker 配置文件,是否跳过覆盖备份?"
if [[ "${CAN_USE_ADVANCED_INTERACTIVE_SELECTION}" == "true" ]]; then
echo ''
interactive_select_boolean "${BOLD}${ask_text}${PLAIN}"
if [[ "${_SELECT_RESULT}" == "false" ]]; then
echo ''
cp -rvf $File_DockerConfig $File_DockerConfigBackup 2>&1
fi
else
local CHOICE_BACKUP="$(echo -e "\n${BOLD}└─ ${ask_text} [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 $File_DockerConfig $File_DockerConfigBackup 2>&1
;;
*)
input_error "默认不覆盖"
;;
esac
fi
fi
else
echo ''
cp -rvf $File_DockerConfig $File_DockerConfigBackup 2>&1
echo -e "\n$COMPLETE 已备份原有 Docker 配置文件"
fi
sleep 2s
else
mkdir -p $Dir_Docker >/dev/null 2>&1
touch $File_DockerConfig
fi
echo -e '{\n "registry-mirrors": ["https://'"${SOURCE_REGISTRY}"'"]\n}' >$File_DockerConfig
## 重启服务
systemctl daemon-reload
if [[ "$(systemctl is-active docker 2>/dev/null)" == "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}" | "${SYSTEM_TENCENTOS}")
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 "${Dir_Docker}" ] || mkdir -p "${Dir_Docker}"
if [ -s "${File_DockerConfig}" ]; then
## 安装 jq
if ! command_exists jq; 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}" | "${SYSTEM_TENCENTOS}")
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_exists jq; then
output_error "软件包 ${BLUE}jq${PLAIN} 安装失败,请自行安装后重新运行脚本!"
fi
fi
[ -s "${File_DockerConfig}" ] || echo "{}" >$File_DockerConfig
jq '.["registry-mirrors"] = ["https://'"${SOURCE_REGISTRY}"'"]' $File_DockerConfig >$File_DockerConfig.tmp && mv $File_DockerConfig.tmp $File_DockerConfig
else
echo -e '{\n "registry-mirrors": ["https://'"${SOURCE_REGISTRY}"'"]\n}' >$File_DockerConfig
fi
echo -e "\n${BLUE}\$${PLAIN} docker info --format '{{json .RegistryConfig.Mirrors}}'"
echo -e "\n${GREEN}${PLAIN} $(docker info --format '{{json .RegistryConfig.Mirrors}}')"
## 重启服务
systemctl daemon-reload
if [[ "$(systemctl is-active docker 2>/dev/null)" == "active" ]]; then
systemctl restart docker
fi
if [[ "${PURE_MODE}" != "true" ]]; then
echo -e "\n$COMPLETE 已更换镜像仓库"
fi
}
## 查看版本并验证安装结果
function check_installed_result() {
if command_exists docker; 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$FAIL 安装失败"
local source_file package_manager
case "${SYSTEM_FACTIONS}" in
"${SYSTEM_DEBIAN}")
source_file="${File_DockerSourceList}"
package_manager="apt-get"
;;
"${SYSTEM_REDHAT}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}" | "${SYSTEM_TENCENTOS}")
source_file="${File_DockerRepo}"
package_manager="$(get_package_manager)"
;;
esac
echo -e "\n检查源文件cat ${source_file}"
echo -e "请尝试手动执行安装命令:${package_manager} install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin\n"
exit 1
fi
if [[ "$(systemctl is-active docker 2>/dev/null)" != "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$WARN 检测到 Docker 服务启动${RED}异常${PLAIN},可尝试再次执行本脚本重试"
local start_cmd
if command_exists systemctl; then
start_cmd="systemctl start docker"
else
start_cmd="service docker start"
fi
echo -e "\n$TIP 请执行 ${BLUE}${start_cmd}${PLAIN} 命令尝试启动或自行查询错误原因"
fi
fi
else
echo -e "\n$FAIL 安装失败"
fi
}
## 选择系统包管理器
function get_package_manager() {
local command="yum"
case "${SYSTEM_JUDGMENT}" in
"${SYSTEM_RHEL}" | "${SYSTEM_CENTOS_STREAM}" | "${SYSTEM_ROCKY}" | "${SYSTEM_ALMALINUX}" | "${SYSTEM_ORACLE}")
case "${SYSTEM_VERSION_ID_MAJOR}" in
9 | 10)
command="dnf"
;;
esac
;;
"${SYSTEM_FEDORA}" | "${SYSTEM_OPENEULER}" | "${SYSTEM_OPENCLOUDOS}" | "${SYSTEM_ANOLISOS}" | "${SYSTEM_TENCENTOS}")
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))
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")
if [ "$selected" -gt 0 ]; then
selected=$((selected - 1))
if [ "$selected" -lt "$start" ]; then
start=$((start - 1))
fi
fi
;;
"[B" | "s" | "S")
if [ "$selected" -lt $((${#options[@]} - 1)) ]; then
selected=$((selected + 1))
if [ "$selected" -ge $((start + page_size)) ]; then
start=$((start + 1))
fi
fi
;;
"")
tput rmcup
break
;;
*) ;;
esac
draw_menu
done
tput cnorm 2>/dev/null
tput rmcup 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")
if [ "$selected" -gt 0 ]; then
selected=$((selected - 1))
clear_menu
draw_menu
fi
;;
"[C" | "d" | "D")
if [ "$selected" -lt 1 ]; then
selected=$((selected + 1))
clear_menu
draw_menu
fi
;;
"")
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}
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))
if [[ "${line}" =~ ^[[:ascii:]]*$ && ${#line} -le $display_width ]]; then
echo "${line}"
return
fi
local non_ascii_count=$(echo "${line// /}" | sed "s|[0-9a-zA-Z\.\=\:\_\(\)\'\"-\/\!·]||g;" | wc -m)
local total_length=${#line}
local display_length=$((total_length + non_ascii_count))
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 "")))
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
if ! [[ "$char" =~ [0-9a-zA-Z\.\=\:\_\(\)\'\"-\/\!·] ]]; then
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