name: Release # Overrides the default run name (the tagged commit's message) so the Actions # runs list shows "Release v0.3.0" instead. run-name: "Release ${{ gitea.event.inputs.tag || gitea.ref_name }}" # Publishes the Bookhoard container image to the Gitea container registry AND # creates a Gitea Release whose body is the annotated tag's message (generated # locally by `make release VERSION=...` via git-cliff). Triggered by a version # tag push, or manually via workflow_dispatch with a tag. Pushing to main does # nothing, so work-in-progress commits never ship. Each release publishes two # image tags: the version (e.g. v0.3.0) and "latest". on: push: tags: - 'v*' workflow_dispatch: inputs: tag: description: 'Tag to release (e.g. v0.3.0)' required: true type: string jobs: build-and-push: runs-on: ubuntu-latest permissions: contents: read packages: write env: # Resolve the target tag for both triggers: explicit input on manual # dispatch, otherwise the pushed tag ref. TAG: ${{ gitea.event.inputs.tag || gitea.ref_name }} steps: - name: Checkout uses: actions/checkout@v4 with: # Full history ensures the tag annotation (the release notes) is present. fetch-depth: 0 ref: ${{ gitea.event.inputs.tag || gitea.ref }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Gitea Container Registry uses: docker/login-action@v3 with: registry: git.linuxhg.com username: ${{ gitea.actor }} # PAT stored as a repo Actions secret (auto GITHUB_TOKEN lacks package scope in Gitea) password: ${{ secrets.REGISTRY_TOKEN }} - name: Build and push image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile push: true # Publishes both the exact version (e.g. v0.2.0) and the movable "latest" tag. # Deployments default to "latest" via ${IMAGE_TAG:-latest} in docker-compose.yml; # pin or roll back by setting IMAGE_TAG in .env. tags: | git.linuxhg.com/bookhoard/bookhoard:${{ env.TAG }} git.linuxhg.com/bookhoard/bookhoard:latest - name: Create Gitea Release env: # REGISTRY_TOKEN is reused for release creation because Gitea's auto # GITHUB_TOKEN cannot create releases on this instance. The PAT must # carry write:repository scope. Idempotent: re-runs update an existing # release for this tag instead of failing with 409. On any HTTP error # the API response body is printed so a 403 names the missing scope. TOKEN: ${{ secrets.REGISTRY_TOKEN }} REPO: ${{ gitea.repository }} run: | set -euo pipefail : "${TAG:?TAG is required}" API="https://git.linuxhg.com/api/v1/repos/${REPO}/releases" AUTH="Authorization: token ${TOKEN}" # Release body = the annotated tag's message (the git-cliff notes). BODY="$(git tag -l --format='%(contents)' "${TAG}")" # Tags containing a '-' (e.g. v0.3.0-rc1) are published as pre-releases. PRE="false"; case "${TAG}" in *-*) PRE="true";; esac PAYLOAD=$(jq -n \ --arg t "${TAG}" --arg n "${TAG}" --arg b "${BODY}" --argjson p "${PRE}" \ '{tag_name:$t, name:$n, body:$b, draft:false, prerelease:$p}') # POST/PATCH the release, surfacing Gitea's error message on failure # (e.g. "token does not have write scope") instead of failing silently. api_call() { local method="$1" url="$2" resp code rbody resp="$(curl -sS -w '\n%{http_code}' -X "${method}" \ -H "${AUTH}" -H "Content-Type: application/json" \ -d "${PAYLOAD}" "${url}")" code="$(printf '%s' "${resp}" | tail -n1)" rbody="$(printf '%s' "${resp}" | sed '$d')" if [ "${code}" -ge 400 ]; then echo "::error::Release API ${code} (${method} ${url}): ${rbody}" >&2 return 1 fi } EXISTING_ID="$(curl -sS -H "${AUTH}" "${API}/tags/${TAG}" | jq -r '.id // empty' 2>/dev/null || true)" if [ -n "${EXISTING_ID}" ]; then api_call PATCH "${API}/${EXISTING_ID}" echo "Updated existing release id=${EXISTING_ID} for ${TAG}" else api_call POST "${API}" echo "Created new release for ${TAG}" fi