name: Release and Build Image on: push: tags: - "v*" jobs: release: runs-on: ubuntu-latest env: # Internal/origin Gitea URL. Do NOT use the Cloudflare fronted URL here. # Examples: # http://gitea.internal.lan:3000 # https://gitea-origin.yourdomain.local GITEA_INTERNAL_URL: "https://git.tuffraid.net" # Internal/origin registry host. Usually same host as above, but without protocol. # Example: # gitea.internal:3000 REGISTRY_HOST: "git.tuffraid.net" steps: - name: Check out repository uses: actions/checkout@v4 - name: Prepare release metadata shell: bash run: | set -euo pipefail TAG="${GITHUB_REF_NAME:-${GITHUB_REF##refs/tags/}}" VERSION="${TAG#v}" IMAGE_NAME="${REGISTRY_HOST}/${{ gitea.repository }}" echo "TAG=$TAG" >> "$GITHUB_ENV" echo "VERSION=$VERSION" >> "$GITHUB_ENV" echo "IMAGE_NAME=$IMAGE_NAME" >> "$GITHUB_ENV" if [[ "$TAG" == *-* ]]; then echo "PRERELEASE=true" >> "$GITHUB_ENV" else echo "PRERELEASE=false" >> "$GITHUB_ENV" fi echo "Resolved TAG=$TAG" echo "Resolved VERSION=$VERSION" echo "Resolved IMAGE_NAME=$IMAGE_NAME" - name: Log in to Gitea container registry shell: bash env: REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | set -euo pipefail echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USERNAME" --password-stdin - name: Build Docker image shell: bash run: | set -euo pipefail docker build \ -t "$IMAGE_NAME:$TAG" \ -t "$IMAGE_NAME:latest" \ . - name: Push version tag shell: bash run: | set -euo pipefail docker push "$IMAGE_NAME:$TAG" - name: Push latest tag if: ${{ !contains(env.TAG, '-') }} shell: bash run: | set -euo pipefail docker push "$IMAGE_NAME:latest" - name: Push prerelease channel tag if: ${{ contains(env.TAG, '-') }} shell: bash env: TAG: ${{ env.TAG }} run: | set -euo pipefail CHANNEL="${TAG#*-}" CHANNEL="${CHANNEL%%.*}" echo "Resolved prerelease channel: $CHANNEL" docker tag "$IMAGE_NAME:$TAG" "$IMAGE_NAME:$CHANNEL" docker push "$IMAGE_NAME:$CHANNEL" - name: Extract matching CHANGELOG section shell: bash env: VERSION: ${{ env.VERSION }} run: | set -euo pipefail python3 - <<'PY' import os import re from pathlib import Path version = os.environ["VERSION"] changelog_path = Path("CHANGELOG.md") if not changelog_path.exists(): Path("release_body.md").write_text(f"Release {version}\n", encoding="utf-8") raise SystemExit(0) text = changelog_path.read_text(encoding="utf-8") # Matches headings like: # ## [0.1.0] # ## 0.1.0 # ## [0.1.0-alpha.1] pattern = re.compile( rf"^##\s+\[?{re.escape(version)}\]?[^\n]*\n(.*?)(?=^##\s+\[?[^\n]+|\Z)", re.MULTILINE | re.DOTALL, ) match = pattern.search(text) if match: body = match.group(1).strip() else: body = f"Release {version}" if not body: body = f"Release {version}" Path("release_body.md").write_text(body + "\n", encoding="utf-8") print("----- release_body.md -----") print(body) print("---------------------------") PY - name: Create Gitea release shell: bash env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} GITEA_REPOSITORY: ${{ gitea.repository }} GITEA_INTERNAL_URL: ${{ env.GITEA_INTERNAL_URL }} TAG: ${{ env.TAG }} PRERELEASE: ${{ env.PRERELEASE }} run: | set -euo pipefail python3 - <<'PY' import json import os import urllib.request import urllib.error from pathlib import Path tag = os.environ["TAG"] prerelease = os.environ["PRERELEASE"].lower() == "true" server_url = os.environ["GITEA_INTERNAL_URL"].rstrip("/") repo = os.environ["GITEA_REPOSITORY"] token = os.environ["RELEASE_TOKEN"] body = Path("release_body.md").read_text(encoding="utf-8").strip() # Check if the release already exists for this tag get_url = f"{server_url}/api/v1/repos/{repo}/releases/tags/{tag}" get_req = urllib.request.Request( get_url, method="GET", headers={ "Authorization": f"token {token}", "Accept": "application/json", "User-Agent": "lst-release-workflow/1.0", }, ) existing_release = None try: with urllib.request.urlopen(get_req) as resp: existing_release = json.loads(resp.read().decode("utf-8")) except urllib.error.HTTPError as e: if e.code != 404: details = e.read().decode("utf-8", errors="replace") print("Failed checking existing release:") print(details) raise payload = { "tag_name": tag, "name": tag, "body": body, "draft": False, "prerelease": prerelease, } data = json.dumps(payload).encode("utf-8") if existing_release: release_id = existing_release["id"] url = f"{server_url}/api/v1/repos/{repo}/releases/{release_id}" method = "PATCH" print(f"Release already exists for tag {tag}, updating release id {release_id}") else: url = f"{server_url}/api/v1/repos/{repo}/releases" method = "POST" print(f"No release exists for tag {tag}, creating a new one") req = urllib.request.Request( url, data=data, method=method, headers={ "Authorization": f"token {token}", "Content-Type": "application/json", "Accept": "application/json", "User-Agent": "lst-release-workflow/1.0", }, ) try: with urllib.request.urlopen(req) as resp: print(resp.read().decode("utf-8")) except urllib.error.HTTPError as e: details = e.read().decode("utf-8", errors="replace") print("Release create/update failed:") print(details) raise PY