name: Unbound Release on: workflow_call: inputs: tag_only: description: 'Set to true to only create tags without full releases' required: false default: false type: boolean env: GITEA_URL: https://git.unbound.se RELEASE_TOKEN_FILE: /runner-secrets/release-token GIT_CLIFF_VERSION: "2.11.0" jobs: preconditions: name: Check Preconditions runs-on: ubuntu-latest steps: - name: Validate token run: | if [ ! -r "${RELEASE_TOKEN_FILE}" ]; then echo "Release token file not found at ${RELEASE_TOKEN_FILE}" echo "This workflow requires the runner to have RELEASE_TOKEN configured." exit 1 fi if [ ! -s "${RELEASE_TOKEN_FILE}" ]; then echo "Release token file is empty" exit 1 fi echo "Release token found" changelog-and-pr: name: Generate Changelog and Handle PR runs-on: ubuntu-latest needs: preconditions if: github.ref_type == 'branch' && github.ref_name == github.event.repository.default_branch steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install git-cliff run: | curl -sSfL "https://github.com/orhun/git-cliff/releases/download/v${GIT_CLIFF_VERSION}/git-cliff-${GIT_CLIFF_VERSION}-x86_64-unknown-linux-gnu.tar.gz" | tar xz sudo mv "git-cliff-${GIT_CLIFF_VERSION}/git-cliff" /usr/local/bin/ git-cliff --version - name: Generate changelog run: | git-cliff --bump --unreleased --strip header > CHANGES.md git-cliff --bump | sed "s/[[:space:]]\+$//" > CHANGELOG.md - name: Get bumped version id: version run: | VERSION=$(git-cliff --bumped-version 2>/dev/null || echo "") echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "${VERSION}" > VERSION - name: Check for changes id: check run: | LATEST=$(cat .version 2>/dev/null | jq -r '.version' 2>/dev/null || git describe --abbrev=0 --tags 2>/dev/null || echo '') VERSION=$(cat VERSION) if [ -n "${LATEST}" ] && [ "${VERSION}" = "${LATEST}" ]; then echo "has_changes=false" >> $GITHUB_OUTPUT else echo "has_changes=true" >> $GITHUB_OUTPUT fi - name: Create or update release PR if: steps.check.outputs.has_changes == 'true' env: REPOSITORY: ${{ github.repository }} DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} run: | TOKEN=$(cat "${RELEASE_TOKEN_FILE}") VERSION=$(cat VERSION) OWNER=$(echo "${REPOSITORY}" | cut -d'/' -f1) REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2) API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}" TITLE="chore(release): prepare for ${VERSION}" # Read CHANGES.md and escape for JSON DESCRIPTION=$(cat CHANGES.md | jq -Rs .) DESCRIPTION="${DESCRIPTION:1:-1}" # Remove surrounding quotes from jq # Add squash merge reminder DESCRIPTION="${DESCRIPTION} --- **Note:** Please use **Squash Merge** when merging this PR." echo "Checking for existing release PRs..." PRS=$(curl -sf \ -H "Authorization: token ${TOKEN}" \ "${API_URL}/pulls?state=open" | jq '[.[] | select(.head.ref == "next-release")]') PR_INDEX=$(echo "${PRS}" | jq -r '.[0].number // empty') echo "Checking for existing next-release branch..." BRANCH_EXISTS=$(curl -sf \ -H "Authorization: token ${TOKEN}" \ "${API_URL}/branches/next-release" 2>/dev/null && echo "true" || echo "false") # Prepare CHANGELOG.md content CHANGELOG_CONTENT=$(base64 -w0 < CHANGELOG.md) # Prepare .version content VERSION_JSON=$(jq -n --arg v "${VERSION}" '{"version":$v}') VERSION_CONTENT=$(echo "${VERSION_JSON}" | base64 -w0) if [ "${BRANCH_EXISTS}" = "true" ]; then echo "Updating existing next-release branch..." # Get SHA of existing CHANGELOG.md CHANGELOG_SHA=$(curl -sf \ -H "Authorization: token ${TOKEN}" \ "${API_URL}/contents/CHANGELOG.md?ref=next-release" | jq -r '.sha // empty') # Update or create CHANGELOG.md if [ -n "${CHANGELOG_SHA}" ]; then curl -sf -X PUT \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ --data "$(jq -n \ --arg content "${CHANGELOG_CONTENT}" \ --arg sha "${CHANGELOG_SHA}" \ --arg message "${TITLE}" \ --arg branch "next-release" \ '{content: $content, sha: $sha, message: $message, branch: $branch}')" \ "${API_URL}/contents/CHANGELOG.md" else curl -sf -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ --data "$(jq -n \ --arg content "${CHANGELOG_CONTENT}" \ --arg message "${TITLE}" \ --arg branch "next-release" \ '{content: $content, message: $message, branch: $branch, new_branch: $branch}')" \ "${API_URL}/contents/CHANGELOG.md" fi # Get SHA of existing .version VERSION_SHA=$(curl -sf \ -H "Authorization: token ${TOKEN}" \ "${API_URL}/contents/.version?ref=next-release" | jq -r '.sha // empty') # Update or create .version if [ -n "${VERSION_SHA}" ]; then curl -sf -X PUT \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ --data "$(jq -n \ --arg content "${VERSION_CONTENT}" \ --arg sha "${VERSION_SHA}" \ --arg message "${TITLE}" \ --arg branch "next-release" \ '{content: $content, sha: $sha, message: $message, branch: $branch}')" \ "${API_URL}/contents/.version" else curl -sf -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ --data "$(jq -n \ --arg content "${VERSION_CONTENT}" \ --arg message "${TITLE}" \ --arg branch "next-release" \ '{content: $content, message: $message, branch: $branch}')" \ "${API_URL}/contents/.version" fi else echo "Creating new next-release branch with CHANGELOG.md..." curl -sf -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ --data "$(jq -n \ --arg content "${CHANGELOG_CONTENT}" \ --arg message "${TITLE}" \ --arg branch "next-release" \ --arg new_branch "next-release" \ '{content: $content, message: $message, branch: $branch, new_branch: $new_branch}')" \ "${API_URL}/contents/CHANGELOG.md" echo "Adding .version to next-release branch..." curl -sf -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ --data "$(jq -n \ --arg content "${VERSION_CONTENT}" \ --arg message "${TITLE}" \ --arg branch "next-release" \ '{content: $content, message: $message, branch: $branch}')" \ "${API_URL}/contents/.version" fi if [ -n "${PR_INDEX}" ]; then echo "Updating existing PR #${PR_INDEX}..." curl -sf -X PATCH \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ --data "$(jq -n \ --arg title "${TITLE}" \ --arg body "${DESCRIPTION}" \ '{title: $title, body: $body}')" \ "${API_URL}/pulls/${PR_INDEX}" else echo "Creating new PR..." curl -sf -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ --data "$(jq -n \ --arg title "${TITLE}" \ --arg body "${DESCRIPTION}" \ --arg head "next-release" \ --arg base "${DEFAULT_BRANCH}" \ '{title: $title, body: $body, head: $head, base: $base}')" \ "${API_URL}/pulls" fi create-release: name: Create Release runs-on: ubuntu-latest needs: preconditions if: | github.ref_type == 'branch' && github.ref_name == github.event.repository.default_branch && inputs.tag_only != true steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install git-cliff run: | curl -sSfL "https://github.com/orhun/git-cliff/releases/download/v${GIT_CLIFF_VERSION}/git-cliff-${GIT_CLIFF_VERSION}-x86_64-unknown-linux-gnu.tar.gz" | tar xz sudo mv "git-cliff-${GIT_CLIFF_VERSION}/git-cliff" /usr/local/bin/ git-cliff --version - name: Generate changelog run: git-cliff --bump --unreleased --strip header > CHANGES.md - name: Get version id: version run: | VERSION=$(git-cliff --bumped-version 2>/dev/null || echo "") echo "version=${VERSION}" >> $GITHUB_OUTPUT - name: Create release env: REPOSITORY: ${{ github.repository }} DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} VERSION: ${{ steps.version.outputs.version }} run: | TOKEN=$(cat "${RELEASE_TOKEN_FILE}") if [ ! -r .version ]; then echo "Version file not found" exit 0 fi CURRENT_VERSION=$(cat .version 2>/dev/null | jq -r '.version') LATEST=$(git describe --abbrev=0 --tags 2>/dev/null || echo '') if [ -n "${LATEST}" ] && [ "${CURRENT_VERSION}" = "${LATEST}" ]; then echo "Version ${CURRENT_VERSION} already exists" exit 0 fi OWNER=$(echo "${REPOSITORY}" | cut -d'/' -f1) REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2) API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}" MESSAGE=$(cat CHANGES.md | jq -Rs .) MESSAGE="${MESSAGE:1:-1}" # Remove surrounding quotes echo "Creating release ${VERSION}..." curl -sf -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ --data "$(jq -n \ --arg tag_name "${VERSION}" \ --arg name "${VERSION}" \ --arg body "${MESSAGE}" \ --arg target "${DEFAULT_BRANCH}" \ '{tag_name: $tag_name, name: $name, body: $body, target_commitish: $target}')" \ "${API_URL}/releases" create-tag: name: Create Tag runs-on: ubuntu-latest needs: preconditions if: | github.ref_type == 'branch' && github.ref_name == github.event.repository.default_branch && inputs.tag_only == true steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install git-cliff run: | curl -sSfL "https://github.com/orhun/git-cliff/releases/download/v${GIT_CLIFF_VERSION}/git-cliff-${GIT_CLIFF_VERSION}-x86_64-unknown-linux-gnu.tar.gz" | tar xz sudo mv "git-cliff-${GIT_CLIFF_VERSION}/git-cliff" /usr/local/bin/ git-cliff --version - name: Get version id: version run: | VERSION=$(git-cliff --bumped-version 2>/dev/null || echo "") echo "version=${VERSION}" >> $GITHUB_OUTPUT - name: Create tag env: REPOSITORY: ${{ github.repository }} DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} VERSION: ${{ steps.version.outputs.version }} run: | TOKEN=$(cat "${RELEASE_TOKEN_FILE}") if [ ! -r .version ]; then echo "Version file not found" exit 0 fi CURRENT_VERSION=$(cat .version 2>/dev/null | jq -r '.version') LATEST=$(git describe --abbrev=0 --tags 2>/dev/null || echo '') if [ -n "${LATEST}" ] && [ "${CURRENT_VERSION}" = "${LATEST}" ]; then echo "Version ${CURRENT_VERSION} already exists" exit 0 fi OWNER=$(echo "${REPOSITORY}" | cut -d'/' -f1) REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2) API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}" echo "Creating tag ${VERSION}..." curl -sf -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ --data "$(jq -n \ --arg tag_name "${VERSION}" \ --arg target "${DEFAULT_BRANCH}" \ --arg message "${VERSION}" \ '{tag_name: $tag_name, target: $target, message: $message}')" \ "${API_URL}/tags"