From b706c031ea1bef9da5a38dd166e642d103ec9e11 Mon Sep 17 00:00:00 2001 From: James Mackay Date: Thu, 8 Oct 2026 07:32:08 +0100 Subject: [PATCH] ci: streamline Gitea release workflow under .gitea/ (GITEA_TOKEN, concurrency, no HACS mirror job) --- .gitea/workflows/release.yml | 158 ++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 158 ---------------------------------- 2 files changed, 158 insertions(+), 158 deletions(-) create mode 100644 .gitea/workflows/release.yml delete mode 100644 .github/workflows/release.yml diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..4e20955 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,158 @@ +# Publish a date-based release every time master moves. +# +# Versioning is date-based: vYYYY.MM.DD. Priority rule — an unreleased `version` +# declared in custom_components/nvidia_shield/manifest.json wins (bump the +# manifest to force a specific release, e.g. after a meaningful change); otherwise +# the version is today's date (UTC), so a plain push to master publishes the +# day's release. Repeated pushes on the same day are deduped by tag existence. +# +# The Gitea release carries a nvidia_shield.zip asset (custom_components/ +# nvidia_shield/) so the integration is installable manually from any release. +# +# No hand-made secrets are required: Gitea mints a token for every job and +# exposes it as secrets.GITEA_TOKEN. + +name: Release + +on: + push: + branches: [master] + # Manual runs, for the first release (no tags yet) or to re-publish after a + # partial failure. + workflow_dispatch: + +# One release at a time. Two pushes in quick succession would otherwise race to +# tag the same commit; a later run dedupes safely instead of interleaving. +concurrency: + group: release-master + cancel-in-progress: false + +permissions: + # The job token pushes the tag and creates the release. + contents: write + +env: + ZIP_NAME: nvidia_shield.zip + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # Tags drive versioning decisions, so the clone must carry them. + fetch-depth: 0 + ref: master + token: ${{ secrets.GITEA_TOKEN }} + + - name: Determine release version + id: version + run: | + set -euo pipefail + MANIFEST_VERSION="$(jq -r .version custom_components/nvidia_shield/manifest.json)" + DATE_VERSION="$(date -u +%Y.%m.%d)" + # An unreleased manifest version wins over today's date. + if git rev-parse -q --verify "refs/tags/v${MANIFEST_VERSION}" >/dev/null; then + VERSION="${DATE_VERSION}" + else + VERSION="${MANIFEST_VERSION}" + fi + echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + if git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then + echo "v${VERSION} is already tagged — nothing to release" + echo "exists=yes" >> "$GITHUB_OUTPUT" + else + echo "releasing v${VERSION}" + echo "exists=no" >> "$GITHUB_OUTPUT" + fi + + - name: Create tag + if: steps.version.outputs.exists == 'no' + run: | + set -euo pipefail + git config user.name "gitea-actions" + git config user.email "actions@notexpectedyet.com" + git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}" + git push origin "refs/tags/${{ steps.version.outputs.tag }}" + + - name: Build the release zip + if: steps.version.outputs.exists == 'no' + run: | + set -euo pipefail + cd custom_components + zip -r "../${ZIP_NAME}" nvidia_shield + + - name: Publish the release + if: steps.version.outputs.exists == 'no' + env: + VERSION: ${{ steps.version.outputs.version }} + TAG: ${{ steps.version.outputs.tag }} + SERVER: ${{ github.server_url }} + REPO: ${{ github.repository }} + TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -euo pipefail + + # Notes: subject lines of the commits in the released range. + PREV="$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || true)" + if [ -n "$PREV" ]; then + RANGE="${PREV}..${TAG}" + else + RANGE="${TAG}" + fi + { + echo "Release ${TAG}" + echo + git log --pretty=format:'- %s' "$RANGE" + } > /tmp/notes.md + + # The body is built by an encoder rather than by string interpolation: + # release notes are multi-line markdown and quoting them into JSON by + # hand is a reliable way to publish a malformed release. + python3 - <<'PY' > /tmp/release.json + import json, os + with open('/tmp/notes.md', encoding='utf-8') as handle: + body = handle.read() + print(json.dumps({ + 'tag_name': 'v' + os.environ['VERSION'], + 'name': 'v' + os.environ['VERSION'], + 'body': body, + 'draft': False, + 'prerelease': False, + })) + PY + + status="$(curl -sS -o /tmp/release-response.json -w '%{http_code}' \ + -X POST "${SERVER}/api/v1/repos/${REPO}/releases" \ + -H "Authorization: token ${TOKEN}" \ + -H 'Content-Type: application/json' \ + --data-binary @/tmp/release.json)" + + RELEASE_ID="$(jq -r '.id // empty' /tmp/release-response.json)" + # 409 is "a release for this tag already exists": resolve the existing + # release instead of failing, so the zip still gets attached. + if [ -z "$RELEASE_ID" ]; then + RELEASE_ID="$(curl -sS "${SERVER}/api/v1/repos/${REPO}/releases/tags/${TAG}" \ + -H "Authorization: token ${TOKEN}" | jq -r '.id')" + else + if [ "$status" != "201" ] && [ "$status" != "200" ]; then + echo "release creation failed (HTTP ${status})" >&2 + cat /tmp/release-response.json >&2 + exit 1 + fi + fi + case "$RELEASE_ID" in + ''|*[!0-9]*) + echo "could not resolve a release id for ${TAG} (HTTP ${status})" >&2 + cat /tmp/release-response.json >&2 + exit 1 + ;; + esac + echo "release id: ${RELEASE_ID}" + + curl -sS -o /tmp/asset-response.json \ + -X POST "${SERVER}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \ + -H "Authorization: token ${TOKEN}" \ + -F "attachment=@${ZIP_NAME}" + echo "published ${TAG} with ${ZIP_NAME}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 5c4459c..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,158 +0,0 @@ -name: Release - -on: - push: - branches: [master] - workflow_dispatch: - -env: - ZIP_NAME: nvidia_shield.zip - -jobs: - # Tag + release on Gitea (https://git.notexpectedyet.com/notexpectedyet/ha-nvidia-shield). - # Always runs; this is the source-of-truth release. - gitea-release: - runs-on: ubuntu-latest - permissions: - contents: write - outputs: - tag: ${{ steps.version.outputs.tag }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - # Date-based versioning: vYYYY.MM.DD. - # Priority: an unreleased manifest version wins (bump manifest.json to - # force a specific version); otherwise fall back to today's date, so a - # plain push to master produces/updates the day's release. The - # tag-exists check below dedupes same-day pushes. - - name: Determine release version - id: version - run: | - MANIFEST_VERSION=$(jq -r .version custom_components/nvidia_shield/manifest.json) - DATE_VERSION=$(date -u +%Y.%m.%d) - if git ls-remote --tags origin "refs/tags/v$MANIFEST_VERSION" | grep -q .; then - VERSION="$DATE_VERSION" - else - VERSION="$MANIFEST_VERSION" - fi - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" - - - name: Check if tag already exists - id: check - run: | - if git ls-remote --tags origin "refs/tags/${{ steps.version.outputs.tag }}" | grep -q .; then - echo "exists=true" >> "$GITHUB_OUTPUT" - else - echo "exists=false" >> "$GITHUB_OUTPUT" - fi - - - name: Create tag - if: steps.check.outputs.exists == 'false' - run: | - git config user.name "release-bot" - git config user.email "release-bot@notexpectedyet.com" - git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}" - git push origin "${{ steps.version.outputs.tag }}" - - - name: Build release zip - if: steps.check.outputs.exists == 'false' - run: | - cd custom_components - zip -r "../${ZIP_NAME}" nvidia_shield - - # Gitea Actions sets GITHUB_API_URL (https:///api/v1) and - # GITHUB_TOKEN for compatibility; no secrets needed for the Gitea release. - - name: Create Gitea release and upload zip - if: steps.check.outputs.exists == 'false' - env: - TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG: ${{ steps.version.outputs.tag }} - REPO: ${{ github.repository }} - run: | - API_URL="${GITHUB_API_URL:-$(echo "$GITHUB_SERVER_URL" | sed 's#/$##')/api/v1}" - echo "API: $API_URL, repo: $REPO, tag: $TAG" - - # Release notes: subjects of commits since the previous tag - PREV=$(git describe --tags --abbrev=0 "$TAG^" 2>/dev/null || true) - if [ -n "$PREV" ]; then - RANGE="$PREV..$TAG" - else - RANGE="$TAG" - fi - NOTES=$(git log --pretty=format:'- %s' "$RANGE" | head -30) - PAYLOAD=$(jq -n --arg tag "$TAG" --arg body "$NOTES" \ - '{tag_name:$tag, name:$tag, body:$body, draft:false, prerelease:false}') - - # Create the release; if one already exists for this tag, reuse it - CREATE_RESP=$(curl -s -X POST "$API_URL/repos/$REPO/releases" \ - -H "Authorization: token $TOKEN" \ - -H "Content-Type: application/json" \ - -d "$PAYLOAD") - RELEASE_ID=$(echo "$CREATE_RESP" | jq -r '.id // empty') - if [ -z "$RELEASE_ID" ]; then - echo "Release create returned no id (already exists?) — looking up by tag" - RELEASE_ID=$(curl -s "$API_URL/repos/$REPO/releases/tags/$TAG" \ - -H "Authorization: token $TOKEN" | jq -r '.id') - fi - echo "Release id: $RELEASE_ID" - - curl -s -f -X POST "$API_URL/repos/$REPO/releases/$RELEASE_ID/assets?name=$ZIP_NAME" \ - -H "Authorization: token $TOKEN" \ - -F "attachment=@${ZIP_NAME}" > /dev/null - echo "Uploaded $ZIP_NAME to $TAG" - - # Optional: mirror the release to a public GitHub repository so HACS can - # distribute it (HACS only supports public GitHub repos). - # Required secrets: - # GH_MIRROR_TOKEN — GitHub PAT with repo (contents) scope - # GH_MIRROR_REPO — target repo, e.g. "notexpectedyet/ha-nvidia-shield" - hacs-github-mirror: - runs-on: ubuntu-latest - needs: gitea-release - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Push mirror and create GitHub release - env: - GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }} - GH_REPO: ${{ secrets.GH_MIRROR_REPO }} - TAG: ${{ needs.gitea-release.outputs.tag }} - run: | - if [ -z "$GH_TOKEN" ] || [ -z "$GH_REPO" ]; then - echo "GH_MIRROR_TOKEN / GH_MIRROR_REPO not set — skipping GitHub mirror." - exit 0 - fi - - git remote add github "https://x-access-token:${GH_TOKEN}@github.com/${GH_REPO}.git" - git push github HEAD:master - - if git push github "refs/tags/$TAG" 2>/dev/null; then - echo "Pushed tag $TAG to GitHub" - else - echo "Tag $TAG already exists on GitHub" - fi - - RESP=$(curl -s -X POST "https://api.github.com/repos/${GH_REPO}/releases" \ - -H "Authorization: Bearer $GH_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - -d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":\"Release $TAG\",\"draft\":false,\"prerelease\":false}") - RELEASE_ID=$(echo "$RESP" | jq -r '.id // empty') - if [ -z "$RELEASE_ID" ]; then - echo "GitHub release create failed or already exists:" - echo "$RESP" - exit 0 - fi - - cd custom_components - zip -r "../${ZIP_NAME}" nvidia_shield - cd .. - - curl -s -f -X POST "https://uploads.github.com/repos/${GH_REPO}/releases/${RELEASE_ID}/assets?name=$ZIP_NAME" \ - -H "Authorization: Bearer $GH_TOKEN" \ - -F "attachment=@${ZIP_NAME}" > /dev/null - echo "Uploaded $ZIP_NAME to GitHub release $TAG"