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 }} SUMMARY_FILE: ${{ runner.temp }}/do-release-summary.md steps: - name: Checkout tagged revision uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.ref }} - name: Checkout requested tag if: ${{ inputs.tag != '' }} uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ startsWith(inputs.tag, 'v') && format('refs/tags/{0}', inputs.tag) || format('refs/tags/v{0}', inputs.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: Create or update release id: publish uses: ./publish with: token: ${{ secrets.GITHUB_TOKEN }} version: ${{ inputs.tag }} - 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: ${{ github.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