From b6153dbaac46882e3ee12d206257c110917ffbe8 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Wed, 12 Aug 2026 20:13:30 +0800 Subject: [PATCH] fix release sync --- .github/workflows/release-mirror.yml | 209 +++++++++++++++++++-------- 1 file changed, 145 insertions(+), 64 deletions(-) diff --git a/.github/workflows/release-mirror.yml b/.github/workflows/release-mirror.yml index e039475..c487816 100644 --- a/.github/workflows/release-mirror.yml +++ b/.github/workflows/release-mirror.yml @@ -17,8 +17,11 @@ permissions: contents: read jobs: - mirror: + setup: runs-on: ubuntu-latest + outputs: + tag: ${{ steps.release.outputs.tag }} + found: ${{ steps.release.outputs.found }} steps: - name: Resolve release tag id: release @@ -73,65 +76,113 @@ jobs: -H 'Accept: application/vnd.github+json' \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" > release.json jq -e '.tag_name == $tag' --arg tag "$TAG" release.json >/dev/null - jq -r '.assets[] | [.name, .browser_download_url] | @tsv' release.json | - while IFS=$'\t' read -r name url; do - curl -fsSL -L -H "Authorization: Bearer $GH_TOKEN" \ - -H 'Accept: application/octet-stream' -o "assets/$name" "$url" - done + + total=$(jq '.assets | length' release.json) + downloaded=0 + failed=0 + while IFS=$' ' read -r name url; do + if curl -fsSL -L -H "Authorization: Bearer $GH_TOKEN" \ + -H 'Accept: application/octet-stream' -o "assets/$name" "$url"; then + downloaded=$((downloaded+1)) + else + echo "::warning::Failed to download asset: $name" + failed=$((failed+1)) + fi + done < <(jq -r '.assets[] | [.name, .browser_download_url] | @tsv' release.json) + + echo "Downloaded ${downloaded}/${total} assets (${failed} failed)" + [ "$failed" -eq 0 ] || { echo 'Not all release assets were downloaded.' >&2; exit 1; } + + - name: Upload release metadata artifact + if: steps.release.outputs.found == 'true' + uses: actions/upload-artifact@v4 + with: + name: release-mirror-data + path: | + release.json + assets/* + if-no-files-found: error + + mirror-gitverse: + needs: setup + if: needs.setup.outputs.found == 'true' + runs-on: ubuntu-latest + steps: + - name: Download release metadata + uses: actions/download-artifact@v4 + with: + name: release-mirror-data - name: Sync tag and release to GitVerse - if: steps.release.outputs.found == 'true' - continue-on-error: true env: GITVERSE_PAT: ${{ secrets.GITVERSE_PAT }} GITVERSE_ORG: ${{ vars.GITVERSE_ORG || 'dcr_labs' }} GITVERSE_REPO: ${{ vars.GITVERSE_REPO || 'dcr' }} - TAG: ${{ steps.release.outputs.tag }} + TAG: ${{ needs.setup.outputs.tag }} run: | set -euo pipefail test -n "$GITVERSE_PAT" || { echo 'GITVERSE_PAT is not configured' >&2; exit 1; } - api="https://api.gitverse.ru" - accept='Accept: application/vnd.gitverse.object+json;version=1' - remote="https://oauth2:${GITVERSE_PAT}@gitverse.ru/${GITVERSE_ORG}/${GITVERSE_REPO}.git" - git clone --mirror "https://github.com/${GITHUB_REPOSITORY}.git" mirror.git - git -C mirror.git remote add gitverse "$remote" - git -C mirror.git push gitverse "refs/tags/${TAG}:refs/tags/${TAG}" + project="${GITVERSE_ORG}%2F${GITVERSE_REPO}" + remote="https://oauth2:${GITVERSE_PAT}@gitverse.ru/${GITVERSE_ORG}/${GITVERSE_REPO}.git" + git clone --mirror "https://github.com/${GITHUB_REPOSITORY}.git" mirror.git + git -C mirror.git remote add gitverse "$remote" + git -C mirror.git push gitverse "refs/tags/${TAG}:refs/tags/${TAG}" - title=$(jq -r '.name // .tag_name' release.json) - notes=$(jq -r '.body // ""' release.json) - payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \ - '{name:$title, tag_name:$tag, body:$notes, draft:false, prerelease:false}') - created=0 - if [ "$(curl -sS -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $GITVERSE_PAT" \ - "${api}/repos/${GITVERSE_ORG}/${GITVERSE_REPO}/releases/tags/${TAG}" 2>/dev/null)" = '404' ]; then - curl -fsSL -X POST -H "$accept" -H "Authorization: Bearer $GITVERSE_PAT" \ - -H 'Content-Type: application/json' --data "$payload" \ - "${api}/repos/${GITVERSE_ORG}/${GITVERSE_REPO}/releases" > gitverse-release.json - created=1 + title=$(jq -r '.name // .tag_name' release.json) + notes=$(jq -r '.body // ""' release.json) + payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \ + '{name:$title, tag_name:$tag, description:$notes, ref:$tag}') + + http=$(curl -sS -o gitverse-release.json -w '%{http_code}' \ + -H "PRIVATE-TOKEN: $GITVERSE_PAT" \ + "https://gitverse.ru/api/v4/projects/${project}/releases/${TAG}" || true) + if [ "$http" = '200' ]; then + echo "GitVerse release ${TAG} already exists; skipping create." + else + curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \ + -H 'Content-Type: application/json' --data "$payload" \ + "https://gitverse.ru/api/v4/projects/${project}/releases" || \ + curl -fsSL -X PUT -H "PRIVATE-TOKEN: $GITVERSE_PAT" \ + -H 'Content-Type: application/json' --data "$payload" \ + "https://gitverse.ru/api/v4/projects/${project}/releases/${TAG}" + fi + + uploaded=0; failed=0 + for file in assets/*; do + [ -f "$file" ] || continue + upload=$(curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \ + -F "file=@$file" "https://gitverse.ru/api/v4/projects/${project}/uploads") + url=$(jq -r '.url // .file_path' <<< "$upload") + name=$(basename "$file") + link=$(jq -n --arg name "$name" --arg url "$url" '{name:$name,url:$url}') + if curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \ + -H 'Content-Type: application/json' --data "$link" \ + "https://gitverse.ru/api/v4/projects/${project}/releases/${TAG}/assets/links" >/dev/null 2>&1; then + uploaded=$((uploaded+1)) else - curl -fsSL -H "$accept" -H "Authorization: Bearer $GITVERSE_PAT" \ - "${api}/repos/${GITVERSE_ORG}/${GITVERSE_REPO}/releases/tags/${TAG}" > gitverse-release.json + echo "::warning::GitVerse: failed to attach asset $name" + failed=$((failed+1)) fi - for file in assets/*; do - [ -f "$file" ] || continue - # Upload replaces an existing asset with the same name. - curl -fsSL -X POST -H "$accept" -H "Authorization: Bearer $GITVERSE_PAT" \ - -F "name=$(basename "$file")" -F "attachment=@$file" \ - "${api}/repos/${GITVERSE_ORG}/${GITVERSE_REPO}/releases/$(jq -r '.id' gitverse-release.json)/assets" \ - >/dev/null - done - echo "gitverse=ok" >> "$GITHUB_OUTPUT" - + done + echo "GitVerse: uploaded ${uploaded}, failed ${failed}" + [ "$failed" -eq 0 ] || exit 1 + mirror-sourcecraft: + needs: setup + if: needs.setup.outputs.found == 'true' + runs-on: ubuntu-latest + steps: + - name: Download release metadata + uses: actions/download-artifact@v4 + with: + name: release-mirror-data - name: Sync tag and release to SourceCraft - if: steps.release.outputs.found == 'true' - continue-on-error: true env: SOURCECRAFT_PAT: ${{ secrets.SOURCECRAFT_PAT }} SOURCECRAFT_ORG: ${{ vars.SOURCECRAFT_ORG || 'dexoron-dev' }} SOURCECRAFT_REPO: ${{ vars.SOURCECRAFT_REPO || 'dcr' }} - TAG: ${{ steps.release.outputs.tag }} + TAG: ${{ needs.setup.outputs.tag }} run: | set -euo pipefail test -n "$SOURCECRAFT_PAT" || { echo 'SOURCECRAFT_PAT is not configured' >&2; exit 1; } @@ -145,31 +196,51 @@ jobs: notes=$(jq -r '.body // ""' release.json) payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \ '{tag:$tag,title:$title,release_notes:$notes,publish:true}') + base="https://api.sourcecraft.tech/repos/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}" - # Force update release + attach all assets - curl -fsSL -X POST -H "Authorization: Bearer $SOURCECRAFT_PAT" \ - -H 'Content-Type: application/json' --data "$payload" \ - "https://api.sourcecraft.tech/repos/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}/releases" || \ - curl -fsSL -X PUT -H "Authorization: Bearer $SOURCECRAFT_PAT" \ - -H 'Content-Type: application/json' --data "$payload" \ - "https://api.sourcecraft.tech/repos/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}/releases/${TAG}" + http=$(curl -sS -o /dev/null -w '%{http_code}' \ + -H "Authorization: Bearer $SOURCECRAFT_PAT" "${base}/releases/tag/${TAG}" || true) + if [ "$http" != '200' ]; then + # Try create, then update (idempotent). + curl -fsSL -X POST -H "Authorization: Bearer $SOURCECRAFT_PAT" \ + -H 'Content-Type: application/json' --data "$payload" "${base}/releases" || \ + curl -fsSL -X PUT -H "Authorization: Bearer $SOURCECRAFT_PAT" \ + -H 'Content-Type: application/json' --data "$payload" "${base}/releases/${TAG}" || \ + echo 'SourceCraft release already exists; continuing with assets.' + else + echo "SourceCraft release ${TAG} already exists; continuing with assets." + fi + uploaded=0; failed=0 for file in assets/*; do [ -f "$file" ] || continue - curl -fsSL -X POST -H "Authorization: Bearer $SOURCECRAFT_PAT" \ + if curl -fsSL -X POST -H "Authorization: Bearer $SOURCECRAFT_PAT" \ -F "name=$(basename "$file")" -F "file=@$file" \ - "https://api.sourcecraft.tech/repos/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}/releases/tag/${TAG}/attachments" || true + "${base}/releases/tag/${TAG}/attachments" >/dev/null 2>&1; then + uploaded=$((uploaded+1)) + else + echo "::warning::SourceCraft: failed to attach asset $(basename "$file")" + failed=$((failed+1)) + fi done + echo "SourceCraft: uploaded ${uploaded}, failed ${failed}" + [ "$failed" -eq 0 ] || exit 1 - echo "sourcecraft=ok" >> "$GITHUB_OUTPUT" + mirror-gitlab: + needs: setup + if: needs.setup.outputs.found == 'true' + runs-on: ubuntu-latest + steps: + - name: Download release metadata + uses: actions/download-artifact@v4 + with: + name: release-mirror-data - name: Sync release to GitLab - if: steps.release.outputs.found == 'true' - continue-on-error: true env: GITLAB_PAT: ${{ secrets.GITLAB_PAT }} GITLAB_PROJECT: ${{ vars.GITLAB_PROJECT || 'dexoron-dev/dcr-tool/dcr' }} - TAG: ${{ steps.release.outputs.tag }} + TAG: ${{ needs.setup.outputs.tag }} run: | set -euo pipefail test -n "$GITLAB_PAT" || { echo 'GITLAB_PAT is not configured' >&2; exit 1; } @@ -180,14 +251,18 @@ jobs: payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \ '{tag_name:$tag, name:$title, description:$notes}') - # Tags are already mirrored by GitLab's built-in repository mirror. - curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITLAB_PAT" \ - -H 'Content-Type: application/json' --data "$payload" \ - "${api}/releases" || \ - curl -fsSL -X PUT -H "PRIVATE-TOKEN: $GITLAB_PAT" \ - -H 'Content-Type: application/json' --data "$payload" \ - "${api}/releases/${TAG}" + http=$(curl -sS -o /dev/null -w '%{http_code}' \ + -H "PRIVATE-TOKEN: $GITLAB_PAT" "${api}/releases/${TAG}" || true) + if [ "$http" != '200' ]; then + curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITLAB_PAT" \ + -H 'Content-Type: application/json' --data "$payload" "${api}/releases" || \ + curl -fsSL -X PUT -H "PRIVATE-TOKEN: $GITLAB_PAT" \ + -H 'Content-Type: application/json' --data "$payload" "${api}/releases/${TAG}" + else + echo "GitLab release ${TAG} already exists; continuing with assets." + fi + uploaded=0; failed=0 for file in assets/*; do [ -f "$file" ] || continue upload=$(curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITLAB_PAT" \ @@ -196,8 +271,14 @@ jobs: name=$(basename "$file") link=$(jq -n --arg name "$name" --arg url "$url" \ '{name:$name, url:$url, link_type:"other"}') - curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITLAB_PAT" \ + if curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITLAB_PAT" \ -H 'Content-Type: application/json' --data "$link" \ - "${api}/releases/${TAG}/assets/links" || true + "${api}/releases/${TAG}/assets/links" >/dev/null 2>&1; then + uploaded=$((uploaded+1)) + else + echo "::warning::GitLab: failed to attach asset $name" + failed=$((failed+1)) + fi done - echo "gitlab=ok" >> "$GITHUB_OUTPUT" + echo "GitLab: uploaded ${uploaded}, failed ${failed}" + [ "$failed" -eq 0 ] || exit 1 \ No newline at end of file