Files
frpc-console/deploy.sh
T
2026-07-30 12:48:46 +08:00

639 lines
22 KiB
Bash
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
# ============================================================
# 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
# ---------- 状态变量 ----------
OS=""; OS_VERSION=""; ARCH=""
HAS_GIT=false; HAS_CURL=false; HAS_WGET=false; HAS_DOCKER=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
}
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
}
# ---------- 版本管理 ----------
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}/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/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 [ "$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"
PLAN="${PLAN} • 构建 Docker 镜像: ${IMAGE_NAME}:${IMAGE_TAG}\n"
PLAN="${PLAN} • 目标版本: ${TARGET_VERSION}\n"
if [ "$CONTAINER_EXISTS" = true ]; then
PLAN="${PLAN} • 停止并删除旧容器\n"
fi
PLAN="${PLAN} • 启动 frpc-console 容器 (端口 ${PORT})"
}
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})"
# ----- 停止旧容器 -----
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"
# 如果根目录有旧数据库文件,但 data/ 目录没有,执行迁移
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
# 复制文件到 data/ 目录
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/frpc-console.db "SELECT COUNT(*) FROM users;" 2>/dev/null | grep -q "^[0-9]"; then
print_success "数据库可用"
else
print_warn "数据库为空或不可用,请通过 WebUI 注册管理员账户"
fi
# ----- 验证版本 -----
CONTAINER_VERSION=$(docker exec frpc-console cat /app/version.ini 2>/dev/null | tr -d '\n' || echo "")
if [ "$CONTAINER_VERSION" = "$TARGET_VERSION" ]; then
print_success "容器版本验证通过: ${CONTAINER_VERSION}"
else
print_warn "容器版本: ${CONTAINER_VERSION}, 目标版本: ${TARGET_VERSION}"
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}/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
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 "$@"