#!/usr/bin/env bash # Copyright 2026 Alibaba Group # Licensed under the Apache License, Version 2.0 # # Mirror release artifacts to a Gitee release so China users can install without # hitting GitHub. The repo code itself is kept in sync by Gitee's repo-mirror # feature; this script handles what the mirror does NOT carry — the GitHub # Release *attachments* (binaries, checksums, skills zip) — by uploading them to # the matching Gitee release via the Gitee OpenAPI v5. # # Consumed by install.sh when DWS_GITEE_REPO is set (it resolves each asset's # real download_url from the Gitee API, since Gitee attachment URLs carry an # unstable numeric id). # # Required environment (CI secrets): # GITEE_TOKEN Gitee private access token (scopes: projects) # GITEE_USER Gitee username for git push authentication # GITEE_REPO "owner/repo" on Gitee, e.g. DingTalk-Real-AI/dingtalk-workspace-cli # Optional: # VERSION release tag (default: git describe) # DIST_DIR artifacts dir (default: ./dist) # GITEE_API API base (default: https://gitee.com/api/v5) # # Gating: if GITEE_TOKEN / GITEE_USER / GITEE_REPO are unset, exit 0 with a # notice so the step can live in release.yml without breaking forks. set -eu SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" DIST_DIR="${DIST_DIR:-dist}" GITEE_API="${GITEE_API:-https://gitee.com/api/v5}" GITEE_CURL_CONNECT_TIMEOUT="${GITEE_CURL_CONNECT_TIMEOUT:-15}" GITEE_CURL_MAX_TIME="${GITEE_CURL_MAX_TIME:-120}" GITEE_SYNC_TIMEOUT_SECONDS="${GITEE_SYNC_TIMEOUT_SECONDS:-5700}" GITEE_TAG_TIMEOUT_SECONDS="${GITEE_TAG_TIMEOUT_SECONDS:-300}" GITEE_RELEASE_LOOKUP_MAX_TIME="${GITEE_RELEASE_LOOKUP_MAX_TIME:-60}" GITEE_RELEASE_CREATE_MAX_TIME="${GITEE_RELEASE_CREATE_MAX_TIME:-60}" GITEE_RECONCILE_TIMEOUT_SECONDS="${GITEE_RECONCILE_TIMEOUT_SECONDS:-4920}" GITEE_CHILD_DEADLINE_RESERVE_SECONDS="${GITEE_CHILD_DEADLINE_RESERVE_SECONDS:-5}" err() { printf 'error: %s\n' "$*" >&2 exit 1 } require_positive_integer() { local name="$1" value="$2" case "$value" in ''|*[!0-9]*) err "${name} must be a positive integer: ${value}" ;; esac [ "$value" -gt 0 ] || err "${name} must be greater than zero" } for setting in \ GITEE_CURL_CONNECT_TIMEOUT \ GITEE_CURL_MAX_TIME \ GITEE_SYNC_TIMEOUT_SECONDS \ GITEE_TAG_TIMEOUT_SECONDS \ GITEE_RELEASE_LOOKUP_MAX_TIME \ GITEE_RELEASE_CREATE_MAX_TIME \ GITEE_RECONCILE_TIMEOUT_SECONDS \ GITEE_CHILD_DEADLINE_RESERVE_SECONDS; do require_positive_integer "$setting" "${!setting}" done missing="" [ -z "${GITEE_TOKEN:-}" ] && missing="$missing GITEE_TOKEN" [ -z "${GITEE_USER:-}" ] && missing="$missing GITEE_USER" [ -z "${GITEE_REPO:-}" ] && missing="$missing GITEE_REPO" if [ -n "$missing" ]; then echo "ℹ️ Gitee mirror sync skipped — missing:${missing}" echo " Set these as repo secrets to auto-mirror releases to Gitee for China users." exit 0 fi now_seconds() { date +%s } sync_deadline=$(( $(now_seconds) + GITEE_SYNC_TIMEOUT_SECONDS )) deadline_remaining() { local remaining=$(( sync_deadline - $(now_seconds) )) [ "$remaining" -gt 0 ] || return 1 printf '%s\n' "$remaining" } bounded_max_time() { local configured="$1" remaining remaining="$(deadline_remaining)" || return 1 if [ "$configured" -lt "$remaining" ]; then printf '%s\n' "$configured" else printf '%s\n' "$remaining" fi } VERSION="${VERSION:-$(git describe --tags --always 2>/dev/null || echo dev)}" OWNER="${GITEE_REPO%%/*}" NAME="${GITEE_REPO##*/}" base="${GITEE_API}/repos/${OWNER}/${NAME}" echo "📦 Mirroring release ${VERSION} → Gitee ${GITEE_REPO}" # ── Keep the Gitee git tag aligned with the GitHub release tag ─────────────── # Creating a Gitee release with target_commitish=main can silently create a tag # at the Gitee-localized main commit. The helper skips an already-aligned tag, # pushes only a missing tag, and refuses to move a published tag. VERSION="$VERSION" \ GITEE_PARENT_DEADLINE_EPOCH="$sync_deadline" \ GITEE_TAG_TIMEOUT_SECONDS="$GITEE_TAG_TIMEOUT_SECONDS" \ "$SCRIPT_DIR/sync-gitee-tag.sh" deadline_remaining >/dev/null || err "overall Gitee sync deadline exhausted during tag synchronization" target_commit="$(git rev-parse --verify "${VERSION}^{commit}")" api_get() { local configured_max_time="$1" max_time shift max_time="$(bounded_max_time "$configured_max_time")" || return 1 curl -fsSL --connect-timeout "$GITEE_CURL_CONNECT_TIMEOUT" \ --max-time "$max_time" "$@" } # ── Resolve or create the Gitee release for this tag ────────────────────────── rel_json="$(api_get "$GITEE_RELEASE_LOOKUP_MAX_TIME" \ "${base}/releases/tags/${VERSION}?access_token=${GITEE_TOKEN}" 2>/dev/null || true)" release_id="$(printf '%s' "$rel_json" | grep -o '"id":[ ]*[0-9]*' | head -1 | grep -o '[0-9]*' || true)" if [ -z "$release_id" ]; then deadline_remaining >/dev/null || err "overall Gitee sync deadline exhausted during release lookup" echo " No Gitee release for ${VERSION} yet — creating it." create_max_time="$(bounded_max_time "$GITEE_RELEASE_CREATE_MAX_TIME")" \ || err "overall Gitee sync deadline exhausted before release creation" rel_json="$(curl -fsSL --connect-timeout "$GITEE_CURL_CONNECT_TIMEOUT" \ --max-time "$create_max_time" -X POST "${base}/releases" \ -F "access_token=${GITEE_TOKEN}" \ -F "tag_name=${VERSION}" \ -F "name=${VERSION}" \ -F "body=Mirror of GitHub release ${VERSION} for China users." \ -F "target_commitish=${target_commit}" 2>/dev/null || true)" release_id="$(printf '%s' "$rel_json" | grep -o '"id":[ ]*[0-9]*' | head -1 | grep -o '[0-9]*' || true)" fi [ -n "$release_id" ] || { echo "❌ Could not get/create Gitee release for ${VERSION}. Response: ${rel_json}" >&2; exit 1; } echo " Gitee release id = ${release_id}" # ── Reconcile the complete, byte-verified release asset set ────────────────── remaining="$(deadline_remaining)" || err "overall Gitee sync deadline exhausted before asset reconciliation" reconcile_timeout="$GITEE_RECONCILE_TIMEOUT_SECONDS" max_child_timeout=$(( remaining - GITEE_CHILD_DEADLINE_RESERVE_SECONDS )) [ "$max_child_timeout" -gt 0 ] || err "insufficient Gitee sync budget for asset reconciliation" if [ "$reconcile_timeout" -gt "$max_child_timeout" ]; then reconcile_timeout="$max_child_timeout" fi DIST_DIR="$DIST_DIR" \ GITEE_API="$GITEE_API" \ GITEE_TOKEN="$GITEE_TOKEN" \ GITEE_REPO="$GITEE_REPO" \ GITEE_CURL_CONNECT_TIMEOUT="$GITEE_CURL_CONNECT_TIMEOUT" \ GITEE_CURL_MAX_TIME="$GITEE_CURL_MAX_TIME" \ GITEE_RELEASE_ID="$release_id" \ GITEE_OVERALL_TIMEOUT_SECONDS="$reconcile_timeout" \ "$SCRIPT_DIR/reconcile-gitee-assets.sh" deadline_remaining >/dev/null || err "overall Gitee sync deadline exhausted after asset reconciliation" echo " China install: DWS_GITEE_REPO=${GITEE_REPO} \\" echo " curl -fsSL https://gitee.com/${GITEE_REPO}/raw/main/scripts/install.sh | sh"