name: Release # Checkout, interpolation and caching conventions match ci.yaml -- see the header there # for why there are no JS actions and why every `${{ }}` goes through `env:`. # # There is no upload-artifact/download-artifact equivalent here (both are JS actions, and # this Gitea has no artifact store wired up), so the job that builds the binaries is also # the job that publishes them. Nothing is handed between jobs. # # The asset names matter beyond being tidy: internal/updater looks for exactly # terdut-tui--- in the latest release and reports every available name # when it cannot find one. Renaming the pattern here breaks self-update for every # installed binary. on: push: tags: - 'v*' workflow_dispatch: concurrency: group: release-${{ github.ref }} cancel-in-progress: true env: REPO_URL: https://git.ryuvia.com/niklas/terdut-tui.git API: https://git.ryuvia.com/api/v1/repos/niklas/terdut-tui jobs: # Gates the build, so a tag that fails here publishes no binaries. test: runs-on: ubuntu-latest container: image: golang:1.26.6-bookworm volumes: - go-mod-cache:/go/pkg/mod - go-build-cache:/root/.cache/go-build - gobin-cache:/go/bin steps: - name: Checkout env: REF_NAME: ${{ github.ref_name }} run: git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" . - name: Vet run: go vet ./... - name: Test run: go test ./... binaries: needs: test runs-on: ubuntu-latest container: image: golang:1.26.6-bookworm volumes: - go-mod-cache:/go/pkg/mod - go-build-cache:/root/.cache/go-build - gobin-cache:/go/bin steps: - name: Checkout env: REF_NAME: ${{ github.ref_name }} run: git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" . - name: Build every target env: REF_NAME: ${{ github.ref_name }} run: | set -eu mkdir -p dist for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64; do GOOS="${target%/*}" GOARCH="${target#*/}" out="dist/terdut-tui-${REF_NAME}-${GOOS}-${GOARCH}" echo "building $out" GOOS="$GOOS" GOARCH="$GOARCH" go build \ -ldflags "-X main.version=${REF_NAME}" \ -o "$out" . done # Creating the release is made idempotent rather than assumed-new: a re-run of a # failed release must not die on the release that already exists. Assets are # replaced the same way, so a re-run repairs a partial upload. - name: Publish the release env: REF_NAME: ${{ github.ref_name }} TOKEN: ${{ secrets.REGISTRY_TOKEN }} run: | set -eu auth="Authorization: token $TOKEN" body=$(curl -sf -H "$auth" "$API/releases/tags/$REF_NAME" || true) if [ -z "$body" ]; then body=$(curl -sf -X POST -H "$auth" -H 'Content-Type: application/json' \ -d "{\"tag_name\":\"$REF_NAME\",\"name\":\"$REF_NAME\"}" \ "$API/releases") fi # The release object serialises `id` first, so the first match is the release's # own id and not one of the nested author/asset ids. release_id=$(printf '%s' "$body" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) [ -n "$release_id" ] || { echo "::error::could not determine release id"; exit 1; } echo "release id $release_id" for f in dist/*; do name=$(basename "$f") # Drop an existing asset of the same name first: Gitea happily stores two # attachments with one name, and the updater matches by name. old=$(curl -sf -H "$auth" "$API/releases/$release_id/assets" \ | tr '}' '\n' | grep "\"name\":\"$name\"" \ | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true) if [ -n "$old" ]; then curl -sf -X DELETE -H "$auth" "$API/releases/$release_id/assets/$old" || true fi echo "uploading $name" curl -sf -X POST -H "$auth" -F "attachment=@$f" \ "$API/releases/$release_id/assets?name=$name" > /dev/null done