name: mirror-to-gitee # 把本仓(开源项目门户站)镜像到 Gitee 企业仓 /<同名仓库>。 # 触发:main 分支推送、v* tag 推送、手动;另有每日兜底重试。 on: push: branches: - main tags: - 'v*' schedule: # Gitea Actions 的 cron 用 UTC:18:30 UTC = 次日 02:30 北京时间 - cron: '30 18 * * *' workflow_dispatch: concurrency: group: mirror-${{ github.ref }} cancel-in-progress: true jobs: mirror: runs-on: host steps: - name: 同步到 Gitee env: SRC_TOKEN: ${{ secrets.SRC_GIT_TOKEN }} DST_TOKEN: ${{ secrets.GITEE_TOKEN }} DST_OWNER: ${{ secrets.GITEE_OWNER }} DST_USER: ${{ secrets.GITEE_USER }} run: | set -u # 仓库名取当前仓。取不到就直接失败,不要回退到别的仓名 # (回退会把本仓的内容推进另一个仓,是危险的默认值)。 REPO_NAME="${GITHUB_REPOSITORY#*/}" if [ -z "${REPO_NAME:-}" ] || [ "$REPO_NAME" = "${GITHUB_REPOSITORY:-}" ]; then echo "❌ 取不到仓库名(GITHUB_REPOSITORY=${GITHUB_REPOSITORY:-<空>}),中止" exit 1 fi SRC="git.5iot.com/iotlcm-public/${REPO_NAME}.git" DST="gitee.com/${DST_OWNER}/${REPO_NAME}.git" echo "源:$SRC" echo "目标:$DST" # Gitee 不支持 push 建仓,目标仓不存在时先按企业 API 创建(已存在会报 400,忽略) if ! git ls-remote "https://${DST_USER}:${DST_TOKEN}@${DST}" >/dev/null 2>&1; then echo "目标仓不存在,尝试创建 ${DST_OWNER}/${REPO_NAME}" curl -sS -X POST \ "https://gitee.com/api/v5/enterprises/${DST_OWNER}/repos?access_token=${DST_TOKEN}" \ -H 'Content-Type: application/json' \ -d "{\"name\":\"${REPO_NAME}\",\"auto_init\":false,\"private\":false,\"description\":\"雪羊科技开源项目门户:iotLCM / 秀杰 DTU / XiuAdmin 等开源项目索引\"}" \ -o /tmp/repo-create.json -w '建仓 HTTP %{http_code}\n' || true head -c 300 /tmp/repo-create.json 2>/dev/null; echo else echo "目标仓已存在" fi WORK=$(mktemp -d) trap 'rm -rf "$WORK"' EXIT git clone --mirror "https://jenkins:${SRC_TOKEN}@git.5iot.com/iotlcm-public/${REPO_NAME}.git" "$WORK/repo.git" cd "$WORK/repo.git" echo "本地 ref:$(git for-each-ref --format='%(refname:short)' | tr '\n' ' ')" # Gitee 在推送密集时会返回 429 Pending queue overflowed / 408 Authorization server timeout, # 属服务端限流,退避重试即可。 attempt=0 while [ "$attempt" -lt 5 ]; do attempt=$((attempt + 1)) echo "----- 推送尝试 $attempt/5" if git push --mirror "https://${DST_USER}:${DST_TOKEN}@${DST}" 2>&1; then echo "✅ 已同步到 Gitee" # 推送后校验:远端 main 与 tag 集合必须与本地一致 # (避免“绿了但其实没推上去” —— Gitee 限流时容易发生) # # 每条赋值都带 `|| true`:命令替换里任何一段返回非零(新建仓没有 tag 时 # `grep -v` 会返回 1)在 set -e 下会直接中断脚本, # 导致「推送其实成功了,但 job 报红」这种假失败。 LOCAL_MAIN=$(git rev-parse refs/heads/main 2>/dev/null || true) REMOTE_MAIN=$(git ls-remote "https://${DST_USER}:${DST_TOKEN}@${DST}" refs/heads/main 2>/dev/null | awk '{print $1}' || true) LOCAL_TAGS=$(git for-each-ref --format='%(refname:short)' refs/tags 2>/dev/null | sort | tr '\n' ' ' || true) REMOTE_TAGS=$(git ls-remote --tags "https://${DST_USER}:${DST_TOKEN}@${DST}" 2>/dev/null \ | awk '{print $2}' | sed 's#refs/tags/##' | { grep -v '\^{}$' || true; } | sort | tr '\n' ' ' || true) echo "本地 main: ${LOCAL_MAIN:-<无>}" echo "远端 main: ${REMOTE_MAIN:-<无>}" echo "本地 tag: ${LOCAL_TAGS:-<无>}" echo "远端 tag: ${REMOTE_TAGS:-<无>}" if [ -n "$LOCAL_MAIN" ] && [ -n "$REMOTE_MAIN" ] && [ "$LOCAL_MAIN" != "$REMOTE_MAIN" ]; then echo "❌ 校验失败:远端 main 与本地不一致" exit 1 fi if [ "${LOCAL_TAGS% }" != "${REMOTE_TAGS% }" ]; then echo "❌ 校验失败:远端 tag 与本地不一致" exit 1 fi echo "✅ 校验通过:Gitee 端 main 与 tag 均已对齐" # 目标仓可见性:企业仓建仓 API 忽略 public/private 字段, # 新建的仓一律是私有的;改可见性需要企业仓管理员权限, # 本令牌(普通成员令牌)会被拒(401「修改仓库公开属性失败,您没有此项权限」)。 # 因此这里只做「尽力而为 + 明确提示」,失败不算 job 失败。 echo "----- 尝试把目标仓可见性校正为公开" HTTP=$(curl -sS -m 25 -X PATCH \ "https://gitee.com/api/v5/repos/${DST_OWNER}/${REPO_NAME}?access_token=${DST_TOKEN}" \ -H 'Content-Type: application/json' \ -d "{\"name\":\"${REPO_NAME}\",\"private\":false,\"internal\":false,\"description\":\"雪羊科技开源项目门户:iotLCM / 秀杰 DTU / XiuAdmin 等开源项目索引\"}" \ -o /tmp/repo-patch.json -w '%{http_code}' || echo 000) echo "改可见性 HTTP ${HTTP}" head -c 300 /tmp/repo-patch.json 2>/dev/null; echo case "$HTTP" in 2*) echo "✅ 目标仓已为公开" ;; *) echo "ℹ️ 目标仓当前为私有。需要用企业仓管理员账号在 Gitee 后台把 ${DST_OWNER}/${REPO_NAME} 改为「公开」;代码已同步,不影响镜像内容。" ;; esac exit 0 fi wait_s=$((attempt * 30)) echo "推送失败,${wait_s}s 后重试" sleep "$wait_s" done echo "❌ 连续 5 次推送失败,请检查 Gitee 状态或令牌" exit 1