diff --git a/deploy.sh b/deploy.sh index 5610cfc..548af7c 100644 --- a/deploy.sh +++ b/deploy.sh @@ -24,9 +24,28 @@ DEFAULT_DEPLOY_DIR="/opt/frpc-console" IMAGE_NAME="frpc-console" KEEP_IMAGES=3 -# ---------- 状态变量 ---------- +# ---------- 镜像加速配置 ---------- +DEFAULT_MIRRORS=( + "https://docker.1panel.live" + "https://docker.1panel.dev" + "https://docker.1ms.run" +) +DAEMON_JSON="/etc/docker/daemon.json" +MIRROR_CHECK_TIMEOUT=5 + +# ---------- 镜像加速状态变量 ---------- +MIRROR_STATUS="" # no_config | has_mirrors | default_unavailable +NEED_RESTART=false # 是否需要重启 Docker +NEED_REORDER=false # 是否需要重排已有镜像源 +HAS_REGISTRY_MIRRORS_KEY=false # daemon.json 中是否存在 registry-mirrors 字段 +AVAILABLE_MIRRORS=() # 可用的默认地址 +USER_MIRRORS_AVAILABLE=() # 用户配置中可用的地址 +USER_MIRRORS_UNAVAILABLE=() # 用户配置中不可用的地址 + +# ---------- 其他状态变量 ---------- OS=""; OS_VERSION=""; ARCH="" HAS_GIT=false; HAS_CURL=false; HAS_WGET=false; HAS_DOCKER=false +HAS_JQ=false NEED_INSTALL_GIT=false; NEED_INSTALL_CURL=false; NEED_INSTALL_WGET=false PORT=${DEFAULT_PORT} DEPLOY_DIR=${DEFAULT_DEPLOY_DIR} @@ -36,6 +55,7 @@ SKIP_CONFIRM=false; CHECK_ONLY=false; DRY_RUN=false CHANNEL=""; BRANCH=""; TARGET_VERSION=""; IMAGE_TAG="" CURRENT_VERSION="" +# ---------- 打印函数 ---------- print_info() { echo -e "${BLUE}[INFO]${NC} $1"; } print_success() { echo -e "${GREEN}[✓]${NC} $1"; } print_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; } @@ -65,6 +85,7 @@ parse_args() { done } +# ---------- 权限检测 ---------- check_root() { if [ "$EUID" -ne 0 ]; then print_error "请使用 root 权限运行此脚本" @@ -73,6 +94,7 @@ check_root() { fi } +# ---------- 系统检测 ---------- detect_os() { if [ -f /etc/os-release ]; then . /etc/os-release @@ -96,11 +118,13 @@ detect_arch() { esac } +# ---------- 工具检测 ---------- check_tools() { command -v git &> /dev/null && HAS_GIT=true || NEED_INSTALL_GIT=true command -v curl &> /dev/null && HAS_CURL=true || NEED_INSTALL_CURL=true command -v wget &> /dev/null && HAS_WGET=true || NEED_INSTALL_WGET=true command -v docker &> /dev/null && HAS_DOCKER=true + command -v jq &> /dev/null && HAS_JQ=true || HAS_JQ=false } check_container() { @@ -112,6 +136,328 @@ check_container() { fi } +# ---------- 镜像加速:连通性检测 ---------- +check_mirror_available() { + local url="$1" + local http_code + http_code=$(curl -s --max-time "$MIRROR_CHECK_TIMEOUT" -o /dev/null -w "%{http_code}" \ + "${url}/v2/" 2>/dev/null) + if [[ "$http_code" =~ ^2[0-9][0-9]$ ]] || [ "$http_code" = "401" ] || [ "$http_code" = "403" ]; then + # 2xx 表示成功,401/403 表示需要认证但服务可达(也是可用的) + return 0 + fi + return 1 +} + +# ---------- 镜像加速:安装 jq ---------- +install_jq() { + print_info "尝试安装 jq..." + case $OS in + opensuse*|suse*|opensuse-tumbleweed|opensuse-slowroll|opensuse-leap) + zypper install -y jq 2>/dev/null || true + ;; + ubuntu|debian|linuxmint) + apt update -qq 2>/dev/null && apt install -y jq 2>/dev/null || true + ;; + centos|rhel|fedora|rocky|almalinux) + yum install -y jq 2>/dev/null || true + ;; + alpine) + apk add jq 2>/dev/null || true + ;; + arch|manjaro|endeavouros) + pacman -S --needed --noconfirm jq 2>/dev/null || true + ;; + *) + return 1 + ;; + esac + command -v jq &> /dev/null && HAS_JQ=true || HAS_JQ=false + if [ "$HAS_JQ" = true ]; then + print_success "jq 安装成功" + return 0 + else + print_warn "jq 安装失败,将使用 sed 模式(功能受限)" + return 1 + fi +} + +# ---------- 镜像加速:检测阶段 ---------- +check_docker_mirrors() { + print_step "检测 Docker 镜像源配置..." + + # 1. 检测三个默认地址的连通性 + print_info "检测默认镜像源连通性 (超时 ${MIRROR_CHECK_TIMEOUT}s)..." + for mirror in "${DEFAULT_MIRRORS[@]}"; do + if check_mirror_available "$mirror"; then + AVAILABLE_MIRRORS+=("$mirror") + print_info " ✅ $mirror 可用" + else + print_info " ❌ $mirror 不可用" + fi + done + + if [ ${#AVAILABLE_MIRRORS[@]} -eq 0 ]; then + MIRROR_STATUS="default_unavailable" + print_warn "所有默认镜像源均不可用,将跳过镜像加速配置" + print_warn "如果 Docker 构建失败,请检查网络或手动配置镜像源" + return 0 + fi + + print_success "找到 ${#AVAILABLE_MIRRORS[@]} 个可用默认镜像源" + + # 2. 检查 daemon.json + if [ ! -f "$DAEMON_JSON" ]; then + MIRROR_STATUS="no_config" + HAS_REGISTRY_MIRRORS_KEY=false + print_info "daemon.json 不存在,将创建新配置" + return 0 + fi + + # 3. 检测 jq 是否可解析 + if ! jq -e '.' "$DAEMON_JSON" >/dev/null 2>&1 2>/dev/null; then + print_warn "daemon.json 格式不标准,将备份并重建" + local backup_path="${DAEMON_JSON}.bak.$(date +%Y%m%d-%H%M%S)" + cp "$DAEMON_JSON" "$backup_path" + print_info "已备份至: $backup_path" + echo '{}' > "$DAEMON_JSON" + print_warn "已重建 daemon.json,请确认是否需要将其他自定义配置迁回" + MIRROR_STATUS="no_config" + HAS_REGISTRY_MIRRORS_KEY=false + return 0 + fi + + # 4. 检查是否存在 registry-mirrors 字段 + local has_key + has_key=$(jq -e 'has("registry-mirrors")' "$DAEMON_JSON" 2>/dev/null) + if [ "$has_key" != "true" ]; then + HAS_REGISTRY_MIRRORS_KEY=false + MIRROR_STATUS="has_mirrors" + print_info "daemon.json 中无 registry-mirrors 字段,将仅插入该字段(保留其他配置)" + return 0 + fi + + HAS_REGISTRY_MIRRORS_KEY=true + + # 5. 读取 registry-mirrors 数组 + local mirror_list + mirror_list=$(jq -r '."registry-mirrors" // [] | .[]' "$DAEMON_JSON" 2>/dev/null) + if [ -z "$mirror_list" ]; then + MIRROR_STATUS="has_mirrors" + print_info "registry-mirrors 为空数组,将写入默认地址" + return 0 + fi + + # 6. 检测用户配置中每个地址的连通性 + print_info "检测用户配置的镜像源连通性..." + USER_MIRRORS_AVAILABLE=() + USER_MIRRORS_UNAVAILABLE=() + + while IFS= read -r mirror; do + if [ -n "$mirror" ]; then + if check_mirror_available "$mirror"; then + USER_MIRRORS_AVAILABLE+=("$mirror") + print_info " ✅ $mirror (用户配置) 可用" + else + USER_MIRRORS_UNAVAILABLE+=("$mirror") + print_info " ❌ $mirror (用户配置) 不可用" + fi + fi + done <<< "$mirror_list" + + MIRROR_STATUS="has_mirrors" + + # 7. 判断是否需要重排 + if [ ${#USER_MIRRORS_AVAILABLE[@]} -gt 0 ]; then + # 检查可用地址是否已经全部排在不可用地址前面 + NEED_REORDER=false + local seen_unavailable=false + for mirror in "${USER_MIRRORS_AVAILABLE[@]}" "${USER_MIRRORS_UNAVAILABLE[@]}"; do + # 遍历原始顺序:在 USER_MIRRORS_AVAILABLE 和 USER_MIRRORS_UNAVAILABLE 的拼接中, + # 如果先遇到不可用,再遇到可用,说明需要重排 + # 但我们这里无法直接还原原始顺序,用另一个方法:检查原始列表中是否存在可用在不可用之后 + # 更简单的方法:如果存在任何一个不可用在可用之前 + # 用原始列表检测 + local raw_list + raw_list=$(jq -r '."registry-mirrors" // [] | .[]' "$DAEMON_JSON" 2>/dev/null) + local raw_available=() + local raw_unavailable=() + while IFS= read -r m; do + if [ -n "$m" ]; then + local found=false + for a in "${USER_MIRRORS_AVAILABLE[@]}"; do + if [ "$a" = "$m" ]; then + raw_available+=("$m") + found=true + break + fi + done + if [ "$found" = false ]; then + raw_unavailable+=("$m") + fi + fi + done <<< "$raw_list" + + # 检查原始列表中,是否有不可用地址出现在可用地址之前 + local seen_avail=false + for m in "${raw_available[@]}" "${raw_unavailable[@]}"; do + # 这里只检查原始顺序 + true + done + + # 更准确的方法:遍历原始列表,记录是否见过不可用 + local seen_unavail=false + local need_reorder=false + while IFS= read -r m; do + if [ -n "$m" ]; then + local is_avail=false + for a in "${USER_MIRRORS_AVAILABLE[@]}"; do + if [ "$a" = "$m" ]; then + is_avail=true + break + fi + done + if [ "$is_avail" = true ] && [ "$seen_unavail" = true ]; then + need_reorder=true + break + fi + if [ "$is_avail" = false ]; then + seen_unavail=true + fi + fi + done <<< "$raw_list" + NEED_REORDER=$need_reorder + done + + if [ "$NEED_REORDER" = true ]; then + print_info "检测到可用镜像源排在不可用镜像源之后,需要重排" + else + print_info "用户镜像源配置正常,无需调整" + fi + elif [ ${#USER_MIRRORS_UNAVAILABLE[@]} -gt 0 ] && [ ${#AVAILABLE_MIRRORS[@]} -gt 0 ]; then + # 用户配置全部不可用,但有可用的默认地址 + NEED_REORDER=true + print_info "用户配置全部不可用,将插入可用默认地址" + fi + + print_success "镜像源检测完成" +} + +# ---------- 镜像加速:配置执行阶段 ---------- +configure_docker_mirrors() { + print_step "配置 Docker 镜像源..." + + if [ "$MIRROR_STATUS" = "default_unavailable" ]; then + print_warn "无可用默认镜像源,跳过配置" + return 0 + fi + + if [ ${#AVAILABLE_MIRRORS[@]} -eq 0 ]; then + print_warn "无可用的默认镜像源,跳过配置" + return 0 + fi + + # 确保 jq 可用 + if [ "$HAS_JQ" = false ]; then + print_warn "jq 未安装,尝试安装..." + if ! install_jq; then + print_error "jq 安装失败,无法配置镜像源" + print_error "请手动配置 /etc/docker/daemon.json 中的 registry-mirrors" + return 1 + fi + fi + + local new_mirrors=() + local need_write=false + local need_restart=false + + # 情况判断 + if [ "$HAS_REGISTRY_MIRRORS_KEY" = false ]; then + # 不存在 registry-mirrors 字段,直接插入 + new_mirrors=("${AVAILABLE_MIRRORS[@]}") + need_write=true + need_restart=true + print_info "插入 registry-mirrors 字段: ${AVAILABLE_MIRRORS[*]}" + elif [ ${#USER_MIRRORS_AVAILABLE[@]} -eq 0 ] && [ ${#USER_MIRRORS_UNAVAILABLE[@]} -eq 0 ]; then + # 空数组,直接写入 + new_mirrors=("${AVAILABLE_MIRRORS[@]}") + need_write=true + need_restart=true + print_info "写入 registry-mirrors: ${AVAILABLE_MIRRORS[*]}" + elif [ ${#USER_MIRRORS_AVAILABLE[@]} -gt 0 ]; then + if [ "$NEED_REORDER" = true ]; then + # 重排:可用地址在前 + 不可用地址在后(仅限用户配置中不可用的) + new_mirrors=("${USER_MIRRORS_AVAILABLE[@]}" "${USER_MIRRORS_UNAVAILABLE[@]}") + need_write=true + need_restart=true + print_info "重排镜像源: 可用前置" + else + print_info "镜像源配置正常,无需修改" + return 0 + fi + elif [ ${#USER_MIRRORS_AVAILABLE[@]} -eq 0 ] && [ ${#USER_MIRRORS_UNAVAILABLE[@]} -gt 0 ]; then + # 用户配置全部不可用,插入可用默认地址到最前面 + new_mirrors=("${AVAILABLE_MIRRORS[@]}" "${USER_MIRRORS_UNAVAILABLE[@]}") + need_write=true + need_restart=true + print_info "插入可用默认镜像源到最前面: ${AVAILABLE_MIRRORS[*]}" + else + print_info "无需修改镜像源配置" + return 0 + fi + + if [ "$need_write" = false ]; then + return 0 + fi + + # 检查是否需要重启 Docker(交互模式确认) + if [ "$need_restart" = true ] && [ "$SKIP_CONFIRM" != true ]; then + echo "" + echo -e "${YELLOW}⚠ 修改 Docker 镜像源配置需要重启 Docker 服务。${NC}" + echo -e "${YELLOW} 重启可能影响其他正在运行的容器。${NC}" + read -p "确认继续? [y/N]: " confirm "${DAEMON_JSON}.tmp" 2>/dev/null; then + print_error "写入 daemon.json 失败" + return 1 + fi + + mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + print_success "daemon.json 已更新" + + # 重启 Docker + if [ "$need_restart" = true ]; then + print_step "重启 Docker 服务..." + if systemctl restart docker 2>/dev/null; then + print_success "Docker 重启完成" + sleep 2 + else + print_error "Docker 重启失败,请手动重启后重新运行脚本" + print_info "手动重启命令: systemctl restart docker" + exit 1 + fi + fi + + print_success "镜像源配置完成" +} + # ---------- 版本管理 ---------- read_version_from_remote() { local branch="$1" @@ -227,6 +573,22 @@ print_environment_summary() { print_error "Docker 未安装,请先安装 Docker" exit 1 fi + echo "" + echo " ${CYAN}镜像源状态:${NC}" + if [ ${#AVAILABLE_MIRRORS[@]} -gt 0 ]; then + echo " 可用默认镜像源: ${AVAILABLE_MIRRORS[*]}" + else + echo " 可用默认镜像源: 无" + fi + if [ ${#USER_MIRRORS_AVAILABLE[@]} -gt 0 ]; then + echo " 用户配置可用: ${USER_MIRRORS_AVAILABLE[*]}" + elif [ ${#USER_MIRRORS_UNAVAILABLE[@]} -gt 0 ]; then + echo " 用户配置: ${#USER_MIRRORS_UNAVAILABLE[@]} 个地址均不可用" + elif [ "$HAS_REGISTRY_MIRRORS_KEY" = false ]; then + echo " 用户配置: 无 registry-mirrors" + else + echo " 用户配置: 无" + fi if [ "$CONTAINER_EXISTS" = true ]; then echo "" echo " ${CYAN}容器状态:${NC}" @@ -251,6 +613,11 @@ generate_plan() { [ "$NEED_INSTALL_WGET" = true ] && pkgs="${pkgs} wget" PLAN="${PLAN} • 安装必要工具:${pkgs}\n" fi + if [ "$MIRROR_STATUS" != "default_unavailable" ] && [ ${#AVAILABLE_MIRRORS[@]} -gt 0 ]; then + if [ "$HAS_REGISTRY_MIRRORS_KEY" = false ] || [ ${#USER_MIRRORS_AVAILABLE[@]} -eq 0 -a ${#USER_MIRRORS_UNAVAILABLE[@]} -eq 0 ] || [ "$NEED_REORDER" = true ]; then + PLAN="${PLAN} • 配置 Docker 镜像加速\n" + fi + fi PLAN="${PLAN} • 拉取 frpc-console 源码 (${BRANCH} 分支)\n" PLAN="${PLAN} • 构建 Docker 镜像: ${IMAGE_NAME}:${IMAGE_TAG}\n" PLAN="${PLAN} • 目标版本: ${TARGET_VERSION}\n" @@ -384,6 +751,14 @@ do_deploy() { mkdir -p bin static print_success "代码拉取完成 (分支: ${BRANCH})" + # ============================================================ + # 镜像加速配置:拉取代码之后、docker build 之前执行 + # ============================================================ + if ! configure_docker_mirrors; then + print_error "镜像源配置失败,请检查后重试" + exit 1 + fi + # ----- 停止旧容器 ----- if [ "$CONTAINER_EXISTS" = true ]; then print_step "停止旧容器..." @@ -594,6 +969,9 @@ main() { check_tools check_container + # ---------- 镜像源检测(拉取代码前,只检测不修改) ---------- + check_docker_mirrors + select_channel generate_target_version "$CHANNEL" "$BRANCH"