name: Mirror releases to GitVerse, SourceCraft, and GitLab on: workflow_run: workflows: - Release types: - completed workflow_dispatch: inputs: tag: description: Git tag of the GitHub release to mirror (for example v0.7.1) required: true type: string permissions: contents: read jobs: mirror: runs-on: ubuntu-latest steps: - name: Resolve release tag id: release env: GH_TOKEN: ${{ github.token }} MANUAL_TAG: ${{ inputs.tag }} WORKFLOW_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} run: | set -euo pipefail tag="$MANUAL_TAG" if [ -z "$tag" ] && [[ "$WORKFLOW_HEAD_BRANCH" == v* ]]; then tag="$WORKFLOW_HEAD_BRANCH" fi if [ -z "$tag" ] && [ -n "$WORKFLOW_HEAD_SHA" ]; then releases=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \ -H 'Accept: application/vnd.github+json' \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases?per_page=100") while IFS= read -r candidate; do ref=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \ -H 'Accept: application/vnd.github+json' \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${candidate}") sha=$(jq -r '.object.sha' <<< "$ref") if [ "$(jq -r '.object.type' <<< "$ref")" = tag ]; then sha=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \ -H 'Accept: application/vnd.github+json' \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/git/tags/${sha}" | jq -r '.object.sha') fi if [ "$sha" = "$WORKFLOW_HEAD_SHA" ]; then tag="$candidate" break fi done < <(jq -r '.[].tag_name' <<< "$releases") fi if [ -z "$tag" ] || [[ "$tag" != v* ]]; then echo 'No release tag was found; release.yml may have failed before creating a GitHub release.' echo 'found=false' >> "$GITHUB_OUTPUT" exit 0 fi echo "tag=$tag" >> "$GITHUB_OUTPUT" echo 'found=true' >> "$GITHUB_OUTPUT" - name: Download GitHub release metadata and assets if: steps.release.outputs.found == 'true' env: GH_TOKEN: ${{ github.token }} TAG: ${{ steps.release.outputs.tag }} run: | set -euo pipefail mkdir -p assets curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \ -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 - 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 }} 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}" 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 "PRIVATE-TOKEN: $GITVERSE_PAT" \ "${api}/repos/${GITVERSE_ORG}/${GITVERSE_REPO}/releases/tags/${TAG}" 2>/dev/null)" = '404' ]; then curl -fsSL -X POST -H "$accept" -H "PRIVATE-TOKEN: $GITVERSE_PAT" \ -H 'Content-Type: application/json' --data "$payload" \ "${api}/repos/${GITVERSE_ORG}/${GITVERSE_REPO}/releases" > gitverse-release.json created=1 else curl -fsSL -H "$accept" -H "PRIVATE-TOKEN: $GITVERSE_PAT" \ "${api}/repos/${GITVERSE_ORG}/${GITVERSE_REPO}/releases/tags/${TAG}" > gitverse-release.json 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 "PRIVATE-TOKEN: $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" - 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 }} run: | set -euo pipefail test -n "$SOURCECRAFT_PAT" || { echo 'SOURCECRAFT_PAT is not configured' >&2; exit 1; } remote="https://anyname:${SOURCECRAFT_PAT}@git.sourcecraft.dev/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}.git" git clone "$remote" sourcecraft git -C sourcecraft remote add github "https://github.com/${GITHUB_REPOSITORY}.git" git -C sourcecraft fetch github "refs/tags/${TAG}:refs/tags/${TAG}" git -C sourcecraft push origin "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" \ '{tag:$tag,title:$title,release_notes:$notes,publish:true}') 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" || \ echo 'SourceCraft release already exists; continuing with assets.' for file in assets/*; do [ -f "$file" ] || continue 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 done echo "sourcecraft=ok" >> "$GITHUB_OUTPUT" - 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 }} run: | set -euo pipefail test -n "$GITLAB_PAT" || { echo 'GITLAB_PAT is not configured' >&2; exit 1; } project_id="${GITLAB_PROJECT//\//%2F}" api="https://gitlab.com/api/v4/projects/${project_id}" 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" \ '{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}" for file in assets/*; do [ -f "$file" ] || continue upload=$(curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITLAB_PAT" \ -F "file=@$file" "${api}/uploads") url="https://gitlab.com/${GITLAB_PROJECT}$(jq -r '.url' <<< "$upload")" 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" \ -H 'Content-Type: application/json' --data "$link" \ "${api}/releases/${TAG}/assets/links" || true done echo "gitlab=ok" >> "$GITHUB_OUTPUT"