#!/bin/bash # ================= 配置区域 ================= # 源代码路径 SOURCE_DIR="/home/lq/lq_workspace/LQAgentServer/source/LQAgentPlatform" # Docker Compose 运行路径 DOCKER_APP_DIR="/home/lq/lq_workspace/LQAgentServer/app/docker" # 配置文件名称 COMPOSE_FILE="docker-compose.yml" # 镜像名称 (Repository) IMAGE_NAME="lq_agent_platform_server_dev" # Git 凭证 GIT_USER="WangXuMing" GIT_PASS="123456" # ================= 辅助函数 ================= # 打印带时间戳的日志 log_info() { echo -e "\033[32m[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1\033[0m" } log_error() { echo -e "\033[31m[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1\033[0m" } # 检查命令执行状态,如果失败则退出 check_status() { if [ $? -ne 0 ]; then log_error "$1 执行失败,脚本终止。" exit 1 fi } # ================= 步骤 1: Git 拉取代码 (带重试+强制拉取) ================= log_info "步骤 1: 进入源码目录并拉取最新代码..." if [ ! -d "$SOURCE_DIR" ]; then log_error "源码目录不存在: $SOURCE_DIR" exit 1 fi # 检查目录进入权限并修复 if [ ! -x "$SOURCE_DIR" ]; then log_error "源码目录无进入权限!正在修复..." sudo chmod +x "$SOURCE_DIR" sudo chown -R lq:lq "$SOURCE_DIR" fi cd "$SOURCE_DIR" || { log_error "进入源码目录失败!路径:$SOURCE_DIR" log_error "可能原因:1. 目录权限不足 2. 路径含特殊字符 3. 目录被删除" exit 1 } check_status "进入源码目录" # 双重保障 # 检查是否为 Git 仓库 if [ ! -d ".git" ]; then log_error "当前目录不是 Git 仓库!路径:$SOURCE_DIR" exit 1 fi log_info "检查本地是否存在可能与远程冲突的已修改文件..." HAS_CONFLICT_FILES=$(git status --porcelain | grep -v "^??") if [ -n "$HAS_CONFLICT_FILES" ]; then log_info "发现以下文件存在本地修改(将被远程最新代码覆盖):" echo "$HAS_CONFLICT_FILES" | awk '{print " - " $2}' log_info "正在强制丢弃本地修改,确保同步远程最新代码..." # 强制丢弃修改 git checkout -- . # 仅丢弃已跟踪文件的本地修改(冲突风险文件) log_info "本地冲突文件修改已丢弃,准备拉取远程最新代码..." else log_info "本地无可能冲突的已修改文件,直接拉取远程最新代码..." fi # 组装 Git 认证 URL(保留原逻辑) ORIGIN_URL=$(git remote get-url origin 2>/dev/null) if [ $? -ne 0 ]; then log_error "获取 Git 远程地址失败!请检查 remote 配置" exit 1 fi # 初始化认证 URL(默认使用 origin 远程) CLEAN_URL=${ORIGIN_URL#*://} AUTH_URL="http://${GIT_USER}:${GIT_PASS}@${CLEAN_URL}" # 定义备用远程(upstream)及认证 URL UPSTREAM_URL=$(git remote get-url upstream 2>/dev/null) if [ $? -ne 0 ]; then log_warn "未配置 upstream 远程,503 时无法切换备用源" UPSTREAM_AVAILABLE=0 else UPSTREAM_CLEAN_URL=${UPSTREAM_URL#*://} UPSTREAM_AUTH_URL="http://${GIT_USER}:${GIT_PASS}@${UPSTREAM_CLEAN_URL}" UPSTREAM_AVAILABLE=1 fi MAX_RETRIES=3 COUNT=0 GIT_SUCCESS=0 CURRENT_AUTH_URL="$AUTH_URL" # 当前使用的认证 URL while [ $COUNT -lt $MAX_RETRIES ]; do log_info "正在执行 Git Pull (第 $((COUNT+1)) 次尝试) - 强制拉取 dev 分支最新代码..." log_info "当前使用远程地址:${CURRENT_AUTH_URL}" # 执行 git pull 并捕获错误输出 PULL_OUTPUT=$(git pull "$CURRENT_AUTH_URL" dev --force --allow-unrelated-histories 2>&1) PULL_EXIT_CODE=$? if [ $PULL_EXIT_CODE -eq 0 ]; then # 拉取成功:输出结果并退出循环 GIT_SUCCESS=1 LATEST_COMMIT=$(git log -1 --format="%h - %s ") log_info "Git Pull 成功!当前部署提交版本:$LATEST_COMMIT" break else # 拉取失败:判断错误类型(新增 returned error: 503 匹配规则) if echo "$PULL_OUTPUT" | grep -qiE "503 Service Unavailable|503 Unavailable|returned error: 503" && [ $UPSTREAM_AVAILABLE -eq 1 ]; then # 错误类型:503 服务不可用 + 有备用 upstream 远程 log_error "Git Pull 失败:当前远程(origin)返回 503 不可达,切换到备用远程(upstream)重试..." log_error "错误详情:$PULL_OUTPUT" CURRENT_AUTH_URL="$UPSTREAM_AUTH_URL" # 切换为 upstream 认证 URL COUNT=$((COUNT+1)) sleep 3 elif echo "$PULL_OUTPUT" | grep -qiE "503 Service Unavailable|503 Unavailable|returned error: 503" && [ $UPSTREAM_AVAILABLE -eq 0 ]; then # 错误类型:503 但无备用源 log_error "Git Pull 失败:远程返回 503 不可达,但未配置 upstream 备用源,无法切换..." log_error "错误详情:$PULL_OUTPUT" COUNT=$((COUNT+1)) sleep 3 else # 其他错误(如认证失败、网络不通、分支不存在等):按原逻辑重试 log_error "Git Pull 失败(非 503 错误),准备重试..." log_error "错误详情:$PULL_OUTPUT" COUNT=$((COUNT+1)) sleep 3 fi fi done # 所有重试失败后的处理 if [ $GIT_SUCCESS -eq 0 ]; then log_error "Git Pull 已重试 $MAX_RETRIES 次,全部失败!" exit 1 fi # ================= 步骤 2: 关闭当前容器 ================= log_info "步骤 2: 关闭正在运行的容器..." if [ ! -d "$DOCKER_APP_DIR" ]; then log_error "Docker 运行目录不存在: $DOCKER_APP_DIR" exit 1 fi cd "$DOCKER_APP_DIR" check_status "进入 Docker 运行目录" docker compose down # 即使 down 失败(例如没启动),也继续执行,只记录错误 if [ $? -ne 0 ]; then log_error "警告: Docker Compose Down 返回非零状态,尝试继续..." fi # ================= 步骤 3: 提取版本号并删除旧镜像 ================= log_info "步骤 3: 查找旧镜像并计算新版本号..." # 获取镜像信息,例如: lq_agent_platform_server v0.13 76d87fcfb5e5 IMAGE_INFO=$(docker images | grep "^${IMAGE_NAME} " | awk '{print $2, $3}' | head -n 1) NEW_TAG="v0.01" # 默认初始版本 if [ -n "$IMAGE_INFO" ]; then OLD_TAG=$(echo "$IMAGE_INFO" | awk '{print $1}') IMAGE_ID=$(echo "$IMAGE_INFO" | awk '{print $2}') log_info "找到旧镜像: Tag=$OLD_TAG, ID=$IMAGE_ID" # 提取版本号数字 (去掉 'v'),例如 v0.13 -> 0.13 VERSION_NUM=$(echo "$OLD_TAG" | sed 's/v//') # 计算新版本号 (这里设置为 +0.01,即 0.13 -> 0.14) NEW_VERSION_NUM=$(echo "$VERSION_NUM" | awk '{printf "%.2f", $1 + 0.01}') NEW_TAG="v$NEW_VERSION_NUM" log_info "计算出的新版本号为: $NEW_TAG" log_info "正在删除旧镜像 ID: $IMAGE_ID ..." docker rmi -f "$IMAGE_ID" if [ $? -ne 0 ]; then log_error "警告: 删除旧镜像失败,可能被占用,将继续构建。" else log_info "旧镜像删除成功。" fi else log_info "未找到旧镜像,将使用默认版本 $NEW_TAG 构建。" fi # ================= 步骤 4: 构建新镜像 ================= log_info "步骤 4: 构建新镜像 $IMAGE_NAME:$NEW_TAG ..." cd "$SOURCE_DIR" check_status "返回源码目录" docker build -t "${IMAGE_NAME}:${NEW_TAG}" . check_status "镜像构建" log_info "镜像构建成功: ${IMAGE_NAME}:${NEW_TAG}" # ================= 步骤 5: 修改 docker-compose.yml 版本号 ================= log_info "步骤 5: 更新 docker-compose.yml 中的版本号..." cd "$DOCKER_APP_DIR" check_status "进入 Docker 运行目录" if [ ! -f "$COMPOSE_FILE" ]; then log_error "找不到配置文件: $COMPOSE_FILE" exit 1 fi # 使用 sed 正则替换 # 匹配: image: lq_agent_platform_server:任意字符 # 替换为: image: lq_agent_platform_server:新版本号 sed -i "s|image: ${IMAGE_NAME}:.*|image: ${IMAGE_NAME}:${NEW_TAG}|" "$COMPOSE_FILE" check_status "修改 docker-compose.yml" # 验证修改结果 MATCH_LINE=$(grep "image: ${IMAGE_NAME}:" "$COMPOSE_FILE") log_info "配置文件已更新: $MATCH_LINE" # ================= 步骤 6: 启动容器 ================= log_info "步骤 6: 启动 Docker Compose..." docker compose up -d check_status "启动容器" log_info "====================================================" log_info " 开发版部署成功!当前运行端口8002,部署版本: $NEW_TAG" log_info "===================================================="