脚本
https://github.xyh.moe:8888/xian.yuheng/docker-push
#! /bin/bash
# 禁止 ^C 显示
stty -echoctl
#stty echoctl
################################################################################
# 检测 docker 是否存在
command -v docker 1>/dev/null 2>/dev/null || { echo -e "\e[31m[错误] 未检测到 Docker。\e[0m"; exit 1; }
# 检测是否有 docker 运行权限
docker version 1>/dev/null 2>/dev/null || { echo -e "\e[31m[错误] 无 Docker 运行权限。\e[0m"; exit 1; }
################################################################################
# 帮助文档
help_str=$(cat << EOF
参数说明:
-h, --help 帮助文档
-f, --file 指定镜像列表文件
-r, --registry 自定义推送仓库地址
-a, --arch 指定架构 "linux/amd64" "linux/arm64"
-t, --force-tag 强制重拉推tag
--delete 删除已推送本地镜像
CTRL + C 跳过当前镜像
CTRL + \ 退出脚本程序
EOF
)
#################################################################################
# 函数
function generate_horizontal_line()
{ # 创建一条和终端等宽的 分割线 [horizontal line]
printf "%0.s${mark:-=}" $(seq $(tput cols)) && printf "\n"
}
skip_signal=false
function handle_skip()
{ # 跳过信号
skip_signal=true
}
trap handle_skip SIGINT # Ctrl+C -> 跳过
quit_signal=false
function handle_quit()
{ # 退出信号
echo -e "❌ 收到退出信号,正在终止脚本..."
quit_signal=true
}
trap handle_quit SIGQUIT # Ctrl+\ -> 退出
function check_signals()
{ # 检查跳过和终止信号
local image="$1"
if [[ "$quit_signal" == true ]]; then
exit 0
fi
if [[ "$skip_signal" == true ]]; then
echo -e "\e[33m[跳过镜像] $image\e[0m"
skip_signal=false
return 1 # 返回 1 表示跳过当前镜像
fi
return 0 # 返回 0 表示继续处理
}
function check_remote_image()
{ # 判断 远程镜像 是否存在
# ${1} 一个 镜像名 形如 registry/repo/image:tag
# 存在 返回 0
# 不存在 返回 1
docker manifest inspect "$1" 1>/dev/null 2>/dev/null
}
function check_force_tag()
{ # 判断是否为强制拉推tag
local tag="$1"
shift
local force_tags=("$@")
for t in "${force_tags[@]}"; do
[[ "$tag" == "$t" ]] && return 0
done
return 1
}
function read_image_file()
{ # 处理 输入文件
# 排除 # 开头的行
# 排除 > 开头的行
# 排除 空白字符行
local input_file="$1"
local image_list=()
while IFS= read -r line; do
[[ "$line" =~ ^\s*$ ]] && continue
[[ "$line" =~ ^[#\>] ]] && continue
image_list+=("$line")
done < "$input_file"
echo "${image_list[@]}"
}
function format_image_name()
{ # 格式化镜像名
local image="$1"
local fallback_registry="$2"
local tag name
# 判断是否带tag,是否带:,且冒号后是否不带 /。
if [[ "$image" == *:* && "${image##*:}" != */* ]]; then
tag="${image##*:}"
name="${image%:*}"
else
tag="latest"
name="$image"
fi
IFS='/' read -ra parts <<< "$name"
local registry repo_parts image_name repo
if [[ "${#parts[@]}" -ge 2 && ( "${parts[0]}" == *.* || "${parts[0]}" == *:* || "${parts[0]}" == "localhost" ) ]]; then
registry="$fallback_registry"
image_name="${parts[${#parts[@]}-1]}"
repo_parts=("${parts[@]:1:${#parts[@]}-2}")
else
registry="$fallback_registry"
image_name="${parts[${#parts[@]}-1]}"
repo_parts=("${parts[@]:0:${#parts[@]}-1}")
[[ "${#repo_parts[@]}" -eq 0 ]] && repo_parts=("library")
fi
repo="$(IFS=/; echo "${repo_parts[*]}")"
if [[ -n "$repo" ]]; then
echo "${registry}/${repo}/${image_name}:${tag}"
else
echo "${registry}/library/${image_name}:${tag}"
fi
}
################################################################################
# 默认参数
# 强制tag
force_tags=("latest" "stable" "LTS" "lts" "master")
# 指定架构
architecture=""
################################################################################
# 参数判断
ARGS=` \
getopt \
-o hf:r:t:a: \
-l help,file:,registry:,arch,force-tag:,delete \
-n "$0" \
-- "$@" \
`
[ $? != 0 ] && echo -e "$help_str" && exit 1;
eval set -- "${ARGS}"
while true; do
case "$1" in
-h | --help) echo -e "$help_str"; exit 0 ;;
-f | --file) file="$2"; shift 2 ;;
-r | --registry) custom_registry="$2"; shift 2 ;;
-a | --arch) architecture="$2"; shift 2 ;;
--delete) remove_local=true; shift ;;
-t | --force-tag) # 支持逗号分隔多个tag,或者多次使用此参数都加进去
IFS=',' read -ra extra_tags <<< "$2"
for t in "${extra_tags[@]}"; do
force_tags+=("$t")
done
shift 2
;;
--) shift; break;;
*) echo "未知错误"; exit 1 ;;
esac
done
# 推送源
push_registry="${custom_registry:-docker.io}"
################################################################################
if [[ -n "$file" ]]; then
images=($(read_image_file "$file"))
else
images=($(docker images --format '{{.Repository}}:{{.Tag}}'))
fi
generate_horizontal_line
printf "%-24s: %s\n" 推送仓库地址 "$push_registry"
printf "%-24s: %s\n" 镜像列表文件 "${file:-[使用本地镜像]}"
generate_horizontal_line
# 输出 镜像 列表
index=1
for image in "${images[@]}"; do
printf "%5d. %s\n" "$index" "$image"
((index++))
done
generate_horizontal_line
################################################################################
# 推送镜像
for image in "${images[@]}";do
check_signals "$image" || continue
formated_image_name=$(format_image_name "$image" "$push_registry")
# 获取 tag
tag="${image##*:}"
force_tag=false
if check_force_tag "$tag" "${force_tags[@]}" || ! check_remote_image "$formated_image_name"; then
# 拉取镜像
if [[ -n "$architecture" ]]; then
docker pull --platform "$architecture" "$image" 1>/dev/null 2>/dev/null
else
docker pull "$image" 1>/dev/null 2>/dev/null
fi
if [[ $? == 0 ]]; then
echo -e "\e[32m[拉取成功] $image\e[0m"
check_signals "$image" || continue
docker tag "$image" "$formated_image_name"
docker push "$formated_image_name" 1>/dev/null
if [[ $? == 0 ]]; then
cleaning_list+=("$image")
echo -e "\e[32m[推送成功] $formated_image_name\e[0m"
else
echo -e "\e[31m[推送失败] $formated_image_name\e[0m"
fi
docker rmi "$formated_image_name" 1>/dev/null 2>/dev/null
else
check_signals "$image" || continue
echo -e "\e[31m[拉取失败] $image\e[0m"
fi
else
echo -e "\e[34m[镜像存在] $formated_image_name\e[0m"
cleaning_list+=("$image")
fi
done
################################################################################
# 清理镜像
if [[ "$remove_local" == true ]]; then
#generate_horizontal_line
#echo "开始删除本地镜像..."
for image in "${cleaning_list[@]}"; do
docker rmi "$image" 1>/dev/null 2>/dev/null
[ $? -eq 0 ] && echo -e "\e[32m[清理镜像] $image\e[0m"
done
#echo "本地镜像删除完成"
#generate_horizontal_line
fi
################################################################################
exit 0
镜像文件格式
# Nginx
docker.io/library/nginx:1.25-alpine
docker.io/library/nginx:1.25.0-alpine
docker.io/library/nginx:1.25.1-alpine
docker.io/library/nginx:1.25.2-alpine