deploy.sh 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. #!/bin/bash
  2. # ================= 配置区域 =================
  3. # 源代码路径
  4. SOURCE_DIR="/home/lq/lq_workspace/LQAgentServer/source/LQAgentPlatform"
  5. # Docker Compose 运行路径
  6. DOCKER_APP_DIR="/home/lq/lq_workspace/LQAgentServer/app/docker"
  7. # 配置文件名称
  8. COMPOSE_FILE="docker-compose.yml"
  9. # 镜像名称 (Repository)
  10. IMAGE_NAME="lq_agent_platform_server_dev"
  11. # Git 凭证
  12. GIT_USER="WangXuMing"
  13. GIT_PASS="123456"
  14. # ================= 辅助函数 =================
  15. # 打印带时间戳的日志
  16. log_info() {
  17. echo -e "\033[32m[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1\033[0m"
  18. }
  19. log_error() {
  20. echo -e "\033[31m[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1\033[0m"
  21. }
  22. # 检查命令执行状态,如果失败则退出
  23. check_status() {
  24. if [ $? -ne 0 ]; then
  25. log_error "$1 执行失败,脚本终止。"
  26. exit 1
  27. fi
  28. }
  29. # ================= 步骤 1: Git 拉取代码 (带重试+强制拉取) =================
  30. log_info "步骤 1: 进入源码目录并拉取最新代码..."
  31. if [ ! -d "$SOURCE_DIR" ]; then
  32. log_error "源码目录不存在: $SOURCE_DIR"
  33. exit 1
  34. fi
  35. # 检查目录进入权限并修复
  36. if [ ! -x "$SOURCE_DIR" ]; then
  37. log_error "源码目录无进入权限!正在修复..."
  38. sudo chmod +x "$SOURCE_DIR"
  39. sudo chown -R lq:lq "$SOURCE_DIR"
  40. fi
  41. cd "$SOURCE_DIR" || {
  42. log_error "进入源码目录失败!路径:$SOURCE_DIR"
  43. log_error "可能原因:1. 目录权限不足 2. 路径含特殊字符 3. 目录被删除"
  44. exit 1
  45. }
  46. check_status "进入源码目录" # 双重保障
  47. # 检查是否为 Git 仓库
  48. if [ ! -d ".git" ]; then
  49. log_error "当前目录不是 Git 仓库!路径:$SOURCE_DIR"
  50. exit 1
  51. fi
  52. log_info "检查本地是否存在可能与远程冲突的已修改文件..."
  53. HAS_CONFLICT_FILES=$(git status --porcelain | grep -v "^??")
  54. if [ -n "$HAS_CONFLICT_FILES" ]; then
  55. log_info "发现以下文件存在本地修改(将被远程最新代码覆盖):"
  56. echo "$HAS_CONFLICT_FILES" | awk '{print " - " $2}'
  57. log_info "正在强制丢弃本地修改,确保同步远程最新代码..."
  58. # 强制丢弃修改
  59. git checkout -- . # 仅丢弃已跟踪文件的本地修改(冲突风险文件)
  60. log_info "本地冲突文件修改已丢弃,准备拉取远程最新代码..."
  61. else
  62. log_info "本地无可能冲突的已修改文件,直接拉取远程最新代码..."
  63. fi
  64. # 组装 Git 认证 URL(保留原逻辑)
  65. ORIGIN_URL=$(git remote get-url origin 2>/dev/null)
  66. if [ $? -ne 0 ]; then
  67. log_error "获取 Git 远程地址失败!请检查 remote 配置"
  68. exit 1
  69. fi
  70. # 初始化认证 URL(默认使用 origin 远程)
  71. CLEAN_URL=${ORIGIN_URL#*://}
  72. AUTH_URL="http://${GIT_USER}:${GIT_PASS}@${CLEAN_URL}"
  73. # 定义备用远程(upstream)及认证 URL
  74. UPSTREAM_URL=$(git remote get-url upstream 2>/dev/null)
  75. if [ $? -ne 0 ]; then
  76. log_warn "未配置 upstream 远程,503 时无法切换备用源"
  77. UPSTREAM_AVAILABLE=0
  78. else
  79. UPSTREAM_CLEAN_URL=${UPSTREAM_URL#*://}
  80. UPSTREAM_AUTH_URL="http://${GIT_USER}:${GIT_PASS}@${UPSTREAM_CLEAN_URL}"
  81. UPSTREAM_AVAILABLE=1
  82. fi
  83. MAX_RETRIES=3
  84. COUNT=0
  85. GIT_SUCCESS=0
  86. CURRENT_AUTH_URL="$AUTH_URL" # 当前使用的认证 URL
  87. while [ $COUNT -lt $MAX_RETRIES ]; do
  88. log_info "正在执行 Git Pull (第 $((COUNT+1)) 次尝试) - 强制拉取 dev 分支最新代码..."
  89. log_info "当前使用远程地址:${CURRENT_AUTH_URL}"
  90. # 执行 git pull 并捕获错误输出
  91. PULL_OUTPUT=$(git pull "$CURRENT_AUTH_URL" dev --force --allow-unrelated-histories 2>&1)
  92. PULL_EXIT_CODE=$?
  93. if [ $PULL_EXIT_CODE -eq 0 ]; then
  94. # 拉取成功:输出结果并退出循环
  95. GIT_SUCCESS=1
  96. LATEST_COMMIT=$(git log -1 --format="%h - %s ")
  97. log_info "Git Pull 成功!当前部署提交版本:$LATEST_COMMIT"
  98. break
  99. else
  100. # 拉取失败:判断错误类型(新增 returned error: 503 匹配规则)
  101. if echo "$PULL_OUTPUT" | grep -qiE "503 Service Unavailable|503 Unavailable|returned error: 503" && [ $UPSTREAM_AVAILABLE -eq 1 ]; then
  102. # 错误类型:503 服务不可用 + 有备用 upstream 远程
  103. log_error "Git Pull 失败:当前远程(origin)返回 503 不可达,切换到备用远程(upstream)重试..."
  104. log_error "错误详情:$PULL_OUTPUT"
  105. CURRENT_AUTH_URL="$UPSTREAM_AUTH_URL" # 切换为 upstream 认证 URL
  106. COUNT=$((COUNT+1))
  107. sleep 3
  108. elif echo "$PULL_OUTPUT" | grep -qiE "503 Service Unavailable|503 Unavailable|returned error: 503" && [ $UPSTREAM_AVAILABLE -eq 0 ]; then
  109. # 错误类型:503 但无备用源
  110. log_error "Git Pull 失败:远程返回 503 不可达,但未配置 upstream 备用源,无法切换..."
  111. log_error "错误详情:$PULL_OUTPUT"
  112. COUNT=$((COUNT+1))
  113. sleep 3
  114. else
  115. # 其他错误(如认证失败、网络不通、分支不存在等):按原逻辑重试
  116. log_error "Git Pull 失败(非 503 错误),准备重试..."
  117. log_error "错误详情:$PULL_OUTPUT"
  118. COUNT=$((COUNT+1))
  119. sleep 3
  120. fi
  121. fi
  122. done
  123. # 所有重试失败后的处理
  124. if [ $GIT_SUCCESS -eq 0 ]; then
  125. log_error "Git Pull 已重试 $MAX_RETRIES 次,全部失败!"
  126. exit 1
  127. fi
  128. # ================= 步骤 2: 关闭当前容器 =================
  129. log_info "步骤 2: 关闭正在运行的容器..."
  130. if [ ! -d "$DOCKER_APP_DIR" ]; then
  131. log_error "Docker 运行目录不存在: $DOCKER_APP_DIR"
  132. exit 1
  133. fi
  134. cd "$DOCKER_APP_DIR"
  135. check_status "进入 Docker 运行目录"
  136. docker compose down
  137. # 即使 down 失败(例如没启动),也继续执行,只记录错误
  138. if [ $? -ne 0 ]; then
  139. log_error "警告: Docker Compose Down 返回非零状态,尝试继续..."
  140. fi
  141. # ================= 步骤 3: 提取版本号并删除旧镜像 =================
  142. log_info "步骤 3: 查找旧镜像并计算新版本号..."
  143. # 获取镜像信息,例如: lq_agent_platform_server v0.13 76d87fcfb5e5
  144. IMAGE_INFO=$(docker images | grep "^${IMAGE_NAME} " | awk '{print $2, $3}' | head -n 1)
  145. NEW_TAG="v0.01" # 默认初始版本
  146. if [ -n "$IMAGE_INFO" ]; then
  147. OLD_TAG=$(echo "$IMAGE_INFO" | awk '{print $1}')
  148. IMAGE_ID=$(echo "$IMAGE_INFO" | awk '{print $2}')
  149. log_info "找到旧镜像: Tag=$OLD_TAG, ID=$IMAGE_ID"
  150. # 提取版本号数字 (去掉 'v'),例如 v0.13 -> 0.13
  151. VERSION_NUM=$(echo "$OLD_TAG" | sed 's/v//')
  152. # 计算新版本号 (这里设置为 +0.01,即 0.13 -> 0.14)
  153. NEW_VERSION_NUM=$(echo "$VERSION_NUM" | awk '{printf "%.2f", $1 + 0.01}')
  154. NEW_TAG="v$NEW_VERSION_NUM"
  155. log_info "计算出的新版本号为: $NEW_TAG"
  156. log_info "正在删除旧镜像 ID: $IMAGE_ID ..."
  157. docker rmi -f "$IMAGE_ID"
  158. if [ $? -ne 0 ]; then
  159. log_error "警告: 删除旧镜像失败,可能被占用,将继续构建。"
  160. else
  161. log_info "旧镜像删除成功。"
  162. fi
  163. else
  164. log_info "未找到旧镜像,将使用默认版本 $NEW_TAG 构建。"
  165. fi
  166. # ================= 步骤 4: 构建新镜像 =================
  167. log_info "步骤 4: 构建新镜像 $IMAGE_NAME:$NEW_TAG ..."
  168. cd "$SOURCE_DIR"
  169. check_status "返回源码目录"
  170. docker build -t "${IMAGE_NAME}:${NEW_TAG}" .
  171. check_status "镜像构建"
  172. log_info "镜像构建成功: ${IMAGE_NAME}:${NEW_TAG}"
  173. # ================= 步骤 5: 修改 docker-compose.yml 版本号 =================
  174. log_info "步骤 5: 更新 docker-compose.yml 中的版本号..."
  175. cd "$DOCKER_APP_DIR"
  176. check_status "进入 Docker 运行目录"
  177. if [ ! -f "$COMPOSE_FILE" ]; then
  178. log_error "找不到配置文件: $COMPOSE_FILE"
  179. exit 1
  180. fi
  181. # 使用 sed 正则替换
  182. # 匹配: image: lq_agent_platform_server:任意字符
  183. # 替换为: image: lq_agent_platform_server:新版本号
  184. sed -i "s|image: ${IMAGE_NAME}:.*|image: ${IMAGE_NAME}:${NEW_TAG}|" "$COMPOSE_FILE"
  185. check_status "修改 docker-compose.yml"
  186. # 验证修改结果
  187. MATCH_LINE=$(grep "image: ${IMAGE_NAME}:" "$COMPOSE_FILE")
  188. log_info "配置文件已更新: $MATCH_LINE"
  189. # ================= 步骤 6: 启动容器 =================
  190. log_info "步骤 6: 启动 Docker Compose..."
  191. docker compose up -d
  192. check_status "启动容器"
  193. log_info "===================================================="
  194. log_info " 开发版部署成功!当前运行端口8002,部署版本: $NEW_TAG"
  195. log_info "===================================================="