#!/bin/bash # --- Harbor 配置 --- HARBOR_HOST="10.192.72.12:5000" HARBOR_USER="admin" HARBOR_PASS="Harbor12345" PROJECT_NAME="default" REPO_NAME="yusys_ai_web-yusys-ai-web" HARBOR_URL="http://${HARBOR_HOST}" # --- Portainer 配置 --- PORTAINER_URL="https://10.192.72.12:9443" PORTAINER_TOKEN="ptr_S0ynFUayDlFCGptUhIcfEuv+3FMKdz90CAb2x3YEUKw=" STACK_ID="13" ENDPOINT_ID="2" # --- 从 Harbor 查询最新 tag --- echo "→ 正在从 Harbor 查询最新镜像标签..." # 获取所有 artifacts 的 JSON 响应 ARTIFACTS_JSON=$(curl -s -u "${HARBOR_USER}:${HARBOR_PASS}" \ "${HARBOR_URL}/api/v2.0/projects/${PROJECT_NAME}/repositories/${REPO_NAME}/artifacts" 2>/dev/null) # 检查是否成功获取数据 if [ -z "$ARTIFACTS_JSON" ] || echo "$ARTIFACTS_JSON" | grep -qE '"code"|"errors"'; then echo "⚠️ 无法连接到 Harbor 或仓库不存在,使用默认标签 1.0.0" TAG="1.0.0" else # 提取所有 tags 部分,然后从中提取 name 字段的值(只匹配语义化版本号格式 x.y.z) # 方法:先提取所有 "tags":[...] 部分,然后从中提取 "name":"x.y.z" ALL_TAGS=$(echo "$ARTIFACTS_JSON" | grep -o '"tags":[^]]*' | grep -o '"name":"[0-9]\+\.[0-9]\+\.[0-9]\+"' | sed 's/"name":"\([0-9]\+\.[0-9]\+\.[0-9]\+\)"/\1/' | sort -V | uniq) if [ -z "$ALL_TAGS" ]; then echo "⚠️ 未找到符合格式的标签,使用默认标签 1.0.0" TAG="1.0.0" else # 显示所有找到的标签(用于调试) echo "→ 找到的标签列表:" echo "$ALL_TAGS" | while read tag; do echo " - $tag" done # 获取最新的标签(按版本号排序后的最后一个) LATEST_TAG=$(echo "$ALL_TAGS" | tail -1) echo "→ 最新标签: ${LATEST_TAG}" # 小版本号加1 (例如: 1.0.9 -> 1.0.10) MAJOR=$(echo "$LATEST_TAG" | cut -d. -f1) MINOR=$(echo "$LATEST_TAG" | cut -d. -f2) PATCH=$(echo "$LATEST_TAG" | cut -d. -f3) PATCH=$((PATCH + 1)) TAG="${MAJOR}.${MINOR}.${PATCH}" echo "→ 新标签: ${TAG}" fi fi IMAGE="${HARBOR_HOST}/${PROJECT_NAME}/${REPO_NAME}:${TAG}" # --- 分支检查 --- CUR_BRANCH=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || git rev-parse --abbrev-ref HEAD) [ "$CUR_BRANCH" = "test" ] || { echo "❌ 仅允许在 test 分支打包!"; exit 1; } # --- 交互式拉代码 --- echo "→ 正在 git pull,请输入用户名/密码 ..." git pull origin test || { echo "❌ git pull 失败"; exit 1; } # --- 后续流程 --- npm run build || { echo "❌ npm run build 失败"; exit 1; } docker build -t "${IMAGE}" . && docker push "${IMAGE}" && { # 从 Harbor API 获取镜像的 digest echo "→ 正在获取镜像 SHA256..." ARTIFACT_INFO=$(curl -s -u "${HARBOR_USER}:${HARBOR_PASS}" \ "${HARBOR_URL}/api/v2.0/projects/${PROJECT_NAME}/repositories/${REPO_NAME}/artifacts/${TAG}") SHA256=$(printf '%s' "$ARTIFACT_INFO" | grep -o '"digest":"sha256:[^"]*' | cut -d'"' -f4 | cut -d':' -f2) echo "📦 Image SHA256: ${SHA256}" # 更新 Portainer 堆栈中的镜像 SHA256 if [ -n "$SHA256" ]; then echo "→ 正在更新 Portainer 堆栈..." # 获取当前的堆栈文件内容 STACK_FILE_RESPONSE=$(curl -s -k -X GET -H "X-API-Key: ${PORTAINER_TOKEN}" \ "${PORTAINER_URL}/api/stacks/${STACK_ID}/file") echo "→ Portainer 堆栈文件:${STACK_FILE_RESPONSE}" if [ -z "$STACK_FILE_RESPONSE" ] || echo "$STACK_FILE_RESPONSE" | grep -qE '"message"|"error"'; then echo "⚠️ 无法获取 Portainer 堆栈文件,跳过更新" else # 直接在 JSON 字符串中替换 SHA256(SHA256 只包含十六进制字符,无需转义) UPDATE_JSON=$(printf '%s' "$STACK_FILE_RESPONSE" | sed -E "s/@sha256:[0-9a-fA-F]{64}/@sha256:${SHA256}/g") echo "→ 修改后的 Portainer 堆栈文件:${UPDATE_JSON}" BODY_PREFIX=${UPDATE_JSON%\}} FINAL_PAYLOAD="${BODY_PREFIX}, \"prune\": false, \"pullImage\": true }" # 更新堆栈文件 UPDATE_RESPONSE=$(curl -s -k -X PUT \ -H "X-API-Key: ${PORTAINER_TOKEN}" \ -H "Content-Type: application/json" \ -d "${FINAL_PAYLOAD}" \ "${PORTAINER_URL}/api/stacks/${STACK_ID}?endpointId=${ENDPOINT_ID}") # 3. 检查结果 # 成功时,Portainer 会返回更新后的 Stack 对象(包含 "Id" 字段) if echo "$UPDATE_RESPONSE" | grep -q '"Id":'; then echo "✅ 堆栈更新并重部署成功!" else echo "⚠️ 更新失败" echo "响应: ${UPDATE_RESPONSE}" fi fi else echo "⚠️ 未获取到 SHA256,跳过 Portainer 更新" fi } echo "✅ All done: ${IMAGE}"