Compare commits

...

27 Commits

Author SHA1 Message Date
argoyle e3c17ceaa0 fix: make release PR creation robust against race conditions (#19)
Two fixes for intermittent release workflow failures:

- **Use `BASE_BRANCH` instead of `DEFAULT_BRANCH`** for the PR `base` field — `BASE_BRANCH` has a `:-main` fallback, preventing an empty base from causing a 404
- **Replace fixed `sleep 3` with proper polling and retry** — polls for branch readiness (up to 10 attempts) before creating the PR, then retries PR creation (up to 5 attempts) with backoff

Fixes the `Error creating PR (HTTP 404)` seen when Gitea hasn't fully indexed the `next-release` branch by the time the PR creation request fires.

🤖 Generated with [Claude Code](https://claude.ai/claude-code)

Reviewed-on: #19
2026-03-23 15:52:25 +00:00
argoyle 068c6ef686 Merge pull request 'fix(release): add retry and delay for PR creation to handle Gitea API race condition' (#18) from fix-release-pr-race-condition into main
Reviewed-on: #18
2026-02-23 13:13:23 +00:00
argoyle d5623bdf9c fix(release): add retry and delay for PR creation to handle Gitea API race condition
The PR creation curl immediately follows branch creation via the Contents
API, but Gitea may not have fully indexed the new branch for pull request
operations yet. This causes intermittent HTTP errors on the first run.

- Add sleep 3 before PR creation to allow Gitea to process the new branch
- Use --retry-all-errors so curl retries on HTTP 4xx/5xx (not just
  connection failures)
- Capture and display the actual HTTP error code and response body on
  failure for easier debugging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 14:12:43 +01:00
argoyle e3f1ba4e0a Merge pull request 'fix(release): replace rebase with branch recreation to prevent PR merge conflicts' (#17) from fix-release-pr-conflicts into main
Reviewed-on: #17
2026-02-12 11:02:43 +00:00
argoyle 4688810928 fix(release): replace rebase with branch recreation to prevent PR merge conflicts
Delete and recreate the next-release branch from base on each run instead of
rebasing, which caused 409 conflicts on CHANGELOG.md. Add concurrency group
to prevent parallel workflow runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 12:02:01 +01:00
renovate 646d0e4e0c chore(deps): update dependency orhun/git-cliff to v2.12.0 (#16) 2026-01-20 17:38:12 +00:00
argoyle c564063d20 Merge pull request 'fix(release): add retry logic to curl API calls' (#15) from fix-curl-retry into main
Reviewed-on: #15
2026-01-14 14:14:24 +00:00
argoyle a9458d03dd fix(release): add retry logic to curl API calls
Add --retry 3 --retry-delay 2 --retry-connrefused to all Gitea API
curl calls to handle transient connection failures. This addresses
intermittent exit code 7 (connection refused) errors when the K8s
internal service briefly drops connections.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 15:08:17 +01:00
argoyle 7802e99a9c Merge pull request 'feat(release): rebase PR branch before updating changelog' (#14) from feat-rebase-pr into main
Reviewed-on: #14
2026-01-09 20:10:09 +00:00
argoyle dafe37343f feat(release): rebase PR branch before updating changelog
When updating an existing release PR, the workflow now rebases
the next-release branch onto the base branch using Gitea's PR
update API with style=rebase. This keeps the PR up to date with
main without force-pushing or deleting the branch.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 21:09:21 +01:00
argoyle 33b839e26e Merge pull request 'feat(release): enhance release workflow with improvements' (#13) from fix-workflow-gitea-compat into main
Reviewed-on: #13
2026-01-09 06:39:11 +00:00
argoyle 4b25f5864d feat(release): enhance release workflow with improvements
Adds fallback to main branch if DEFAULT_BRANCH is empty and updates 
the way CHANGELOG.md and .version files are handled in the release 
process. Refactors checks for existing branches and includes 
detailed logging for transparency. This improves the robustness of 
the release workflow and ensures relevant files are created or 
updated correctly.
2026-01-09 07:38:33 +01:00
argoyle 91f60440e8 Merge pull request 'chore(deps): update dependency orhun/git-cliff to v2.11.0' (#10) from renovate/orhun-git-cliff-2.x into main
Reviewed-on: #10
2026-01-09 06:09:49 +00:00
argoyle 4f7694d9e1 Merge pull request 'fix: use DEFAULT_BRANCH as source when creating next-release branch' (#12) from fix-workflow-gitea-compat into main
Reviewed-on: #12
2026-01-09 04:29:32 +00:00
argoyle 2b2dba8c2b fix: use DEFAULT_BRANCH as source when creating next-release branch 2026-01-09 05:28:29 +01:00
argoyle 8b0d478f72 Merge pull request 'fix: use internal Gitea URL for API calls' (#11) from fix-workflow-gitea-compat into main
Reviewed-on: #11
2026-01-09 04:22:49 +00:00
argoyle 5c4e1828de fix: use internal Gitea URL for API calls
Runner is inside K8s cluster and cannot reach external URL
2026-01-09 05:22:08 +01:00
renovate ce44701d56 chore(deps): update dependency orhun/git-cliff to v2.11.0 2026-01-09 04:13:22 +00:00
argoyle 0beb7b8490 Merge pull request 'fix: remove containers and artifacts for Gitea compatibility' (#9) from fix-workflow-gitea-compat into main
Reviewed-on: #9
2026-01-09 04:09:51 +00:00
argoyle 4ba5d96d75 fix: remove containers and artifacts for Gitea compatibility
- Run directly on ubuntu-latest instead of custom containers
- Download git-cliff binary from GitHub releases
- Merge changelog and handle-pr into single job
- Make create-release and create-tag self-contained
- Remove upload-artifact and download-artifact (not supported on GHES/Gitea)
- Add Renovate custom manager for automatic git-cliff updates
2026-01-09 05:08:10 +01:00
argoyle a195ff5a36 Merge pull request 'chore(deps): update actions/checkout action to v6' (#5) from renovate/actions-checkout-6.x into main
Reviewed-on: #5
2026-01-09 03:51:42 +00:00
argoyle f39240e9ff Merge pull request 'fix: remove containers from workflow to fix Node.js compatibility' (#8) from fix-workflow-containers into main
Reviewed-on: #8
2026-01-09 03:49:11 +00:00
argoyle db1f4c1563 fix: remove containers from workflow to fix Node.js compatibility
- Run directly on ubuntu-latest instead of custom containers
- Download git-cliff binary from GitHub releases
- Add Renovate custom manager for automatic git-cliff updates
2026-01-09 04:48:47 +01:00
argoyle 1e799eccc0 Merge pull request 'docs: add CLAUDE.md for Claude Code guidance' (#7) from add-claude-md into main
Reviewed-on: #7
2026-01-09 03:48:29 +00:00
renovate 2977a1b0c4 chore(deps): update actions/checkout action to v6 2026-01-08 21:25:29 +00:00
argoyle 1502bb6b05 docs: add CLAUDE.md for Claude Code guidance 2026-01-08 21:59:06 +01:00
argoyle ca333a2500 feat: use file-based release token instead of secret 2026-01-08 21:48:17 +01:00
5 changed files with 242 additions and 235 deletions
+12
View File
@@ -0,0 +1,12 @@
{
"permissions": {
"allow": [
"Bash(but --help:*)",
"Bash(but rub --help:*)",
"WebSearch",
"WebFetch(domain:docs.gitea.com)",
"WebFetch(domain:gitea.com)",
"Bash(but status:*)"
]
}
}
+159 -205
View File
@@ -8,47 +8,51 @@ on:
required: false required: false
default: false default: false
type: boolean type: boolean
secrets:
UNBOUND_RELEASE_TOKEN: concurrency:
description: 'Token with API access to create PRs and releases' group: release-${{ github.repository }}
required: true cancel-in-progress: false
env: env:
GITEA_URL: https://git.unbound.se GITEA_URL: http://gitea-http.gitea.svc.cluster.local:3000
RELEASE_TOKEN_FILE: /runner-secrets/release-token
GIT_CLIFF_VERSION: "2.12.0"
jobs: jobs:
preconditions: preconditions:
name: Check Preconditions name: Check Preconditions
runs-on: ubuntu-latest runs-on: ubuntu-latest
container:
image: amd64/alpine:3.22.2@sha256:b687e78c6e2785808446f45b52f1540a1e58adc07bdcffea354933b18c613d90
steps: steps:
- name: Validate token - name: Validate token
if: ${{ secrets.UNBOUND_RELEASE_TOKEN == '' }}
run: | run: |
echo "To use Unbound Release, a UNBOUND_RELEASE_TOKEN secret needs to be defined." if [ ! -r "${RELEASE_TOKEN_FILE}" ]; then
echo "It needs API access to write repository files, create PRs and releases." echo "Release token file not found at ${RELEASE_TOKEN_FILE}"
echo " " echo "This workflow requires the runner to have RELEASE_TOKEN configured."
echo "Create a token in Gitea: Settings -> Applications -> Generate New Token"
echo "Required scopes: repository (read/write), issue (read/write)"
exit 1 exit 1
fi
if [ ! -s "${RELEASE_TOKEN_FILE}" ]; then
echo "Release token file is empty"
exit 1
fi
echo "Release token found"
changelog: changelog-and-pr:
name: Generate Changelog name: Generate Changelog and Handle PR
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: preconditions needs: preconditions
if: github.ref_type == 'branch' && github.ref_name == github.event.repository.default_branch if: github.ref_type == 'branch' && github.ref_name == github.event.repository.default_branch
container:
image: orhunp/git-cliff:2.10.1@sha256:6ba0d1fcb051bd7b154cfb19c4b2b3bfa2c22c475f5285fc30606777b6573119
outputs:
version: ${{ steps.version.outputs.version }}
has_changes: ${{ steps.check.outputs.has_changes }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 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 - name: Generate changelog
run: | run: |
git-cliff --bump --unreleased --strip header > CHANGES.md git-cliff --bump --unreleased --strip header > CHANGES.md
@@ -72,63 +76,40 @@ jobs:
echo "has_changes=true" >> $GITHUB_OUTPUT echo "has_changes=true" >> $GITHUB_OUTPUT
fi fi
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: changelog-artifacts
path: |
CHANGES.md
CHANGELOG.md
VERSION
handle-pr:
name: Handle Release PR
runs-on: ubuntu-latest
needs: changelog
if: needs.changelog.outputs.has_changes == 'true'
container:
image: amd64/alpine:3.22.2@sha256:b687e78c6e2785808446f45b52f1540a1e58adc07bdcffea354933b18c613d90
steps:
- name: Install dependencies
run: apk add --no-cache git jq curl
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: changelog-artifacts
- name: Create or update release PR - name: Create or update release PR
if: steps.check.outputs.has_changes == 'true'
env: env:
TOKEN: ${{ secrets.UNBOUND_RELEASE_TOKEN }}
REPOSITORY: ${{ github.repository }} REPOSITORY: ${{ github.repository }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: | run: |
TOKEN=$(cat "${RELEASE_TOKEN_FILE}")
VERSION=$(cat VERSION) VERSION=$(cat VERSION)
OWNER=$(echo "${REPOSITORY}" | cut -d'/' -f1) OWNER=$(echo "${REPOSITORY}" | cut -d'/' -f1)
REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2) REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2)
API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}" API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}"
# Fallback to main if DEFAULT_BRANCH is empty
BASE_BRANCH="${DEFAULT_BRANCH:-main}"
echo "Using base branch: ${BASE_BRANCH}"
TITLE="chore(release): prepare for ${VERSION}" TITLE="chore(release): prepare for ${VERSION}"
# Read CHANGES.md and escape for JSON # Read CHANGES.md content and add note (jq --arg will handle JSON escaping)
DESCRIPTION=$(cat CHANGES.md | jq -Rs .) CHANGES_CONTENT=$(cat CHANGES.md)
DESCRIPTION="${DESCRIPTION:1:-1}" # Remove surrounding quotes from jq PR_NOTE="**Note:** Please use **Squash Merge** when merging this PR."
DESCRIPTION="${CHANGES_CONTENT}"$'\n\n---\n\n'"${PR_NOTE}"
# 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')
# Delete existing next-release branch to start fresh (auto-closes any open PR)
echo "Checking for existing next-release branch..." echo "Checking for existing next-release branch..."
BRANCH_EXISTS=$(curl -sf \ BRANCH_CHECK=$(curl -s --retry 3 --retry-delay 2 --retry-connrefused -w "%{http_code}" -o /dev/null \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
"${API_URL}/branches/next-release" 2>/dev/null && echo "true" || echo "false") "${API_URL}/branches/next-release")
if [ "${BRANCH_CHECK}" = "200" ]; then
echo "Deleting existing next-release branch..."
curl -sf --retry 3 --retry-delay 2 --retry-connrefused -X DELETE \
-H "Authorization: token ${TOKEN}" \
"${API_URL}/branches/next-release"
echo "Branch deleted"
fi
# Prepare CHANGELOG.md content # Prepare CHANGELOG.md content
CHANGELOG_CONTENT=$(base64 -w0 < CHANGELOG.md) CHANGELOG_CONTENT=$(base64 -w0 < CHANGELOG.md)
@@ -137,46 +118,54 @@ jobs:
VERSION_JSON=$(jq -n --arg v "${VERSION}" '{"version":$v}') VERSION_JSON=$(jq -n --arg v "${VERSION}" '{"version":$v}')
VERSION_CONTENT=$(echo "${VERSION_JSON}" | base64 -w0) VERSION_CONTENT=$(echo "${VERSION_JSON}" | base64 -w0)
if [ "${BRANCH_EXISTS}" = "true" ]; then echo "Creating new next-release branch from ${BASE_BRANCH}..."
echo "Updating existing next-release branch..."
# Get SHA of existing CHANGELOG.md # Check if CHANGELOG.md exists on base branch to determine create vs update
CHANGELOG_SHA=$(curl -sf \ CHANGELOG_SHA=$(curl -sf --retry 3 --retry-delay 2 --retry-connrefused \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
"${API_URL}/contents/CHANGELOG.md?ref=next-release" | jq -r '.sha // empty') "${API_URL}/contents/CHANGELOG.md?ref=${BASE_BRANCH}" | jq -r '.sha // empty')
# Update or create CHANGELOG.md
if [ -n "${CHANGELOG_SHA}" ]; then if [ -n "${CHANGELOG_SHA}" ]; then
curl -sf -X PUT \ echo "Updating CHANGELOG.md (exists on ${BASE_BRANCH}) on new branch..."
RESPONSE=$(curl -s --retry 3 --retry-delay 2 --retry-connrefused -w "\n%{http_code}" -X PUT \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
--data "$(jq -n \ --data "$(jq -n \
--arg content "${CHANGELOG_CONTENT}" \ --arg content "${CHANGELOG_CONTENT}" \
--arg sha "${CHANGELOG_SHA}" \ --arg sha "${CHANGELOG_SHA}" \
--arg message "${TITLE}" \ --arg message "${TITLE}" \
--arg branch "next-release" \ --arg branch "${BASE_BRANCH}" \
'{content: $content, sha: $sha, message: $message, branch: $branch}')" \ --arg new_branch "next-release" \
"${API_URL}/contents/CHANGELOG.md" '{content: $content, sha: $sha, message: $message, branch: $branch, new_branch: $new_branch}')" \
"${API_URL}/contents/CHANGELOG.md")
else else
curl -sf -X POST \ echo "Creating CHANGELOG.md on new branch..."
RESPONSE=$(curl -s --retry 3 --retry-delay 2 --retry-connrefused -w "\n%{http_code}" -X POST \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
--data "$(jq -n \ --data "$(jq -n \
--arg content "${CHANGELOG_CONTENT}" \ --arg content "${CHANGELOG_CONTENT}" \
--arg message "${TITLE}" \ --arg message "${TITLE}" \
--arg branch "next-release" \ --arg branch "${BASE_BRANCH}" \
'{content: $content, message: $message, branch: $branch, new_branch: $branch}')" \ --arg new_branch "next-release" \
"${API_URL}/contents/CHANGELOG.md" '{content: $content, message: $message, branch: $branch, new_branch: $new_branch}')" \
"${API_URL}/contents/CHANGELOG.md")
fi
HTTP_CODE=$(echo "${RESPONSE}" | tail -1)
BODY=$(echo "${RESPONSE}" | sed '$d')
if [ "${HTTP_CODE}" -ge 400 ]; then
echo "Error with CHANGELOG.md: ${BODY}"
exit 1
fi fi
# Get SHA of existing .version # Check if .version exists on base branch
VERSION_SHA=$(curl -sf \ VERSION_SHA=$(curl -sf --retry 3 --retry-delay 2 --retry-connrefused \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
"${API_URL}/contents/.version?ref=next-release" | jq -r '.sha // empty') "${API_URL}/contents/.version?ref=${BASE_BRANCH}" | jq -r '.sha // empty')
# Update or create .version
if [ -n "${VERSION_SHA}" ]; then if [ -n "${VERSION_SHA}" ]; then
curl -sf -X PUT \ echo "Updating .version on next-release branch..."
curl -sf --retry 3 --retry-delay 2 --retry-connrefused -X PUT \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
--data "$(jq -n \ --data "$(jq -n \
@@ -187,31 +176,8 @@ jobs:
'{content: $content, sha: $sha, message: $message, branch: $branch}')" \ '{content: $content, sha: $sha, message: $message, branch: $branch}')" \
"${API_URL}/contents/.version" "${API_URL}/contents/.version"
else else
curl -sf -X POST \ echo "Creating .version on next-release branch..."
-H "Authorization: token ${TOKEN}" \ curl -sf --retry 3 --retry-delay 2 --retry-connrefused -X POST \
-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 "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
--data "$(jq -n \ --data "$(jq -n \
@@ -222,110 +188,99 @@ jobs:
"${API_URL}/contents/.version" "${API_URL}/contents/.version"
fi 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..." echo "Creating new PR..."
curl -sf -X POST \ echo "Waiting for next-release branch to be ready..."
for i in $(seq 1 10); do
BRANCH_STATUS=$(curl -s --retry 3 --retry-delay 2 --retry-connrefused \
-w "%{http_code}" -o /dev/null \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \ "${API_URL}/branches/next-release")
--data "$(jq -n \ if [ "${BRANCH_STATUS}" = "200" ]; then
echo "Branch ready after ${i} attempt(s)"
break
fi
if [ "${i}" = "10" ]; then
echo "Branch next-release not found after 10 attempts, giving up"
exit 1
fi
echo "Branch not ready yet (attempt ${i}/10), waiting..."
sleep 3
done
PR_DATA=$(jq -n \
--arg title "${TITLE}" \ --arg title "${TITLE}" \
--arg body "${DESCRIPTION}" \ --arg body "${DESCRIPTION}" \
--arg head "next-release" \ --arg head "next-release" \
--arg base "${DEFAULT_BRANCH}" \ --arg base "${BASE_BRANCH}" \
'{title: $title, body: $body, head: $head, base: $base}')" \ '{title: $title, body: $body, head: $head, base: $base}')
"${API_URL}/pulls"
fi
prepare-release: for i in $(seq 1 5); do
name: Prepare Release RESPONSE=$(curl -s --retry 3 --retry-delay 2 --retry-connrefused \
-w "\n%{http_code}" -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
--data "${PR_DATA}" \
"${API_URL}/pulls")
HTTP_CODE=$(echo "${RESPONSE}" | tail -1)
BODY=$(echo "${RESPONSE}" | sed '$d')
if [ "${HTTP_CODE}" -lt 400 ]; then
echo "PR created successfully"
break
fi
if [ "${i}" = "5" ]; then
echo "Error creating PR after 5 attempts (HTTP ${HTTP_CODE}): ${BODY}"
exit 1
fi
echo "PR creation attempt ${i}/5 failed (HTTP ${HTTP_CODE}), retrying..."
sleep 3
done
create-release:
name: Create Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: preconditions needs: preconditions
if: | if: |
(github.ref_type == 'branch' && github.ref_name == github.event.repository.default_branch) || github.ref_type == 'branch' &&
github.ref_type == 'tag' github.ref_name == github.event.repository.default_branch &&
container: inputs.tag_only != true
image: orhunp/git-cliff:2.10.1@sha256:6ba0d1fcb051bd7b154cfb19c4b2b3bfa2c22c475f5285fc30606777b6573119
outputs:
version: ${{ steps.version.outputs.version }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Generate changelog - name: Install git-cliff
run: | run: |
if [ "${{ github.ref_type }}" = "tag" ]; then 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
git-cliff --bump --latest --strip header > CHANGES.md sudo mv "git-cliff-${GIT_CLIFF_VERSION}/git-cliff" /usr/local/bin/
else git-cliff --version
git-cliff --bump --unreleased --strip header > CHANGES.md
fi - name: Generate changelog
run: git-cliff --bump --unreleased --strip header > CHANGES.md
- name: Get version - name: Get version
id: version id: version
run: | run: |
VERSION=$(git-cliff --bumped-version 2>/dev/null || echo "") VERSION=$(git-cliff --bumped-version 2>/dev/null || echo "")
echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "${VERSION}" > VERSION
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: release-artifacts
path: |
CHANGES.md
VERSION
create-release:
name: Create Release
runs-on: ubuntu-latest
needs: prepare-release
if: |
github.ref_type == 'branch' &&
github.ref_name == github.event.repository.default_branch &&
inputs.tag_only != true
container:
image: amd64/alpine:3.22.2@sha256:b687e78c6e2785808446f45b52f1540a1e58adc07bdcffea354933b18c613d90
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
run: apk add --no-cache git jq curl
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: release-artifacts
- name: Create release - name: Create release
env: env:
TOKEN: ${{ secrets.UNBOUND_RELEASE_TOKEN }}
REPOSITORY: ${{ github.repository }} REPOSITORY: ${{ github.repository }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
VERSION: ${{ steps.version.outputs.version }}
run: | run: |
TOKEN=$(cat "${RELEASE_TOKEN_FILE}")
if [ ! -r .version ]; then if [ ! -r .version ]; then
echo "Version file not found" echo "Version file not found"
exit 0 exit 0
fi fi
VERSION=$(cat .version 2>/dev/null | jq -r '.version') CURRENT_VERSION=$(cat .version 2>/dev/null | jq -r '.version')
LATEST=$(git describe --abbrev=0 --tags 2>/dev/null || echo '') LATEST=$(git describe --abbrev=0 --tags 2>/dev/null || echo '')
if [ -n "${LATEST}" ] && [ "${VERSION}" = "${LATEST}" ]; then if [ -n "${LATEST}" ] && [ "${CURRENT_VERSION}" = "${LATEST}" ]; then
echo "Version ${VERSION} already exists" echo "Version ${CURRENT_VERSION} already exists"
exit 0 exit 0
fi fi
@@ -333,17 +288,15 @@ jobs:
REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2) REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2)
API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}" API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}"
NAME=$(cat VERSION) MESSAGE=$(cat CHANGES.md)
MESSAGE=$(cat CHANGES.md | jq -Rs .)
MESSAGE="${MESSAGE:1:-1}" # Remove surrounding quotes
echo "Creating release ${NAME}..." echo "Creating release ${VERSION}..."
curl -sf -X POST \ curl -sf --retry 3 --retry-delay 2 --retry-connrefused -X POST \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
--data "$(jq -n \ --data "$(jq -n \
--arg tag_name "${NAME}" \ --arg tag_name "${VERSION}" \
--arg name "${NAME}" \ --arg name "${VERSION}" \
--arg body "${MESSAGE}" \ --arg body "${MESSAGE}" \
--arg target "${DEFAULT_BRANCH}" \ --arg target "${DEFAULT_BRANCH}" \
'{tag_name: $tag_name, name: $name, body: $body, target_commitish: $target}')" \ '{tag_name: $tag_name, name: $name, body: $body, target_commitish: $target}')" \
@@ -352,43 +305,46 @@ jobs:
create-tag: create-tag:
name: Create Tag name: Create Tag
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: prepare-release needs: preconditions
if: | if: |
github.ref_type == 'branch' && github.ref_type == 'branch' &&
github.ref_name == github.event.repository.default_branch && github.ref_name == github.event.repository.default_branch &&
inputs.tag_only == true inputs.tag_only == true
container:
image: amd64/alpine:3.22.2@sha256:b687e78c6e2785808446f45b52f1540a1e58adc07bdcffea354933b18c613d90
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install dependencies - name: Install git-cliff
run: apk add --no-cache git jq curl 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: Download artifacts - name: Get version
uses: actions/download-artifact@v4 id: version
with: run: |
name: release-artifacts VERSION=$(git-cliff --bumped-version 2>/dev/null || echo "")
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Create tag - name: Create tag
env: env:
TOKEN: ${{ secrets.UNBOUND_RELEASE_TOKEN }}
REPOSITORY: ${{ github.repository }} REPOSITORY: ${{ github.repository }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
VERSION: ${{ steps.version.outputs.version }}
run: | run: |
TOKEN=$(cat "${RELEASE_TOKEN_FILE}")
if [ ! -r .version ]; then if [ ! -r .version ]; then
echo "Version file not found" echo "Version file not found"
exit 0 exit 0
fi fi
VERSION=$(cat .version 2>/dev/null | jq -r '.version') CURRENT_VERSION=$(cat .version 2>/dev/null | jq -r '.version')
LATEST=$(git describe --abbrev=0 --tags 2>/dev/null || echo '') LATEST=$(git describe --abbrev=0 --tags 2>/dev/null || echo '')
if [ -n "${LATEST}" ] && [ "${VERSION}" = "${LATEST}" ]; then if [ -n "${LATEST}" ] && [ "${CURRENT_VERSION}" = "${LATEST}" ]; then
echo "Version ${VERSION} already exists" echo "Version ${CURRENT_VERSION} already exists"
exit 0 exit 0
fi fi
@@ -396,15 +352,13 @@ jobs:
REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2) REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2)
API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}" API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}"
NAME=$(cat VERSION) echo "Creating tag ${VERSION}..."
curl -sf --retry 3 --retry-delay 2 --retry-connrefused -X POST \
echo "Creating tag ${NAME}..."
curl -sf -X POST \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
--data "$(jq -n \ --data "$(jq -n \
--arg tag_name "${NAME}" \ --arg tag_name "${VERSION}" \
--arg target "${DEFAULT_BRANCH}" \ --arg target "${DEFAULT_BRANCH}" \
--arg message "${NAME}" \ --arg message "${VERSION}" \
'{tag_name: $tag_name, target: $target, message: $message}')" \ '{tag_name: $tag_name, target: $target, message: $message}')" \
"${API_URL}/tags" "${API_URL}/tags"
+32
View File
@@ -0,0 +1,32 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Repository Overview
This repository contains reusable Gitea Actions workflows for Unbound Software repositories. These workflows are called from other repositories using Gitea's `workflow_call` trigger.
## Architecture
- **Location**: Workflows are stored in `.gitea/workflows/` (not `.github/workflows/`)
- **Platform**: Gitea Actions (compatible with GitHub Actions syntax but runs on Gitea)
- **Runner**: Uses `ubuntu-latest` runner directly (no containers)
- **git-cliff**: Downloaded as binary from GitHub releases, version controlled via `GIT_CLIFF_VERSION` env var
### Release.yml Workflow
The main workflow automates semantic versioning releases using git-cliff for changelog generation:
1. **preconditions**: Validates release token exists at `/runner-secrets/release-token`
2. **changelog**: Generates changelog, determines version bump, checks for changes
3. **handle-pr**: Creates/updates a `next-release` branch and PR with CHANGELOG.md and .version
4. **prepare-release**: Prepares release artifacts when triggered
5. **create-release** or **create-tag**: Creates Gitea release or tag based on `tag_only` input
Version tracking uses a `.version` JSON file containing `{"version":"vX.Y.Z"}`.
## Development Notes
- No build/test commands exist - this is a workflow-only repository
- Workflows use Gitea API directly via curl (not gh CLI)
- Authentication reads from file-based token at `/runner-secrets/release-token`
+3 -5
View File
@@ -20,21 +20,19 @@ on:
jobs: jobs:
release: release:
uses: unboundsoftware/shared-workflows/.gitea/workflows/Release.yml@main uses: unboundsoftware/shared-workflows/.gitea/workflows/Release.yml@main
secrets:
UNBOUND_RELEASE_TOKEN: ${{ secrets.GIT_API_TOKEN }}
``` ```
**Inputs:** **Inputs:**
- `tag_only` (boolean, default: `false`): Set to `true` to only create tags without full releases - `tag_only` (boolean, default: `false`): Set to `true` to only create tags without full releases
**Secrets:** **Requirements:**
- `UNBOUND_RELEASE_TOKEN` (required): Token with API access to create PRs and releases. Required scopes: `repository` (read/write), `issue` (read/write) This workflow reads the release token from `/runner-secrets/release-token`, which is automatically available on Unbound's Gitea runners. No repository secrets need to be configured.
**How it works:** **How it works:**
1. On each push to the default branch, generates a changelog using git-cliff 1. On each push to the default branch, generates a changelog using git-cliff
2. Creates or updates a `next-release` branch with the updated CHANGELOG.md and .version file 2. Creates or updates a `next-release` branch with the updated CHANGELOG.md and .version file
3. Opens or updates a PR titled "chore(release): prepare for vX.Y.Z" 3. Opens or updates a PR titled "chore(release): prepare for vX.Y.Z"
4. When the .version file exists (after merging the release PR), creates a GitHub release with the changelog 4. When the .version file exists (after merging the release PR), creates a Gitea release with the changelog
+11
View File
@@ -2,5 +2,16 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [ "extends": [
"config:recommended" "config:recommended"
],
"customManagers": [
{
"customType": "regex",
"fileMatch": ["^\\.gitea/workflows/.*\\.ya?ml$"],
"matchStrings": [
"GIT_CLIFF_VERSION:\\s*[\"']?(?<currentValue>[^\"'\\s]+)[\"']?"
],
"depNameTemplate": "orhun/git-cliff",
"datasourceTemplate": "github-releases"
}
] ]
} }