name: Do Release on: push: tags: - 'v*.*.*' workflow_dispatch: inputs: tag: description: Semantic version tag to publish, with or without leading v. Defaults to the current tag ref when dispatched from a tag. required: false workflow_call: inputs: tag: description: Semantic version tag to publish, with or without leading v. When omitted, the current tag ref is used. required: false default: '' type: string jobs: release: runs-on: ubuntu-latest container: docker.io/catthehacker/ubuntu:act-latest outputs: tag: ${{ steps.publish.outputs.tag }} version: ${{ steps.publish.outputs.version }} defaults: run: shell: bash env: RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN || secrets.GITEA_TOKEN }} SUMMARY_FILE: ${{ runner.temp }}/do-release-summary.md steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Fetch and detect release tag id: detect-tag run: | set -euo pipefail # Fetch all tags from origin first git fetch origin --tags --force --quiet 2>/dev/null || true # Check if HEAD is at a tag (prepare-release may have just tagged it) if head_tag="$(git describe --exact-match --tags HEAD 2>/dev/null)" && [[ -n "$head_tag" ]]; then echo "detected_tag=$head_tag" >> "$GITHUB_OUTPUT" exit 0 fi # Fall back to finding the most recent tag if latest_tag="$(git describe --tags --abbrev=0 2>/dev/null)" && [[ -n "$latest_tag" ]]; then echo "detected_tag=$latest_tag" >> "$GITHUB_OUTPUT" fi - name: Resolve release version id: resolve-version env: INPUT_TAG: ${{ inputs.tag }} DETECTED_TAG: ${{ steps.detect-tag.outputs.detected_tag }} run: | set -euo pipefail # Try to use explicit input first requested_tag="$(printf '%s' "${INPUT_TAG}" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')" # Fall back to detected tag if input is empty if [[ -z "$requested_tag" && -n "${DETECTED_TAG}" ]]; then requested_tag="$DETECTED_TAG" fi # Try GITHUB_REF if still empty if [[ -z "$requested_tag" && "$GITHUB_REF" == refs/tags/* ]]; then requested_tag="${GITHUB_REF#refs/tags/}" fi if [[ -n "$requested_tag" ]]; then # Normalize to v-prefixed format normalized="${requested_tag#v}" tag="v${normalized}" else echo "Error: Could not resolve release version" >&2 echo " - inputs.tag: '$INPUT_TAG'" >&2 echo " - detected_tag: '${DETECTED_TAG}'" >&2 echo " - GITHUB_REF: '$GITHUB_REF'" >&2 exit 1 fi echo "tag=${tag}" >> "$GITHUB_OUTPUT" echo "version=${normalized}" >> "$GITHUB_OUTPUT" - name: Checkout release tag uses: actions/checkout@v4 with: fetch-depth: 0 ref: refs/tags/${{ steps.resolve-version.outputs.tag }} - name: Setup Go uses: actions/setup-go@v5 with: go-version: '1.26.1' check-latest: true cache: true cache-dependency-path: go.sum - name: Preflight release API access env: TAG_NAME: ${{ steps.resolve-version.outputs.tag }} run: | set -euo pipefail if [[ -z "${RELEASE_TOKEN:-}" ]]; then echo "No release token available. Set GITEA_TOKEN (or GITHUB_TOKEN on GitHub)." >&2 exit 1 fi api_base="${GITHUB_API_URL:-${GITHUB_SERVER_URL%/}/api/v1}" repo_api="${api_base}/repos/${GITHUB_REPOSITORY}" curl --fail-with-body -sS \ -H "Authorization: token ${RELEASE_TOKEN}" \ -H "Content-Type: application/json" \ "${repo_api}" >/dev/null curl --fail-with-body -sS \ -H "Authorization: token ${RELEASE_TOKEN}" \ -H "Content-Type: application/json" \ "${repo_api}/releases?limit=1" >/dev/null if ! git rev-parse --verify --quiet "refs/tags/${TAG_NAME}" >/dev/null; then echo "Tag ${TAG_NAME} was not found in the checked out repository." >&2 exit 1 fi - name: Create or update release id: publish uses: ./publish with: token: ${{ secrets.GITHUB_TOKEN || secrets.GITEA_TOKEN }} version: ${{ steps.resolve-version.outputs.version }} - name: Build release binaries env: RELEASE_VERSION: ${{ steps.publish.outputs.version }} run: | set -euo pipefail mkdir -p dist for target in linux/amd64 linux/arm64; do os="${target%/*}" arch="${target#*/}" bin="vociferate_${RELEASE_VERSION}_${os}_${arch}" GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags="-s -w" -o "dist/${bin}" ./cmd/vociferate done ( cd dist shasum -a 256 * > checksums.txt ) - name: Upload release binaries env: RELEASE_ID: ${{ steps.publish.outputs.release-id }} RELEASE_VERSION: ${{ steps.publish.outputs.version }} run: | set -euo pipefail release_api="${GITHUB_API_URL:-${GITHUB_SERVER_URL%/}/api/v1}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets" for asset in dist/*; do name="$(basename "$asset")" assets_json="$(curl -sS --fail-with-body \ -H "Authorization: token ${RELEASE_TOKEN}" \ -H "Content-Type: application/json" \ "${release_api}")" escaped_name="$(printf '%s' "$name" | sed 's/[][(){}.^$*+?|\\/]/\\&/g')" existing_asset_id="$(printf '%s' "$assets_json" | tr -d '\n' | sed -n "s/.*{\"id\":\([0-9][0-9]*\)[^}]*\"name\":\"${escaped_name}\".*/\1/p")" if [[ -n "$existing_asset_id" ]]; then curl --fail-with-body \ -X DELETE \ -H "Authorization: token ${RELEASE_TOKEN}" \ -H "Content-Type: application/json" \ "${release_api}/${existing_asset_id}" fi curl --fail-with-body \ -X POST \ -H "Authorization: token ${RELEASE_TOKEN}" \ -H "Content-Type: application/octet-stream" \ "${release_api}?name=${name}" \ --data-binary "@${asset}" done - name: Summarize published release env: TAG_NAME: ${{ steps.publish.outputs.tag }} RELEASE_VERSION: ${{ steps.publish.outputs.version }} run: | set -euo pipefail { echo "## Release Published" echo echo "- Tag: ${TAG_NAME}" echo "- Release notes sourced from changelog entry ${RELEASE_VERSION}." echo "- Published assets: vociferate_${RELEASE_VERSION}_linux_amd64, vociferate_${RELEASE_VERSION}_linux_arm64, checksums.txt" } >> "$SUMMARY_FILE" - name: Summary if: ${{ always() }} run: | set -euo pipefail echo 'Summary' echo if [[ -s "$SUMMARY_FILE" ]]; then cat "$SUMMARY_FILE" else echo 'No summary generated.' fi validate: runs-on: ubuntu-latest container: docker.io/catthehacker/ubuntu:act-latest needs: release strategy: fail-fast: false matrix: include: - asset_arch: amd64 run_command: ./vociferate - asset_arch: arm64 run_command: qemu-aarch64-static ./vociferate defaults: run: shell: bash env: SUMMARY_FILE: ${{ runner.temp }}/do-release-validate-summary.md steps: - name: Checkout tagged revision uses: actions/checkout@v4 with: ref: refs/tags/${{ needs.release.outputs.tag }} - name: Install arm64 emulator if: ${{ matrix.asset_arch == 'arm64' }} run: | set -euo pipefail apt-get update apt-get install -y qemu-user-static - name: Download released binary env: TOKEN: ${{ secrets.GITHUB_TOKEN || secrets.GITEA_TOKEN }} TAG_NAME: ${{ needs.release.outputs.tag }} RELEASE_VERSION: ${{ needs.release.outputs.version }} ASSET_ARCH: ${{ matrix.asset_arch }} SERVER_URL: ${{ github.server_url }} run: | set -euo pipefail asset_name="vociferate_${RELEASE_VERSION}_linux_${ASSET_ARCH}" asset_url="${SERVER_URL}/aether/vociferate/releases/download/${TAG_NAME}/${asset_name}" curl --fail --location \ -H "Authorization: token ${TOKEN}" \ -o vociferate \ "$asset_url" chmod +x vociferate echo "asset_name=${asset_name}" >> "$GITHUB_ENV" - name: Smoke test released binary env: RUN_COMMAND: ${{ matrix.run_command }} TAG_NAME: ${{ needs.release.outputs.tag }} run: | set -euo pipefail ${RUN_COMMAND} --help >/dev/null recommend_stderr="$(mktemp)" if ${RUN_COMMAND} --recommend --root . >/dev/null 2>"${recommend_stderr}"; then echo "Expected --recommend to fail on the tagged release checkout" >&2 exit 1 fi recommend_error="$(cat "${recommend_stderr}")" case "${recommend_error}" in *"unreleased section is empty"*) ;; *) echo "Unexpected recommend failure output: ${recommend_error}" >&2 exit 1 ;; esac { echo "## Released Binary Validation (${{ matrix.asset_arch }})" echo echo "- Release tag: ${TAG_NAME}" echo "- Asset: ${asset_name}" echo "- Binary executed successfully via --help." echo "- --recommend failed as expected on the tagged checkout because Unreleased is empty." } >> "$SUMMARY_FILE" - name: Summary if: ${{ always() }} run: | set -euo pipefail echo 'Summary' echo if [[ -s "$SUMMARY_FILE" ]]; then cat "$SUMMARY_FILE" else echo 'No summary generated.' fi