Replace rolling canary release each run for immutable-release repos #4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Canary releases | |
| on: | |
| push: | |
| branches: | |
| - main | |
| permissions: | |
| contents: write | |
| packages: write | |
| concurrency: | |
| group: canary-main | |
| cancel-in-progress: true | |
| jobs: | |
| release-canary: | |
| name: Publish canary artifacts | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Checkout source code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Guard - workflow SHA must match main HEAD | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| main_head=$(gh api "repos/${REPO}/git/ref/heads/main" --jq .object.sha) | |
| if [[ "$main_head" != "$SHA" ]]; then | |
| echo "::error::main HEAD ($main_head) differs from workflow SHA ($SHA); refusing to publish canary" | |
| exit 1 | |
| fi | |
| - name: Setup project and build environment | |
| uses: ./.github/actions/common-setup | |
| - name: Package application | |
| run: ./gradlew installDist | |
| - name: Create ZIP file from the directory | |
| run: zip -r octi-server-canary.zip ./build/install/octi-server | |
| - name: Prepare canary metadata | |
| id: canary-meta | |
| env: | |
| REPO: ${{ github.repository }} | |
| SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| short_sha="${SHA:0:7}" | |
| tag="canary" | |
| title="Canary (${short_sha})" | |
| notes_file="canary-notes.md" | |
| { | |
| printf '## Octi Server Canary\n\n' | |
| printf 'This is a rolling bleeding-edge pre-release built from `main`.\n\n' | |
| printf -- '- Commit: `%s`\n' "$SHA" | |
| printf -- '- Docker tags: `ghcr.io/%s:canary`, `ghcr.io/%s:sha-%s`\n\n' "$REPO" "$REPO" "$short_sha" | |
| printf 'Do not use this build for production unless you accept breakage risk.\n' | |
| } > "$notes_file" | |
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| echo "title=$title" >> "$GITHUB_OUTPUT" | |
| echo "notes_file=$notes_file" >> "$GITHUB_OUTPUT" | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a #v4.0.0 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd #v4.0.0 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 #v4.1.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Generate Docker metadata | |
| id: docker-meta | |
| uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf #v6.0.0 | |
| with: | |
| images: ghcr.io/${{ github.repository }} | |
| tags: | | |
| type=raw,value=canary | |
| type=sha,format=short,prefix=sha- | |
| labels: | | |
| org.opencontainers.image.title=Octi Server Canary | |
| org.opencontainers.image.description=Bleeding edge synchronization server for Octi | |
| org.opencontainers.image.vendor=d4rken-org | |
| org.opencontainers.image.version=canary | |
| env: | |
| DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index | |
| - name: Guard - main HEAD must still match before publishing | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| main_head=$(gh api "repos/${REPO}/git/ref/heads/main" --jq .object.sha) | |
| if [[ "$main_head" != "$SHA" ]]; then | |
| echo "::error::main HEAD ($main_head) advanced past workflow SHA ($SHA); aborting before Docker push" | |
| exit 1 | |
| fi | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 #v7.0.0 | |
| with: | |
| context: . | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| tags: ${{ steps.docker-meta.outputs.tags }} | |
| labels: ${{ steps.docker-meta.outputs.labels }} | |
| annotations: ${{ steps.docker-meta.outputs.annotations }} | |
| # GitHub's immutable-releases feature on this repo prevents editing existing releases | |
| # and their tags. Rolling canary is therefore implemented as delete-then-create on | |
| # every run. Per .claude/rules/release.md, tag rulesets cover only `main` and `v*`, | |
| # so deletion of `canary` via GITHUB_TOKEN is permitted. | |
| - name: Replace rolling canary release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| SHA: ${{ github.sha }} | |
| TAG: ${{ steps.canary-meta.outputs.tag }} | |
| TITLE: ${{ steps.canary-meta.outputs.title }} | |
| NOTES_FILE: ${{ steps.canary-meta.outputs.notes_file }} | |
| run: | | |
| set -euo pipefail | |
| main_head=$(gh api "repos/${REPO}/git/ref/heads/main" --jq .object.sha) | |
| if [[ "$main_head" != "$SHA" ]]; then | |
| echo "::error::main HEAD ($main_head) advanced past workflow SHA ($SHA); refusing to replace canary" | |
| exit 1 | |
| fi | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| gh release delete "$TAG" --yes | |
| fi | |
| if gh api "repos/${REPO}/git/ref/tags/${TAG}" >/dev/null 2>&1; then | |
| gh api --method DELETE "repos/${REPO}/git/refs/tags/${TAG}" >/dev/null | |
| fi | |
| gh release create "$TAG" octi-server-canary.zip \ | |
| --title "$TITLE" --notes-file "$NOTES_FILE" \ | |
| --prerelease --target "$SHA" |