1083 lines
39 KiB
Bash
1083 lines
39 KiB
Bash
#!/bin/bash
|
||
|
||
# ============================================================
|
||
# frpc-console 一键部署脚本 (Docker 优先)
|
||
# 用法:
|
||
# run-deploy.sh 引导后自动执行
|
||
# ============================================================
|
||
|
||
set -e
|
||
|
||
# ---------- 颜色检测 ----------
|
||
if [ -t 1 ]; then
|
||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'; CYAN='\033[0;36m'; MAGENTA='\033[0;35m'; NC='\033[0m'
|
||
else
|
||
RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; MAGENTA=''; NC=''
|
||
fi
|
||
|
||
# ---------- 配置 ----------
|
||
REPO_URL="https://git.whitetop.xyz/lxh2875931338/frpc-console.git"
|
||
WORK_DIR="/tmp/frpc-console-build"
|
||
DEFAULT_PORT=9300
|
||
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_file | no_key | empty_array | has_valid_ordered | has_valid_reorder_needed | all_invalid
|
||
DEFAULT_STATUS="" # default_unavailable | default_partial | default_all_available
|
||
WRITE_DAEMON=false
|
||
RESTART_DOCKER=false
|
||
REORDER=false
|
||
INSERT_DEFAULT=false
|
||
HAS_REGISTRY_MIRRORS_KEY=false
|
||
AVAILABLE_MIRRORS=() # 可用的默认地址
|
||
USER_MIRRORS_AVAILABLE=() # 用户配置中可用的地址
|
||
USER_MIRRORS_UNAVAILABLE=() # 用户配置中不可用的地址
|
||
USER_MIRRORS_ALL=() # 用户配置所有地址(保持原始顺序)
|
||
MIRROR_DETECT_OUTPUT="" # 检测阶段输出给用户的描述
|
||
|
||
# ---------- 其他状态变量 ----------
|
||
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}
|
||
DATA_DIR="${DEPLOY_DIR}/data"
|
||
CONTAINER_EXISTS=false; CONTAINER_RUNNING=false
|
||
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"; }
|
||
print_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||
print_step() { echo -e "\n${CYAN}▶${NC} $1"; }
|
||
print_title() { echo -e "\n${MAGENTA}════════════════════════════════════════════════════════${NC}"; }
|
||
print_subtitle() { echo -e "${MAGENTA} $1${NC}"; }
|
||
|
||
# ---------- 解析命令行参数 ----------
|
||
parse_args() {
|
||
for arg in "$@"; do
|
||
case $arg in
|
||
--yes|-y) SKIP_CONFIRM=true ;;
|
||
--check) CHECK_ONLY=true ;;
|
||
--dry-run) DRY_RUN=true ;;
|
||
--help|-h)
|
||
echo "用法: ./deploy.sh [选项]"
|
||
echo "选项:"
|
||
echo " --yes, -y 跳过所有确认提示"
|
||
echo " --check 只检测环境,不执行部署"
|
||
echo " --dry-run 显示将执行的操作,不实际执行"
|
||
echo " --help, -h 显示帮助信息"
|
||
exit 0
|
||
;;
|
||
*) print_error "未知选项: $arg"; exit 1 ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# ---------- 权限检测 ----------
|
||
check_root() {
|
||
if [ "$EUID" -ne 0 ]; then
|
||
print_error "请使用 root 权限运行此脚本"
|
||
echo " sudo bash deploy.sh"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# ---------- 系统检测 ----------
|
||
detect_os() {
|
||
if [ -f /etc/os-release ]; then
|
||
. /etc/os-release
|
||
OS=$ID; OS_VERSION=$VERSION_ID
|
||
else
|
||
print_error "无法识别操作系统"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
detect_arch() {
|
||
ARCH=$(uname -m)
|
||
case $ARCH in
|
||
x86_64|amd64|aarch64|arm64|armv7l|armhf)
|
||
print_success "CPU 架构: $ARCH"
|
||
;;
|
||
*)
|
||
print_error "不支持的 CPU 架构: $ARCH"
|
||
exit 1
|
||
;;
|
||
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() {
|
||
if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^frpc-console$"; then
|
||
CONTAINER_EXISTS=true
|
||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^frpc-console$"; then
|
||
CONTAINER_RUNNING=true
|
||
fi
|
||
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
|
||
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)..."
|
||
local default_available_count=0
|
||
for mirror in "${DEFAULT_MIRRORS[@]}"; do
|
||
if check_mirror_available "$mirror"; then
|
||
AVAILABLE_MIRRORS+=("$mirror")
|
||
default_available_count=$((default_available_count + 1))
|
||
print_info " ✅ $mirror 可用"
|
||
else
|
||
print_info " ❌ $mirror 不可用"
|
||
fi
|
||
done
|
||
|
||
if [ ${#AVAILABLE_MIRRORS[@]} -eq 0 ]; then
|
||
DEFAULT_STATUS="default_unavailable"
|
||
print_warn "所有默认镜像源均不可用,将跳过镜像加速配置"
|
||
elif [ ${#AVAILABLE_MIRRORS[@]} -eq ${#DEFAULT_MIRRORS[@]} ]; then
|
||
DEFAULT_STATUS="default_all_available"
|
||
print_success "所有默认镜像源均可用"
|
||
else
|
||
DEFAULT_STATUS="default_partial"
|
||
print_info "部分默认镜像源可用 (${#AVAILABLE_MIRRORS[@]}/${#DEFAULT_MIRRORS[@]})"
|
||
fi
|
||
|
||
# 2. 检查 daemon.json
|
||
if [ ! -f "$DAEMON_JSON" ]; then
|
||
MIRROR_STATUS="no_file"
|
||
HAS_REGISTRY_MIRRORS_KEY=false
|
||
print_info "daemon.json 不存在"
|
||
# 生成检测报告
|
||
generate_mirror_detect_output
|
||
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_file"
|
||
HAS_REGISTRY_MIRRORS_KEY=false
|
||
generate_mirror_detect_output
|
||
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="no_key"
|
||
print_info "daemon.json 中无 registry-mirrors 字段"
|
||
generate_mirror_detect_output
|
||
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="empty_array"
|
||
print_info "registry-mirrors 为空数组"
|
||
generate_mirror_detect_output
|
||
return 0
|
||
fi
|
||
|
||
# 6. 读取用户配置所有地址(保持顺序)
|
||
USER_MIRRORS_ALL=()
|
||
while IFS= read -r mirror; do
|
||
if [ -n "$mirror" ]; then
|
||
USER_MIRRORS_ALL+=("$mirror")
|
||
fi
|
||
done <<< "$mirror_list"
|
||
|
||
# 7. 检测用户配置中每个地址的连通性
|
||
print_info "检测用户配置的镜像源连通性..."
|
||
USER_MIRRORS_AVAILABLE=()
|
||
USER_MIRRORS_UNAVAILABLE=()
|
||
|
||
for mirror in "${USER_MIRRORS_ALL[@]}"; do
|
||
if check_mirror_available "$mirror"; then
|
||
USER_MIRRORS_AVAILABLE+=("$mirror")
|
||
print_info " ✅ $mirror (用户配置) 可用"
|
||
else
|
||
USER_MIRRORS_UNAVAILABLE+=("$mirror")
|
||
print_info " ❌ $mirror (用户配置) 不可用"
|
||
fi
|
||
done
|
||
|
||
# 8. 判断状态
|
||
if [ ${#USER_MIRRORS_AVAILABLE[@]} -gt 0 ]; then
|
||
# 检查是否需要重排:是否存在不可用在可用之前
|
||
local need_reorder=false
|
||
local seen_unavailable=false
|
||
for mirror in "${USER_MIRRORS_ALL[@]}"; do
|
||
local is_avail=false
|
||
for a in "${USER_MIRRORS_AVAILABLE[@]}"; do
|
||
if [ "$a" = "$mirror" ]; then
|
||
is_avail=true
|
||
break
|
||
fi
|
||
done
|
||
if [ "$is_avail" = true ] && [ "$seen_unavailable" = true ]; then
|
||
need_reorder=true
|
||
break
|
||
fi
|
||
if [ "$is_avail" = false ]; then
|
||
seen_unavailable=true
|
||
fi
|
||
done
|
||
|
||
if [ "$need_reorder" = true ]; then
|
||
MIRROR_STATUS="has_valid_reorder_needed"
|
||
print_info "可用镜像源排在不可用镜像源之后,需要重排"
|
||
else
|
||
MIRROR_STATUS="has_valid_ordered"
|
||
print_info "用户镜像源配置正常,无需调整"
|
||
fi
|
||
else
|
||
# 用户配置全部不可用
|
||
MIRROR_STATUS="all_invalid"
|
||
print_warn "用户配置全部不可用"
|
||
fi
|
||
|
||
generate_mirror_detect_output
|
||
}
|
||
|
||
# ---------- 镜像加速:生成检测输出 ----------
|
||
generate_mirror_detect_output() {
|
||
MIRROR_DETECT_OUTPUT=""
|
||
|
||
# 如果默认地址全部不可用,直接输出警告
|
||
if [ "$DEFAULT_STATUS" = "default_unavailable" ]; then
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}${YELLOW}⚠ 所有默认镜像源均不可用,将跳过镜像加速配置${NC}\n"
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}如果 Docker 构建失败,请检查网络或手动配置镜像源。\n"
|
||
return
|
||
fi
|
||
|
||
# 构建可用地址列表显示
|
||
local avail_list=""
|
||
for m in "${AVAILABLE_MIRRORS[@]}"; do
|
||
avail_list="${avail_list} ✓ ${m}\n"
|
||
done
|
||
|
||
case "$MIRROR_STATUS" in
|
||
no_file|no_key|empty_array)
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}${YELLOW}未检测到 registry-mirrors 配置。${NC}\n\n"
|
||
if [ ${#AVAILABLE_MIRRORS[@]} -gt 0 ]; then
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}将写入以下可用镜像源:\n\n${avail_list}\n"
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}写入位置:${DAEMON_JSON}\n"
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}${YELLOW}Docker 将在配置完成后自动重启以应用配置。${NC}\n"
|
||
WRITE_DAEMON=true
|
||
RESTART_DOCKER=true
|
||
REORDER=false
|
||
INSERT_DEFAULT=true
|
||
else
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}${RED}无可用的默认镜像源,将跳过配置。${NC}\n"
|
||
WRITE_DAEMON=false
|
||
RESTART_DOCKER=false
|
||
REORDER=false
|
||
INSERT_DEFAULT=false
|
||
fi
|
||
;;
|
||
|
||
has_valid_ordered)
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}${GREEN}检测完成。用户已配置可用的镜像源:${NC}\n\n"
|
||
for m in "${USER_MIRRORS_AVAILABLE[@]}"; do
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT} ✓ ${m}\n"
|
||
done
|
||
if [ ${#USER_MIRRORS_UNAVAILABLE[@]} -gt 0 ]; then
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}\n${YELLOW}不可用地址:${NC}\n"
|
||
for m in "${USER_MIRRORS_UNAVAILABLE[@]}"; do
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT} ✗ ${m}\n"
|
||
done
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}\n可用地址已排在不可用地址之前,无需修改。\n"
|
||
else
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}\n所有地址均可用,无需修改。\n"
|
||
fi
|
||
WRITE_DAEMON=false
|
||
RESTART_DOCKER=false
|
||
REORDER=false
|
||
INSERT_DEFAULT=false
|
||
;;
|
||
|
||
has_valid_reorder_needed)
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}${YELLOW}检测完成。用户配置中存在不可用或顺序不当的镜像源。${NC}\n\n"
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}当前配置顺序:\n"
|
||
local idx=1
|
||
for m in "${USER_MIRRORS_ALL[@]}"; do
|
||
local status=""
|
||
local found=false
|
||
for a in "${USER_MIRRORS_AVAILABLE[@]}"; do
|
||
if [ "$a" = "$m" ]; then
|
||
found=true
|
||
break
|
||
fi
|
||
done
|
||
if [ "$found" = true ]; then
|
||
status="${GREEN}✅ 可用${NC}"
|
||
else
|
||
status="${RED}❌ 不可用${NC}"
|
||
fi
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT} ${idx}. ${m} ${status}\n"
|
||
idx=$((idx + 1))
|
||
done
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}\n${YELLOW}将重新排序:可用地址前置,不可用地址后移。${NC}\n"
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}\n新顺序:\n"
|
||
local new_idx=1
|
||
for m in "${USER_MIRRORS_AVAILABLE[@]}"; do
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT} ${new_idx}. ${m} ${GREEN}✅${NC}\n"
|
||
new_idx=$((new_idx + 1))
|
||
done
|
||
for m in "${USER_MIRRORS_UNAVAILABLE[@]}"; do
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT} ${new_idx}. ${m} ${RED}❌${NC}\n"
|
||
new_idx=$((new_idx + 1))
|
||
done
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}\n${YELLOW}Docker 将在配置完成后自动重启以应用配置。${NC}\n"
|
||
WRITE_DAEMON=true
|
||
RESTART_DOCKER=true
|
||
REORDER=true
|
||
INSERT_DEFAULT=false
|
||
;;
|
||
|
||
all_invalid)
|
||
if [ ${#AVAILABLE_MIRRORS[@]} -gt 0 ]; then
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}${YELLOW}检测完成。用户配置的镜像源全部不可用:${NC}\n\n"
|
||
for m in "${USER_MIRRORS_UNAVAILABLE[@]}"; do
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT} ✗ ${m}\n"
|
||
done
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}\n${GREEN}检测到可用镜像源:${NC}\n\n"
|
||
for m in "${AVAILABLE_MIRRORS[@]}"; do
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT} ✓ ${m}\n"
|
||
done
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}\n${YELLOW}将把可用地址插入到用户配置最前面。${NC}\n"
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}${YELLOW}Docker 将在配置完成后自动重启以应用配置。${NC}\n"
|
||
WRITE_DAEMON=true
|
||
RESTART_DOCKER=true
|
||
REORDER=false
|
||
INSERT_DEFAULT=true
|
||
else
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}${RED}检测完成。用户配置的镜像源全部不可用:${NC}\n\n"
|
||
for m in "${USER_MIRRORS_UNAVAILABLE[@]}"; do
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT} ✗ ${m}\n"
|
||
done
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}\n${RED}默认镜像源也全部不可用。将跳过镜像加速配置。${NC}\n"
|
||
MIRROR_DETECT_OUTPUT="${MIRROR_DETECT_OUTPUT}${YELLOW}如果 Docker 构建失败,请检查网络连接,或手动配置 /etc/docker/daemon.json 中的 registry-mirrors。${NC}\n"
|
||
WRITE_DAEMON=false
|
||
RESTART_DOCKER=false
|
||
REORDER=false
|
||
INSERT_DEFAULT=false
|
||
fi
|
||
;;
|
||
esac
|
||
|
||
# 打印检测输出
|
||
echo ""
|
||
print_title
|
||
print_subtitle "Docker 镜像源检测结果"
|
||
echo ""
|
||
echo -e "$MIRROR_DETECT_OUTPUT"
|
||
print_title
|
||
echo ""
|
||
}
|
||
|
||
# ---------- 镜像加速:配置执行阶段 ----------
|
||
configure_docker_mirrors() {
|
||
if [ "$WRITE_DAEMON" != true ]; then
|
||
print_info "无需修改 Docker 镜像源配置"
|
||
return 0
|
||
fi
|
||
|
||
print_step "配置 Docker 镜像源..."
|
||
|
||
# 确保 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=()
|
||
if [ "$REORDER" = true ]; then
|
||
# 重排:可用前置,不可用后移(仅重排用户配置)
|
||
new_mirrors=("${USER_MIRRORS_AVAILABLE[@]}" "${USER_MIRRORS_UNAVAILABLE[@]}")
|
||
print_info "重排镜像源顺序"
|
||
elif [ "$INSERT_DEFAULT" = true ]; then
|
||
if [ "$MIRROR_STATUS" = "no_file" ] || [ "$MIRROR_STATUS" = "no_key" ] || [ "$MIRROR_STATUS" = "empty_array" ]; then
|
||
# 无配置或空配置:直接写入可用默认地址
|
||
new_mirrors=("${AVAILABLE_MIRRORS[@]}")
|
||
print_info "写入可用默认镜像源"
|
||
else
|
||
# all_invalid:可用默认地址插入到最前面
|
||
new_mirrors=("${AVAILABLE_MIRRORS[@]}" "${USER_MIRRORS_UNAVAILABLE[@]}")
|
||
print_info "插入可用默认镜像源到最前面"
|
||
fi
|
||
else
|
||
print_info "无需修改镜像源配置"
|
||
return 0
|
||
fi
|
||
|
||
# 备份 daemon.json
|
||
if [ -f "$DAEMON_JSON" ]; then
|
||
local backup_path="${DAEMON_JSON}.bak.$(date +%Y%m%d-%H%M%S)"
|
||
cp "$DAEMON_JSON" "$backup_path"
|
||
print_info "已备份: $backup_path"
|
||
fi
|
||
|
||
# 写入新配置(仅修改 registry-mirrors 字段,保留其他所有字段)
|
||
local json_mirrors
|
||
json_mirrors=$(printf '%s\n' "${new_mirrors[@]}" | jq -R . | jq -s .)
|
||
|
||
if ! jq --argjson mirrors "$json_mirrors" '."registry-mirrors" = $mirrors' \
|
||
"$DAEMON_JSON" > "${DAEMON_JSON}.tmp" 2>/dev/null; then
|
||
print_error "写入 daemon.json 失败"
|
||
return 1
|
||
fi
|
||
|
||
mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON"
|
||
print_success "daemon.json 已更新"
|
||
print_info "新镜像源列表: ${new_mirrors[*]}"
|
||
|
||
# 重启 Docker
|
||
if [ "$RESTART_DOCKER" = 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 "镜像源配置完成"
|
||
return 0
|
||
}
|
||
|
||
# ---------- 版本管理 ----------
|
||
read_version_from_remote() {
|
||
local branch="$1"
|
||
local version_url="https://git.whitetop.xyz/lxh2875931338/frpc-console/raw/${branch}/version.ini"
|
||
local content
|
||
content=$(curl -sSL "$version_url" 2>/dev/null)
|
||
if [ -z "$content" ]; then
|
||
print_error "无法从远程读取 version.ini: $version_url"
|
||
return 1
|
||
fi
|
||
SEMVER=$(echo "$content" | grep -v "^[;#]" | head -1 | awk '{print $1}')
|
||
echo "$SEMVER"
|
||
}
|
||
|
||
generate_target_version() {
|
||
local channel="$1"
|
||
local branch="${2:-$BRANCH}"
|
||
SEMVER=$(read_version_from_remote "$branch")
|
||
if [ -z "$SEMVER" ]; then
|
||
print_error "读取语义版本失败"
|
||
exit 1
|
||
fi
|
||
local minor="${SEMVER#*.}"
|
||
if [ "$minor" = "0" ] || [ "$minor" = "5" ]; then
|
||
CHANNEL_TYPE="lts"
|
||
else
|
||
CHANNEL_TYPE="preview"
|
||
fi
|
||
if [ "$channel" = "lts" ] || [ "$CHANNEL_TYPE" = "lts" ]; then
|
||
TARGET_VERSION="${SEMVER}-lts-$(date +%Y%m%d)"
|
||
IMAGE_TAG="${SEMVER}-lts-$(date +%Y%m%d)"
|
||
else
|
||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||
TARGET_VERSION="${SEMVER}-preview-${TIMESTAMP}"
|
||
IMAGE_TAG="preview-${TIMESTAMP}"
|
||
fi
|
||
}
|
||
|
||
get_current_version() {
|
||
local version_file="${DEPLOY_DIR}/data/version.ini"
|
||
if [ -f "$version_file" ]; then
|
||
grep -v "^[;#]" "$version_file" | head -1 | tr -d '\n'
|
||
else
|
||
if [ "$CONTAINER_EXISTS" = true ]; then
|
||
docker exec frpc-console cat /app/data/version.ini 2>/dev/null | grep -v "^[;#]" | head -1 | tr -d '\n' || echo ""
|
||
else
|
||
echo ""
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# ---------- 通道选择 ----------
|
||
select_channel() {
|
||
if [ -n "$DEPLOY_CHANNEL" ] && [ -n "$DEPLOY_BRANCH" ]; then
|
||
CHANNEL="$DEPLOY_CHANNEL"
|
||
BRANCH="$DEPLOY_BRANCH"
|
||
print_info "使用环境变量: ${CHANNEL} 通道 (${BRANCH} 分支)"
|
||
return
|
||
fi
|
||
if [ -n "$CHANNEL" ]; then
|
||
if [ "$CHANNEL" != "lts" ] && [ "$CHANNEL" != "preview" ]; then
|
||
print_error "无效通道: $CHANNEL"
|
||
exit 1
|
||
fi
|
||
BRANCH="$([ "$CHANNEL" = "preview" ] && echo "test" || echo "main")"
|
||
return
|
||
fi
|
||
echo ""
|
||
echo -e "${CYAN}请选择部署通道:${NC}"
|
||
echo " 1. LTS (稳定版) [默认] → main 分支"
|
||
echo " 2. Preview (技术预览版) → test 分支"
|
||
echo ""
|
||
read -p "请选择 [1]: " CHANNEL_INPUT </dev/tty
|
||
CHANNEL_INPUT=${CHANNEL_INPUT:-1}
|
||
case $CHANNEL_INPUT in
|
||
1|"") CHANNEL="lts"; BRANCH="main" ;;
|
||
2) CHANNEL="preview"; BRANCH="test" ;;
|
||
*) print_error "无效选择"; exit 1 ;;
|
||
esac
|
||
print_info "已选择: ${CHANNEL} 通道 (${BRANCH} 分支)"
|
||
}
|
||
|
||
# ---------- 环境汇总 ----------
|
||
print_environment_summary() {
|
||
print_title
|
||
print_subtitle "环境检测结果"
|
||
echo ""
|
||
echo -e " ${CYAN}操作系统:${NC} $OS $OS_VERSION"
|
||
echo -e " ${CYAN}CPU 架构:${NC} $ARCH"
|
||
echo ""
|
||
echo " ${CYAN}必要工具:${NC}"
|
||
if [ "$HAS_GIT" = true ]; then
|
||
echo -e " git ✅ 已安装 ($(git --version | awk '{print $3}'))"
|
||
else
|
||
echo " git ❌ 未安装 (将自动安装)"
|
||
fi
|
||
if [ "$HAS_CURL" = true ]; then
|
||
echo " curl ✅ 已安装"
|
||
else
|
||
echo " curl ❌ 未安装 (将自动安装)"
|
||
fi
|
||
if [ "$HAS_WGET" = true ]; then
|
||
echo " wget ✅ 已安装"
|
||
else
|
||
echo " wget ❌ 未安装 (将自动安装)"
|
||
fi
|
||
echo ""
|
||
echo " ${CYAN}Docker 环境:${NC}"
|
||
if [ "$HAS_DOCKER" = true ]; then
|
||
echo -e " docker ✅ 已安装 ($(docker --version | awk '{print $3}' | tr -d ','))"
|
||
else
|
||
echo " docker ❌ 未安装"
|
||
print_error "Docker 未安装,请先安装 Docker"
|
||
exit 1
|
||
fi
|
||
if [ "$CONTAINER_EXISTS" = true ]; then
|
||
echo ""
|
||
echo " ${CYAN}容器状态:${NC}"
|
||
if [ "$CONTAINER_RUNNING" = true ]; then
|
||
echo -e " frpc-console ✅ 运行中"
|
||
else
|
||
echo -e " frpc-console ⏸️ 已停止"
|
||
fi
|
||
CURRENT_VERSION=$(get_current_version)
|
||
[ -n "$CURRENT_VERSION" ] && echo -e " ${CYAN}当前版本:${NC} $CURRENT_VERSION"
|
||
fi
|
||
echo ""
|
||
}
|
||
|
||
# ---------- 部署计划 ----------
|
||
generate_plan() {
|
||
PLAN=""
|
||
# 按时间先后顺序排列
|
||
if [ "$WRITE_DAEMON" = true ]; then
|
||
PLAN="${PLAN} • 配置 Docker 镜像源"
|
||
if [ "$RESTART_DOCKER" = true ]; then
|
||
PLAN="${PLAN}(写入可用地址,并重启 Docker)\n"
|
||
else
|
||
PLAN="${PLAN}\n"
|
||
fi
|
||
fi
|
||
if [ "$NEED_INSTALL_GIT" = true ] || [ "$NEED_INSTALL_CURL" = true ] || [ "$NEED_INSTALL_WGET" = true ]; then
|
||
local pkgs=""
|
||
[ "$NEED_INSTALL_GIT" = true ] && pkgs="${pkgs} git"
|
||
[ "$NEED_INSTALL_CURL" = true ] && pkgs="${pkgs} curl"
|
||
[ "$NEED_INSTALL_WGET" = true ] && pkgs="${pkgs} wget"
|
||
PLAN="${PLAN} • 安装必要工具:${pkgs}\n"
|
||
fi
|
||
PLAN="${PLAN} • 拉取 frpc-console 源码 (${BRANCH} 分支)\n"
|
||
if [ "$CONTAINER_EXISTS" = true ]; then
|
||
PLAN="${PLAN} • 停止并删除旧容器\n"
|
||
fi
|
||
PLAN="${PLAN} • 构建 Docker 镜像: ${IMAGE_NAME}:${IMAGE_TAG}\n"
|
||
PLAN="${PLAN} • 启动容器 (端口 ${PORT})\n"
|
||
PLAN="${PLAN} • 写入版本文件并验证"
|
||
}
|
||
|
||
print_deployment_plan() {
|
||
print_title
|
||
print_subtitle "部署计划"
|
||
echo ""
|
||
echo -e " ${CYAN}将执行以下操作:${NC}"
|
||
echo -e "$(echo -e "$PLAN")"
|
||
echo ""
|
||
echo -e " ${CYAN}配置信息:${NC}"
|
||
echo -e " ────────────────────────────────────"
|
||
echo -e " 监听端口 : ${PORT}"
|
||
echo -e " 部署目录 : ${DEPLOY_DIR}"
|
||
echo -e " 目标版本 : ${TARGET_VERSION}"
|
||
echo -e " 镜像标签 : ${IMAGE_TAG}"
|
||
echo -e " 源码分支 : ${BRANCH}"
|
||
echo -e " ────────────────────────────────────"
|
||
if [ "$CONTAINER_EXISTS" = true ]; then
|
||
echo ""
|
||
echo -e " ${YELLOW}⚠ 检测到已存在的 frpc-console 容器${NC}"
|
||
echo -e " 当前版本: ${CURRENT_VERSION}"
|
||
echo -e " 目标版本: ${TARGET_VERSION}"
|
||
fi
|
||
echo ""
|
||
}
|
||
|
||
confirm_deploy() {
|
||
if [ "$SKIP_CONFIRM" = true ]; then
|
||
echo -e "${GREEN}▶ 已启用 --yes,自动确认${NC}"
|
||
return 0
|
||
fi
|
||
echo -e -n "${CYAN}确认执行? 输入 Y 继续,输入 n 自定义配置 [Y/n]: ${NC}"
|
||
read -r CONFIRM </dev/tty
|
||
case $CONFIRM in
|
||
n|N) return 1 ;;
|
||
*) return 0 ;;
|
||
esac
|
||
}
|
||
|
||
custom_config() {
|
||
print_title
|
||
print_subtitle "自定义配置"
|
||
echo ""
|
||
read -p "请输入监听端口 [${DEFAULT_PORT}]: " INPUT_PORT </dev/tty
|
||
PORT=${INPUT_PORT:-$DEFAULT_PORT}
|
||
read -p "请输入部署目录 [${DEFAULT_DEPLOY_DIR}]: " INPUT_DEPLOY_DIR </dev/tty
|
||
DEPLOY_DIR=${INPUT_DEPLOY_DIR:-$DEFAULT_DEPLOY_DIR}
|
||
DATA_DIR="${DEPLOY_DIR}/data"
|
||
echo ""
|
||
echo -e " ${CYAN}更新后的配置:${NC}"
|
||
echo -e " ────────────────────────────────────"
|
||
echo -e " 监听端口 : ${PORT}"
|
||
echo -e " 部署目录 : ${DEPLOY_DIR}"
|
||
echo -e " 数据目录 : ${DATA_DIR}"
|
||
echo -e " ────────────────────────────────────"
|
||
echo ""
|
||
}
|
||
|
||
# ---------- 执行部署 ----------
|
||
do_deploy() {
|
||
print_title
|
||
print_subtitle "开始部署"
|
||
echo ""
|
||
|
||
# ----- 事务前钩子:备份数据库(仅降级时) -----
|
||
BACKUP_DIR=""
|
||
if [ -n "$CURRENT_VERSION" ] && [ -n "$TARGET_VERSION" ]; then
|
||
CUR_SEMVER=$(echo "$CURRENT_VERSION" | sed -E 's/^([0-9]+\.[0-9]+).*$/\1/')
|
||
TGT_SEMVER=$(echo "$TARGET_VERSION" | sed -E 's/^([0-9]+\.[0-9]+).*$/\1/')
|
||
if [ "$TGT_SEMVER" \< "$CUR_SEMVER" ] 2>/dev/null; then
|
||
print_warn "检测到降级操作: $CURRENT_VERSION → $TARGET_VERSION"
|
||
echo ""
|
||
echo -e "${YELLOW}⚠ 降级可能导致数据不兼容${NC}"
|
||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||
BACKUP_DIR="/opt/frpc-console-backups/${TIMESTAMP}_${CHANNEL}"
|
||
echo -e "${CYAN}备份目录: ${BACKUP_DIR}${NC}"
|
||
read -p "确认执行降级? [y/N]: " CONFIRM_DOWNGRADE </dev/tty
|
||
if [[ ! "$CONFIRM_DOWNGRADE" =~ ^[Yy]$ ]]; then
|
||
print_info "已取消降级操作"
|
||
exit 0
|
||
fi
|
||
print_step "执行降级前备份..."
|
||
mkdir -p "$BACKUP_DIR"
|
||
if [ -d "$DEPLOY_DIR" ]; then
|
||
cp -r "$DEPLOY_DIR" "$BACKUP_DIR/frpc-console"
|
||
print_success "备份完成: $BACKUP_DIR"
|
||
else
|
||
print_warn "部署目录不存在,跳过备份"
|
||
fi
|
||
fi
|
||
else
|
||
print_info "首次部署,跳过版本比对"
|
||
fi
|
||
|
||
# ----- 安装必要工具 -----
|
||
if [ "$NEED_INSTALL_GIT" = true ] || [ "$NEED_INSTALL_CURL" = true ] || [ "$NEED_INSTALL_WGET" = true ]; then
|
||
print_step "安装必要工具..."
|
||
local pkgs=""
|
||
[ "$NEED_INSTALL_GIT" = true ] && pkgs="${pkgs} git"
|
||
[ "$NEED_INSTALL_CURL" = true ] && pkgs="${pkgs} curl"
|
||
[ "$NEED_INSTALL_WGET" = true ] && pkgs="${pkgs} wget"
|
||
case $OS in
|
||
opensuse*|suse*|opensuse-tumbleweed|opensuse-slowroll|opensuse-leap)
|
||
if [ "$NEED_INSTALL_GIT" = true ]; then
|
||
zypper install -y git-core
|
||
pkgs=$(echo "$pkgs" | sed -E 's/(^| )git( |$)/ /g' | sed 's/ */ /g' | sed 's/^ //;s/ $//')
|
||
fi
|
||
[ -n "$pkgs" ] && zypper install -y $pkgs
|
||
;;
|
||
ubuntu|debian|linuxmint) apt update -qq && apt install -y $pkgs ;;
|
||
centos|rhel|fedora|rocky|almalinux) yum install -y $pkgs ;;
|
||
alpine) apk add $pkgs ;;
|
||
arch|manjaro|endeavouros) pacman -S --needed --noconfirm $pkgs ;;
|
||
*) print_error "无法识别包管理器"; exit 1 ;;
|
||
esac
|
||
print_success "必要工具安装完成"
|
||
fi
|
||
|
||
# ----- 拉取代码 -----
|
||
print_step "拉取代码..."
|
||
rm -rf "$WORK_DIR"
|
||
git clone --depth 1 --branch "$BRANCH" "$REPO_URL" "$WORK_DIR"
|
||
cd "$WORK_DIR"
|
||
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 "停止旧容器..."
|
||
if [ "$CONTAINER_RUNNING" = true ]; then
|
||
docker stop frpc-console 2>/dev/null || true
|
||
fi
|
||
docker rm frpc-console 2>/dev/null || true
|
||
print_success "旧容器已清理"
|
||
fi
|
||
|
||
# ----- 准备部署目录 -----
|
||
print_step "准备部署目录..."
|
||
mkdir -p "$DEPLOY_DIR"
|
||
mkdir -p "$DATA_DIR"
|
||
|
||
# ----- 检测并迁移旧版本文件(从根目录迁移到 data/)-----
|
||
print_step "检测旧版本文件..."
|
||
|
||
OLD_DB="${DEPLOY_DIR}/frpc-console.db"
|
||
OLD_TOML="${DEPLOY_DIR}/frpc.toml"
|
||
OLD_LOG="${DEPLOY_DIR}/frpc.log"
|
||
OLD_PID="${DEPLOY_DIR}/frpc.pid"
|
||
OLD_VERSION="${DEPLOY_DIR}/version.ini"
|
||
NEW_DB="${DATA_DIR}/frpc-console.db"
|
||
|
||
if [ -f "$OLD_DB" ] && [ ! -f "$NEW_DB" ]; then
|
||
print_warn "检测到旧版本数据库文件在根目录,将迁移到 data/ 子目录"
|
||
|
||
BACKUP_OLD_DIR="${DEPLOY_DIR}/.old_backup_$(date +%Y%m%d_%H%M%S)"
|
||
mkdir -p "$BACKUP_OLD_DIR"
|
||
|
||
for file in frpc-console.db frpc.toml frpc.log frpc.pid version.ini; do
|
||
if [ -f "${DEPLOY_DIR}/${file}" ]; then
|
||
cp "${DEPLOY_DIR}/${file}" "${BACKUP_OLD_DIR}/" 2>/dev/null || true
|
||
print_info " 已备份: ${file}"
|
||
fi
|
||
done
|
||
|
||
for file in frpc-console.db frpc.toml frpc.log frpc.pid version.ini; do
|
||
if [ -f "${DEPLOY_DIR}/${file}" ]; then
|
||
cp "${DEPLOY_DIR}/${file}" "${DATA_DIR}/" 2>/dev/null || true
|
||
print_info " 已迁移: ${file} → data/"
|
||
fi
|
||
done
|
||
|
||
print_success "旧文件已迁移到 data/ 目录"
|
||
print_info "旧文件备份位置: ${BACKUP_OLD_DIR}"
|
||
print_info "确认服务正常后,可手动删除备份目录"
|
||
else
|
||
print_info "未检测到需要迁移的旧文件"
|
||
fi
|
||
|
||
# ----- 构建 Docker 镜像 -----
|
||
print_step "构建 Docker 镜像..."
|
||
docker build --no-cache \
|
||
-t "${IMAGE_NAME}:${IMAGE_TAG}" \
|
||
.
|
||
if [ $? -ne 0 ]; then
|
||
print_error "Docker 构建失败"
|
||
exit 1
|
||
fi
|
||
print_success "Docker 镜像构建完成: ${IMAGE_NAME}:${IMAGE_TAG}"
|
||
|
||
# 验证镜像内容
|
||
print_step "验证镜像内容..."
|
||
if docker run --rm --entrypoint ls "${IMAGE_NAME}:${IMAGE_TAG}" -l /app/frpc-console 2>/dev/null; then
|
||
print_success "二进制文件存在于镜像中"
|
||
else
|
||
print_error "镜像中缺少 /app/frpc-console"
|
||
exit 1
|
||
fi
|
||
|
||
# 标记通道标签
|
||
if [ "$CHANNEL" = "lts" ]; then
|
||
docker tag "${IMAGE_NAME}:${IMAGE_TAG}" "${IMAGE_NAME}:lts"
|
||
print_info "已标记 ${IMAGE_NAME}:lts"
|
||
else
|
||
docker tag "${IMAGE_NAME}:${IMAGE_TAG}" "${IMAGE_NAME}:preview"
|
||
print_info "已标记 ${IMAGE_NAME}:preview"
|
||
fi
|
||
|
||
# ----- 启动新容器 -----
|
||
print_step "启动 frpc-console 容器..."
|
||
docker run -d \
|
||
--name frpc-console \
|
||
--restart=always \
|
||
--network host \
|
||
-v ${DEPLOY_DIR}/data:/app/data \
|
||
-e PORT=${PORT} \
|
||
-e TZ=Asia/Shanghai \
|
||
${IMAGE_NAME}:${IMAGE_TAG}
|
||
|
||
if docker ps | grep -q frpc-console; then
|
||
print_success "容器启动成功!"
|
||
else
|
||
print_error "容器启动失败,请检查日志: docker logs frpc-console"
|
||
exit 1
|
||
fi
|
||
|
||
# ----- 写入版本文件 -----
|
||
print_step "写入版本文件..."
|
||
echo "$TARGET_VERSION" > "${DEPLOY_DIR}/data/version.ini"
|
||
print_success "版本写入: ${TARGET_VERSION}"
|
||
|
||
# ----- 检查 frpc 子进程(基于 PID 文件)-----
|
||
print_step "检查 frpc 状态..."
|
||
sleep 3
|
||
|
||
PID_FILE="${DEPLOY_DIR}/data/frpc.pid"
|
||
if [ -f "$PID_FILE" ]; then
|
||
PID=$(cat "$PID_FILE" 2>/dev/null | tr -d ' \n')
|
||
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
|
||
print_success "frpc 进程运行正常 (PID: $PID)"
|
||
else
|
||
print_warn "frpc 进程未运行(PID 文件存在但进程已退出,可能配置为空)"
|
||
fi
|
||
else
|
||
print_warn "frpc 进程未运行(PID 文件不存在,请导入 TOML 后重试)"
|
||
fi
|
||
|
||
# ----- 事务后钩子:检测数据库 -----
|
||
print_step "验证数据库状态..."
|
||
if docker exec frpc-console sqlite3 /app/data/frpc-console.db "SELECT COUNT(*) FROM users;" 2>/dev/null | grep -q "^[0-9]"; then
|
||
print_success "数据库可用"
|
||
else
|
||
print_warn "数据库为空或不可用,请通过 WebUI 注册管理员账户"
|
||
fi
|
||
|
||
# ----- 验证版本 -----
|
||
print_step "验证版本..."
|
||
sleep 1
|
||
CONTAINER_VERSION=$(docker exec frpc-console cat /app/data/version.ini 2>/dev/null | grep -v "^[;#]" | head -1 | tr -d '\n' || echo "unknown")
|
||
echo -e " 容器版本: ${CONTAINER_VERSION}"
|
||
echo -e " 目标版本: ${TARGET_VERSION}"
|
||
if [ "$CONTAINER_VERSION" = "$TARGET_VERSION" ]; then
|
||
print_success "版本写入成功"
|
||
else
|
||
print_warn "版本不匹配,请检查"
|
||
fi
|
||
|
||
# ----- 清理旧镜像 -----
|
||
print_step "清理旧镜像..."
|
||
CURRENT_IMAGE_ID=$(docker images -q "${IMAGE_NAME}:${IMAGE_TAG}" 2>/dev/null)
|
||
if [ -n "$CURRENT_IMAGE_ID" ]; then
|
||
ALL_IMAGES=$(docker images "${IMAGE_NAME}:*" --format "{{.ID}} {{.CreatedAt}}" 2>/dev/null | sort -k2 -r | awk '{print $1}')
|
||
COUNTER=0
|
||
TO_DELETE=""
|
||
for IMG_ID in $ALL_IMAGES; do
|
||
if [ "$IMG_ID" = "$CURRENT_IMAGE_ID" ]; then
|
||
continue
|
||
fi
|
||
COUNTER=$((COUNTER + 1))
|
||
if [ $COUNTER -le $KEEP_IMAGES ]; then
|
||
continue
|
||
fi
|
||
TO_DELETE="$TO_DELETE $IMG_ID"
|
||
done
|
||
if [ -n "$TO_DELETE" ]; then
|
||
docker rmi -f $TO_DELETE 2>/dev/null || true
|
||
print_info "已清理旧镜像"
|
||
else
|
||
print_info "无需清理,镜像数量未超过保留策略"
|
||
fi
|
||
fi
|
||
|
||
# ----- 备份目录提示 -----
|
||
if [ -n "$BACKUP_DIR" ]; then
|
||
echo ""
|
||
print_warn "降级前数据已备份到: $BACKUP_DIR"
|
||
echo " 如需回退: cp -r $BACKUP_DIR/frpc-console/* $DEPLOY_DIR/"
|
||
fi
|
||
|
||
# ----- 清理临时文件 -----
|
||
print_step "清理临时文件..."
|
||
rm -rf "$WORK_DIR"
|
||
print_success "临时文件已清理"
|
||
|
||
# ----- 输出 -----
|
||
print_title
|
||
print_success "frpc-console 部署完成!"
|
||
print_title
|
||
echo ""
|
||
echo -e " ${CYAN}📍 访问地址:${NC} http://$(hostname -I | awk '{print $1}'):${PORT}"
|
||
echo -e " ${CYAN}📂 数据目录:${NC} ${DEPLOY_DIR}"
|
||
echo -e " ${CYAN}🐳 容器名称:${NC} frpc-console"
|
||
echo -e " ${CYAN}🏷️ 版本:${NC} ${TARGET_VERSION}"
|
||
echo -e " ${CYAN}🌿 分支:${NC} ${BRANCH}"
|
||
echo ""
|
||
echo -e " ${CYAN}常用命令:${NC}"
|
||
echo " docker logs frpc-console # 查看日志"
|
||
echo " docker restart frpc-console # 重启服务"
|
||
echo " cat ${DEPLOY_DIR}/data/version.ini # 查看当前版本"
|
||
echo ""
|
||
echo -e " ${YELLOW}首次访问需要注册管理员账户${NC}"
|
||
echo ""
|
||
print_title
|
||
}
|
||
|
||
# ---------- 主流程 ----------
|
||
main() {
|
||
clear 2>/dev/null || true
|
||
parse_args "$@"
|
||
check_root
|
||
|
||
print_step "检测环境..."
|
||
detect_os
|
||
detect_arch
|
||
check_tools
|
||
check_container
|
||
|
||
# ---------- 镜像源检测(拉取代码前,只检测不修改) ----------
|
||
check_docker_mirrors
|
||
|
||
select_channel
|
||
generate_target_version "$CHANNEL" "$BRANCH"
|
||
|
||
CURRENT_VERSION=""
|
||
if [ "$CONTAINER_EXISTS" = true ]; then
|
||
CURRENT_VERSION=$(get_current_version)
|
||
fi
|
||
|
||
print_environment_summary
|
||
|
||
if [ "$CHECK_ONLY" = true ]; then
|
||
print_info "环境检测完成(--check 模式)"
|
||
exit 0
|
||
fi
|
||
|
||
if [ "$HAS_DOCKER" = false ]; then
|
||
print_error "Docker 未安装"
|
||
exit 1
|
||
fi
|
||
|
||
generate_plan
|
||
print_deployment_plan
|
||
|
||
if ! confirm_deploy; then
|
||
custom_config
|
||
generate_plan
|
||
print_deployment_plan
|
||
if ! confirm_deploy; then
|
||
print_info "已取消部署"
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
if [ "$DRY_RUN" = true ]; then
|
||
print_info "演练模式(--dry-run)"
|
||
exit 0
|
||
fi
|
||
|
||
do_deploy
|
||
}
|
||
|
||
main "$@" |