Recover v0.3.2 from multiple artifact runs #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Recover v0.3.2 Release From Artifacts | |
| run-name: Recover ${{ inputs.tag_name }} from multiple artifact runs | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| tag_name: | |
| description: "Release tag to publish" | |
| required: true | |
| default: v0.3.2 | |
| source_ref: | |
| description: "Branch containing the release commit" | |
| required: true | |
| default: v0.3.2 | |
| source_sha: | |
| description: "Exact release commit SHA" | |
| required: true | |
| default: 6914c6ec9067054d5084f4e8a6e12a0851deabe0 | |
| primary_run_id: | |
| description: "First run to try for every artifact" | |
| required: true | |
| default: "27012650429" | |
| fallback_run_ids: | |
| description: "Fallback run IDs, space or newline separated" | |
| required: true | |
| default: "27010245377 26995768946" | |
| publish_docker: | |
| description: "Build and publish Docker images from recovered Linux packages" | |
| required: true | |
| default: true | |
| type: boolean | |
| publish_release: | |
| description: "Create or update the GitHub release" | |
| required: true | |
| default: true | |
| type: boolean | |
| permissions: | |
| actions: read | |
| contents: write | |
| packages: write | |
| concurrency: | |
| group: recover-release-${{ inputs.tag_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| recover-assets: | |
| name: Recover release assets | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| source_sha: ${{ steps.meta.outputs.source_sha }} | |
| image_repo: ${{ steps.meta.outputs.image_repo }} | |
| package_url: ${{ steps.meta.outputs.package_url }} | |
| steps: | |
| - name: Clone source | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ inputs.source_sha }} | |
| fetch-depth: 0 | |
| - name: Verify source and artifact runs | |
| id: meta | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG_NAME: ${{ inputs.tag_name }} | |
| SOURCE_REF: ${{ inputs.source_ref }} | |
| SOURCE_SHA: ${{ inputs.source_sha }} | |
| PRIMARY_RUN_ID: ${{ inputs.primary_run_id }} | |
| FALLBACK_RUN_IDS: ${{ inputs.fallback_run_ids }} | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "${TAG_NAME}" =~ ^v[0-9] ]]; then | |
| echo "tag_name must be a v* release tag, got ${TAG_NAME}" >&2 | |
| exit 1 | |
| fi | |
| if [[ -z "${SOURCE_SHA}" || ! "${SOURCE_SHA}" =~ ^[0-9a-f]{40}$ ]]; then | |
| echo "source_sha must be a full 40-character commit SHA." >&2 | |
| exit 1 | |
| fi | |
| git fetch origin +refs/heads/"${SOURCE_REF}":refs/remotes/origin/"${SOURCE_REF}" --no-tags | |
| branch_sha="$(git rev-parse refs/remotes/origin/"${SOURCE_REF}")" | |
| if [[ "${branch_sha}" != "${SOURCE_SHA}" ]]; then | |
| echo "${SOURCE_REF} points to ${branch_sha}, not requested source ${SOURCE_SHA}." >&2 | |
| exit 1 | |
| fi | |
| fallback_text="${FALLBACK_RUN_IDS//$'\n'/ }" | |
| read -r -a fallback_ids <<< "${fallback_text}" | |
| run_ids=("${PRIMARY_RUN_ID}" "${fallback_ids[@]}") | |
| mkdir -p recovery | |
| : > recovery/run-provenance.tsv | |
| for run_id in "${run_ids[@]}"; do | |
| [[ -z "${run_id}" ]] && continue | |
| run_json="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}")" | |
| run_status="$(jq -r '.status' <<< "${run_json}")" | |
| run_head_sha="$(jq -r '.head_sha' <<< "${run_json}")" | |
| run_url="$(jq -r '.html_url' <<< "${run_json}")" | |
| if [[ "${run_status}" != "completed" ]]; then | |
| echo "Run ${run_id} is ${run_status}, not completed: ${run_url}" >&2 | |
| exit 1 | |
| fi | |
| if ! git cat-file -e "${run_head_sha}^{commit}" 2>/dev/null; then | |
| git fetch origin "${run_head_sha}" --no-tags | |
| fi | |
| if ! git merge-base --is-ancestor "${run_head_sha}" "${SOURCE_SHA}"; then | |
| echo "Run ${run_id} built ${run_head_sha}, which is not an ancestor of ${SOURCE_SHA}: ${run_url}" >&2 | |
| exit 1 | |
| fi | |
| printf '%s\t%s\t%s\n' "${run_id}" "${run_head_sha}" "${run_url}" >> recovery/run-provenance.tsv | |
| done | |
| owner="$(printf '%s' "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')" | |
| repo="$(printf '%s' "${GITHUB_REPOSITORY#*/}" | tr '[:upper:]' '[:lower:]')" | |
| image_repo="ghcr.io/${owner}/${repo}" | |
| package_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pkgs/container/${repo}" | |
| echo "source_sha=${SOURCE_SHA}" >> "${GITHUB_OUTPUT}" | |
| echo "image_repo=${image_repo}" >> "${GITHUB_OUTPUT}" | |
| echo "package_url=${package_url}" >> "${GITHUB_OUTPUT}" | |
| echo "Artifact run provenance:" | |
| column -t -s $'\t' recovery/run-provenance.tsv | |
| - name: Download recovered package artifacts | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG_NAME: ${{ inputs.tag_name }} | |
| PRIMARY_RUN_ID: ${{ inputs.primary_run_id }} | |
| FALLBACK_RUN_IDS: ${{ inputs.fallback_run_ids }} | |
| run: | | |
| set -euo pipefail | |
| fallback_text="${FALLBACK_RUN_IDS//$'\n'/ }" | |
| read -r -a fallback_ids <<< "${fallback_text}" | |
| run_ids=("${PRIMARY_RUN_ID}" "${fallback_ids[@]}") | |
| required=( | |
| "beellama-bin-macos-arm64.tar.gz|beellama-${TAG_NAME}-bin-macos-arm64.tar.gz" | |
| "beellama-bin-ubuntu-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-x64.tar.gz" | |
| "beellama-bin-ubuntu-arm64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-arm64.tar.gz" | |
| "beellama-bin-ubuntu-cuda-12.4-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-cuda-12.4-x64.tar.gz" | |
| "beellama-bin-ubuntu-cuda-13.1-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-cuda-13.1-x64.tar.gz" | |
| "beellama-bin-ubuntu-vulkan-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-vulkan-x64.tar.gz" | |
| "beellama-bin-ubuntu-rocm-7.2-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-rocm-7.2-x64.tar.gz" | |
| "beellama-bin-ubuntu-sycl-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-sycl-x64.tar.gz" | |
| "beellama-bin-win-cpu-x64.zip|beellama-bin-win-cpu-x64.zip" | |
| "beellama-bin-win-sycl-x64.zip|beellama-bin-win-sycl-x64.zip" | |
| "beellama-bin-win-cuda-12.4-x64.zip|beellama-bin-win-cuda-12.4-x64.zip" | |
| "beellama-bin-win-cuda-13.1-x64.zip|beellama-bin-win-cuda-13.1-x64.zip" | |
| "beellama-bin-win-hip-radeon-x64.zip|beellama-bin-win-hip-radeon-x64.zip" | |
| "cudart-beellama-bin-win-cuda-12.4-x64.zip|cudart-beellama-bin-win-cuda-12.4-x64.zip" | |
| "cudart-beellama-bin-win-cuda-13.1-x64.zip|cudart-beellama-bin-win-cuda-13.1-x64.zip" | |
| ) | |
| mkdir -p artifact artifact-download recovery | |
| : > recovery/artifact-provenance.tsv | |
| for item in "${required[@]}"; do | |
| artifact_name="${item%%|*}" | |
| expected_file="${item#*|}" | |
| if [[ -f "artifact/${expected_file}" ]]; then | |
| continue | |
| fi | |
| found="false" | |
| for run_id in "${run_ids[@]}"; do | |
| [[ -z "${run_id}" ]] && continue | |
| download_dir="artifact-download/${run_id}/${artifact_name}" | |
| rm -rf "${download_dir}" | |
| mkdir -p "${download_dir}" | |
| if ! gh run download "${run_id}" --repo "${GITHUB_REPOSITORY}" --name "${artifact_name}" --dir "${download_dir}" >/dev/null 2>&1; then | |
| echo "Artifact ${artifact_name} not found in run ${run_id}." | |
| continue | |
| fi | |
| artifact_file="$( | |
| find "${download_dir}" -type f \( -name "${expected_file}" -o -name "${artifact_name}" \) | head -n 1 | |
| )" | |
| if [[ -z "${artifact_file}" ]]; then | |
| echo "Run ${run_id} artifact ${artifact_name} did not contain ${expected_file}." | |
| continue | |
| fi | |
| cp "${artifact_file}" "artifact/${expected_file}" | |
| run_head_sha="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}" --jq '.head_sha')" | |
| printf '%s\t%s\t%s\t%s\n' "${expected_file}" "${artifact_name}" "${run_id}" "${run_head_sha}" >> recovery/artifact-provenance.tsv | |
| found="true" | |
| break | |
| done | |
| if [[ "${found}" != "true" ]]; then | |
| echo "Missing required artifact ${artifact_name} (${expected_file}) in all configured runs." >&2 | |
| exit 1 | |
| fi | |
| done | |
| echo "Recovered artifact provenance:" | |
| column -t -s $'\t' recovery/artifact-provenance.tsv | |
| ls -lh artifact | |
| - name: Prepare release assets | |
| shell: bash | |
| env: | |
| TAG_NAME: ${{ inputs.tag_name }} | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| mkdir -p release | |
| cpu_zip="artifact/beellama-bin-win-cpu-x64.zip" | |
| temp_dir="$(mktemp -d)" | |
| unzip -q "${cpu_zip}" -d "${temp_dir}" | |
| for target_zip in artifact/beellama-bin-win-*-x64.zip; do | |
| if [[ "$(basename "${target_zip}")" == "beellama-bin-win-cpu-x64.zip" ]]; then | |
| continue | |
| fi | |
| realpath_target_zip="$(realpath "${target_zip}")" | |
| (cd "${temp_dir}" && zip -qr "${realpath_target_zip}" .) | |
| done | |
| rm -rf "${temp_dir}" | |
| for zip_file in artifact/beellama-bin-win-*.zip; do | |
| base_name="$(basename "${zip_file}" .zip)" | |
| mv "${zip_file}" "release/beellama-${TAG_NAME}-${base_name#beellama-}.zip" | |
| done | |
| for zip_file in artifact/cudart-beellama-bin-win-cuda-*.zip; do | |
| base_name="$(basename "${zip_file}" .zip)" | |
| suffix="${base_name#cudart-beellama-bin-win-}" | |
| mv "${zip_file}" "release/beellama-${TAG_NAME}-cudart-win-${suffix}.zip" | |
| done | |
| for tar_file in artifact/*.tar.gz; do | |
| mv "${tar_file}" release/ | |
| done | |
| python3 scripts/verify-windows-package.py release/beellama-${TAG_NAME}-bin-win-*.zip | |
| for cuda_version in 12.4 13.1; do | |
| package_zip="release/beellama-${TAG_NAME}-bin-win-cuda-${cuda_version}-x64.zip" | |
| runtime_zip="release/beellama-${TAG_NAME}-cudart-win-cuda-${cuda_version}-x64.zip" | |
| temp_dir="$(mktemp -d)" | |
| unzip -q "${package_zip}" -d "${temp_dir}" | |
| unzip -q "${runtime_zip}" -d "${temp_dir}" | |
| python3 scripts/verify-windows-package.py "${temp_dir}" | |
| rm -rf "${temp_dir}" | |
| done | |
| sha256sum release/* > release/SHA256SUMS.txt | |
| ls -lh release | |
| - name: Write release notes | |
| shell: bash | |
| env: | |
| TAG_NAME: ${{ inputs.tag_name }} | |
| SOURCE_SHA: ${{ inputs.source_sha }} | |
| IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} | |
| PACKAGE_URL: ${{ steps.meta.outputs.package_url }} | |
| run: | | |
| set -euo pipefail | |
| release_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/download/${TAG_NAME}" | |
| changelog_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${SOURCE_SHA}/CHANGELOG.md" | |
| python3 scripts/extract-changelog.py \ | |
| --tag "${TAG_NAME}" \ | |
| --changelog CHANGELOG.md \ | |
| --fallback-url "${changelog_url}" \ | |
| --output changelog-fragment.md | |
| python3 scripts/write-release-notes.py \ | |
| --tag "${TAG_NAME}" \ | |
| --release-url "${release_url}" \ | |
| --image-repo "${IMAGE_REPO}" \ | |
| --package-url "${PACKAGE_URL}" \ | |
| --changelog changelog-fragment.md \ | |
| --output release-notes.md | |
| - name: Upload recovered source artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: beellama-recovered-source-artifacts-${{ inputs.tag_name }} | |
| path: recovery/artifact-provenance.tsv | |
| - name: Upload recovered release bundle | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: beellama-recovered-release-${{ inputs.tag_name }} | |
| path: | | |
| release/* | |
| release-notes.md | |
| recovery/*.tsv | |
| docker-build: | |
| name: Docker ${{ matrix.config.display }} | |
| if: ${{ inputs.publish_docker }} | |
| needs: recover-assets | |
| runs-on: ${{ matrix.config.runs_on }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| config: | |
| - name: cpu-amd64 | |
| display: CPU amd64 | |
| group: cpu | |
| arch: amd64 | |
| platform: linux/amd64 | |
| artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-x64.tar.gz | |
| dockerfile: .devops/runtime-server.Dockerfile | |
| base_image: ubuntu:24.04 | |
| runtime_packages: ca-certificates curl libgomp1 | |
| runs_on: ubuntu-24.04 | |
| - name: cpu-arm64 | |
| display: CPU arm64 | |
| group: cpu | |
| arch: arm64 | |
| platform: linux/arm64 | |
| artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-arm64.tar.gz | |
| dockerfile: .devops/runtime-server.Dockerfile | |
| base_image: ubuntu:24.04 | |
| runtime_packages: ca-certificates curl libgomp1 | |
| runs_on: ubuntu-24.04-arm | |
| - name: cuda12 | |
| display: CUDA 12.4 | |
| group: cuda12 | |
| arch: amd64 | |
| platform: linux/amd64 | |
| artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-cuda-12.4-x64.tar.gz | |
| dockerfile: .devops/runtime-server.Dockerfile | |
| base_image: nvidia/cuda:12.4.1-runtime-ubuntu22.04 | |
| runtime_packages: ca-certificates curl libgomp1 | |
| runs_on: ubuntu-24.04 | |
| - name: cuda13 | |
| display: CUDA 13.1 | |
| group: cuda13 | |
| arch: amd64 | |
| platform: linux/amd64 | |
| artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-cuda-13.1-x64.tar.gz | |
| dockerfile: .devops/runtime-server.Dockerfile | |
| base_image: nvidia/cuda:13.1.1-runtime-ubuntu24.04 | |
| runtime_packages: ca-certificates curl libgomp1 | |
| runs_on: ubuntu-24.04 | |
| - name: rocm | |
| display: ROCm | |
| group: rocm | |
| arch: amd64 | |
| platform: linux/amd64 | |
| artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-rocm-7.2-x64.tar.gz | |
| dockerfile: .devops/runtime-server.Dockerfile | |
| base_image: rocm/dev-ubuntu-24.04:7.2.1-complete | |
| runtime_packages: ca-certificates curl libgomp1 | |
| runs_on: ubuntu-24.04 | |
| - name: vulkan | |
| display: Vulkan | |
| group: vulkan | |
| arch: amd64 | |
| platform: linux/amd64 | |
| artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-vulkan-x64.tar.gz | |
| dockerfile: .devops/runtime-server.Dockerfile | |
| base_image: ubuntu:24.04 | |
| runtime_packages: ca-certificates curl libegl1 libgl1 libgles2 libglvnd0 libglx0 libgomp1 libvulkan1 mesa-vulkan-drivers | |
| runs_on: ubuntu-24.04 | |
| - name: sycl | |
| display: SYCL | |
| group: sycl | |
| arch: amd64 | |
| platform: linux/amd64 | |
| artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-sycl-x64.tar.gz | |
| dockerfile: .devops/runtime-intel-server.Dockerfile | |
| base_image: unused | |
| runtime_packages: unused | |
| runs_on: ubuntu-24.04 | |
| steps: | |
| - name: Clone source | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.recover-assets.outputs.source_sha }} | |
| fetch-depth: 0 | |
| - name: Download recovered release bundle | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: beellama-recovered-release-${{ inputs.tag_name }} | |
| path: ./bundle | |
| - name: Prepare Docker context | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p docker-context/app | |
| tar_file="$(find bundle -type f -name '${{ matrix.config.artifact }}' | head -n 1)" | |
| if [[ -z "${tar_file}" ]]; then | |
| echo "No package found for ${{ matrix.config.artifact }}" >&2 | |
| find bundle -type f -maxdepth 3 | sort >&2 | |
| exit 1 | |
| fi | |
| tar -xzf "${tar_file}" -C docker-context/app --strip-components=1 | |
| test -x docker-context/app/llama-server | |
| cp "${{ matrix.config.dockerfile }}" docker-context/Dockerfile | |
| ls -lh docker-context/app/llama-server | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Get build date | |
| id: build_date | |
| run: echo "date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "${GITHUB_OUTPUT}" | |
| - name: Build and push image digest | |
| id: build | |
| uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7 | |
| with: | |
| context: ./docker-context | |
| file: ./docker-context/Dockerfile | |
| platforms: ${{ matrix.config.platform }} | |
| outputs: type=image,name=${{ needs.recover-assets.outputs.image_repo }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true | |
| provenance: false | |
| build-args: | | |
| BASE_IMAGE=${{ matrix.config.base_image }} | |
| RUNTIME_PACKAGES=${{ matrix.config.runtime_packages }} | |
| BUILD_DATE=${{ steps.build_date.outputs.date }} | |
| APP_VERSION=${{ inputs.tag_name }} | |
| APP_REVISION=${{ needs.recover-assets.outputs.source_sha }} | |
| IMAGE_URL=${{ github.server_url }}/${{ github.repository }} | |
| IMAGE_SOURCE=${{ github.server_url }}/${{ github.repository }} | |
| annotations: | | |
| manifest:org.opencontainers.image.created=${{ steps.build_date.outputs.date }} | |
| manifest:org.opencontainers.image.version=${{ inputs.tag_name }} | |
| manifest:org.opencontainers.image.revision=${{ needs.recover-assets.outputs.source_sha }} | |
| manifest:org.opencontainers.image.title=BeeLlama.cpp | |
| manifest:org.opencontainers.image.description=BeeLlama.cpp GGUF inference with DFlash, TurboQuant, and TCQ cache types | |
| manifest:org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }} | |
| manifest:org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} | |
| cache-from: type=registry,ref=${{ needs.recover-assets.outputs.image_repo }}:buildcache-runtime-${{ matrix.config.name }} | |
| cache-to: type=registry,ref=${{ needs.recover-assets.outputs.image_repo }}:buildcache-runtime-${{ matrix.config.name }},mode=max | |
| - name: Record digest | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| digest="${{ steps.build.outputs.digest }}" | |
| if [[ -z "${digest}" ]]; then | |
| echo "Docker build did not return a digest." >&2 | |
| exit 1 | |
| fi | |
| mkdir -p /tmp/docker-digests | |
| printf '%s\t%s\t%s\n' "${{ matrix.config.group }}" "${{ matrix.config.arch }}" "${digest}" > "/tmp/docker-digests/${{ matrix.config.name }}.tsv" | |
| - name: Upload digest metadata | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: docker-digest-${{ matrix.config.name }} | |
| path: /tmp/docker-digests/${{ matrix.config.name }}.tsv | |
| if-no-files-found: error | |
| docker-merge: | |
| name: Docker tags | |
| if: ${{ inputs.publish_docker }} | |
| needs: | |
| - recover-assets | |
| - docker-build | |
| runs-on: ubuntu-24.04 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| config: | |
| - group: cpu | |
| display: CPU | |
| platforms: linux/amd64 linux/arm64 | |
| tag_suffixes: none -cpu | |
| - group: cuda12 | |
| display: CUDA 12.4 | |
| platforms: linux/amd64 | |
| tag_suffixes: -cuda -cuda12 | |
| - group: cuda13 | |
| display: CUDA 13.1 | |
| platforms: linux/amd64 | |
| tag_suffixes: -cuda13 | |
| - group: rocm | |
| display: ROCm | |
| platforms: linux/amd64 | |
| tag_suffixes: -rocm | |
| - group: vulkan | |
| display: Vulkan | |
| platforms: linux/amd64 | |
| tag_suffixes: -vulkan | |
| - group: sycl | |
| display: SYCL | |
| platforms: linux/amd64 | |
| tag_suffixes: -sycl | |
| steps: | |
| - name: Download digest metadata | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: docker-digest-* | |
| path: /tmp/docker-digests | |
| merge-multiple: true | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create Docker tags | |
| shell: bash | |
| env: | |
| IMAGE_REPO: ${{ needs.recover-assets.outputs.image_repo }} | |
| TAG_NAME: ${{ inputs.tag_name }} | |
| GROUP: ${{ matrix.config.group }} | |
| PLATFORMS: ${{ matrix.config.platforms }} | |
| TAG_SUFFIXES: ${{ matrix.config.tag_suffixes }} | |
| run: | | |
| set -euo pipefail | |
| digest_glob="/tmp/docker-digests/*.tsv" | |
| if ! ls ${digest_glob} >/dev/null 2>&1; then | |
| echo "No Docker digest metadata found." >&2 | |
| exit 1 | |
| fi | |
| sources=() | |
| for platform in ${PLATFORMS}; do | |
| arch="${platform#linux/}" | |
| digest="$(awk -F '\t' -v g="${GROUP}" -v a="${arch}" '$1 == g && $2 == a { print $3; exit }' ${digest_glob})" | |
| if [[ -z "${digest}" ]]; then | |
| echo "Missing digest for group=${GROUP} arch=${arch}" >&2 | |
| exit 1 | |
| fi | |
| sources+=("${IMAGE_REPO}@${digest}") | |
| done | |
| tag_args=() | |
| for suffix in ${TAG_SUFFIXES}; do | |
| if [[ "${suffix}" == "none" ]]; then | |
| suffix="" | |
| fi | |
| tag_args+=("-t" "${IMAGE_REPO}:server${suffix}") | |
| tag_args+=("-t" "${IMAGE_REPO}:server${suffix}-${TAG_NAME}") | |
| done | |
| printf 'Creating Docker tags:\n' | |
| printf ' %s\n' "${tag_args[@]}" | |
| docker buildx imagetools create "${tag_args[@]}" "${sources[@]}" | |
| publish-release: | |
| name: Publish GitHub release | |
| if: ${{ always() && inputs.publish_release && needs.recover-assets.result == 'success' && (!inputs.publish_docker || needs.docker-merge.result == 'success') }} | |
| needs: | |
| - recover-assets | |
| - docker-merge | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Clone source | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.recover-assets.outputs.source_sha }} | |
| fetch-depth: 0 | |
| - name: Prepare release tag | |
| env: | |
| TAG_NAME: ${{ inputs.tag_name }} | |
| SOURCE_SHA: ${{ needs.recover-assets.outputs.source_sha }} | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git tag -f "${TAG_NAME}" "${SOURCE_SHA}" | |
| git push origin "refs/tags/${TAG_NAME}" --force | |
| - name: Download release assets | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: beellama-recovered-release-${{ inputs.tag_name }} | |
| path: ./release-bundle | |
| - name: Create or update release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG_NAME: ${{ inputs.tag_name }} | |
| SOURCE_SHA: ${{ needs.recover-assets.outputs.source_sha }} | |
| run: | | |
| set -euo pipefail | |
| notes_file="$(find release-bundle -type f -name release-notes.md | head -n 1)" | |
| if [[ -z "${notes_file}" ]]; then | |
| echo "release-notes.md not found in release asset bundle" >&2 | |
| exit 1 | |
| fi | |
| mapfile -t release_files < <(find release-bundle -type f ! -name release-notes.md ! -name '*provenance.tsv' | sort) | |
| if [[ "${#release_files[@]}" -eq 0 ]]; then | |
| echo "No release files found in release asset bundle" >&2 | |
| exit 1 | |
| fi | |
| if gh release view "${TAG_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then | |
| gh release upload "${TAG_NAME}" "${release_files[@]}" --clobber --repo "${GITHUB_REPOSITORY}" | |
| mapfile -t existing_assets < <(gh release view "${TAG_NAME}" --repo "${GITHUB_REPOSITORY}" --json assets --jq '.assets[].name') | |
| for existing_asset in "${existing_assets[@]}"; do | |
| keep_asset="false" | |
| for release_file in "${release_files[@]}"; do | |
| if [[ "$(basename "${release_file}")" == "${existing_asset}" ]]; then | |
| keep_asset="true" | |
| break | |
| fi | |
| done | |
| if [[ "${keep_asset}" != "true" ]]; then | |
| gh release delete-asset "${TAG_NAME}" "${existing_asset}" --yes --repo "${GITHUB_REPOSITORY}" | |
| fi | |
| done | |
| gh release edit "${TAG_NAME}" --repo "${GITHUB_REPOSITORY}" --title "${TAG_NAME}" --notes-file "${notes_file}" --target "${SOURCE_SHA}" --prerelease=false --latest=true --verify-tag | |
| else | |
| gh release create "${TAG_NAME}" "${release_files[@]}" --repo "${GITHUB_REPOSITORY}" --title "${TAG_NAME}" --notes-file "${notes_file}" --target "${SOURCE_SHA}" --latest=true --verify-tag | |
| fi |