From 582960ae06e22f5e515afd6cddd7b2469197c20d Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:30:23 +0200 Subject: [PATCH] ci: publish latest only from stable releases and scope workflow tokens A manual run of the release image workflow tagged `latest` on whatever ref it was dispatched from, and the demo deploy and every Watchtower install follow `latest`. It now moves only for a published stable release or a dispatch on a stable `v*` tag. - `static-lite.yml` gave the whole workflow `contents: write`, including the build job that runs pull-request code. The build is read-only now; a separate release job with no checkout uploads the zips. - `docker-publish.yml` passes the ref name through `env` instead of interpolating it into the shell script. --- .github/workflows/docker-publish-releases.yml | 7 ++-- .github/workflows/docker-publish.yml | 9 +++-- .github/workflows/static-lite.yml | 33 ++++++++++++++----- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docker-publish-releases.yml b/.github/workflows/docker-publish-releases.yml index 292246802..21ddb1216 100644 --- a/.github/workflows/docker-publish-releases.yml +++ b/.github/workflows/docker-publish-releases.yml @@ -1,5 +1,8 @@ name: Publish Docker Image on Release +# `latest` moves only for a published stable release, or for a manual run +# dispatched on a stable release tag (to rebuild one). A dispatch on a +# branch builds nothing that the demo deploy or Watchtower users follow. on: release: types: [published] @@ -101,7 +104,7 @@ jobs: with: images: ${{ env.IMAGE_NAME }} tags: | - type=raw,value=latest,enable=${{ github.event_name != 'release' || !github.event.release.prerelease }} + type=raw,value=latest,enable=${{ (github.event_name == 'release' && !github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')) }} type=semver,pattern=v{{version}} type=semver,pattern={{version}} type=semver,pattern=v{{major}}.{{minor}} @@ -212,7 +215,7 @@ jobs: flavor: | suffix=-always,onlatest=true tags: | - type=raw,value=latest,enable=${{ github.event_name != 'release' || !github.event.release.prerelease }} + type=raw,value=latest,enable=${{ (github.event_name == 'release' && !github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')) }} type=semver,pattern=v{{version}} type=semver,pattern={{version}} type=semver,pattern=v{{major}}.{{minor}} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 6a718fbbf..ff0a6a26a 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -29,11 +29,14 @@ jobs: steps: - name: Set image name id: set + env: + REF_NAME: ${{ github.ref_name }} + REPOSITORY: ${{ github.repository }} run: | - if [ "${{ github.ref_name }}" = "main" ]; then - echo "image_name=ghcr.io/${{ github.repository }}-beta" >> $GITHUB_OUTPUT + if [ "$REF_NAME" = "main" ]; then + echo "image_name=ghcr.io/${REPOSITORY}-beta" >> "$GITHUB_OUTPUT" else - echo "image_name=ghcr.io/${{ github.repository }}-${{ github.ref_name }}" >> $GITHUB_OUTPUT + echo "image_name=ghcr.io/${REPOSITORY}-${REF_NAME}" >> "$GITHUB_OUTPUT" fi build: diff --git a/.github/workflows/static-lite.yml b/.github/workflows/static-lite.yml index 256da7a5d..ca07462ed 100644 --- a/.github/workflows/static-lite.yml +++ b/.github/workflows/static-lite.yml @@ -42,7 +42,7 @@ on: default: false permissions: - contents: write + contents: read jobs: build: @@ -98,21 +98,36 @@ jobs: echo "ZIP=${ZIP}" >> "$GITHUB_ENV" ls -la "${ZIP}" - - name: Upload release asset - if: github.event_name == 'release' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ github.event.release.tag_name }} - run: gh release upload "$TAG_NAME" "$ZIP" --clobber - - name: Upload artifact - if: github.event_name != 'release' uses: actions/upload-artifact@v4 with: name: bulwark-lite-${{ matrix.target }} path: ${{ env.ZIP }} retention-days: 7 + release: + # The only job with a write token, and it runs no checked-out code. + needs: build + if: github.event_name == 'release' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + steps: + - name: Download zips + uses: actions/download-artifact@v4 + with: + pattern: bulwark-lite-* + path: zips + merge-multiple: true + + - name: Upload release assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + TAG_NAME: ${{ github.event.release.tag_name }} + run: gh release upload "$TAG_NAME" zips/*.zip --clobber + smoke: # Boots the demo export on a plain static server and drives it with # Playwright: login, open a thread, switch surfaces, reload a deep link.