From 04fee284a528907b20726092f7b100a173af6ceb Mon Sep 17 00:00:00 2001 From: "personal-stack-agents[bot]" Date: Mon, 8 Jun 2026 12:28:34 +0000 Subject: [PATCH 1/2] feat(release): add release-please tagging, version-tagged images, Renovate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scaffolds the version-pinned model (the production Flux pinning + Keel removal is a separate, sequenced PR — see docs/runbooks/versioned-deploy.md): - release-please (release-type simple) tags vX.Y.Z via .github/workflows/release.yml. - build-and-publish also builds/pushes in-house images tagged with the release version on v*.*.* tag pushes (force-all so every service gets the tag), keeping the existing :sha/:latest behaviour. - renovate.json pins exact versions and groups the ExtraToast shared artifacts. - docs/runbooks/versioned-deploy.md documents deploy = reconcile pinned tag, rollback = git revert, and the Keel-removal sequencing. Authored by a parallel Codex fleet; reviewed manually. Part of #609. Part of #610. --- .github/workflows/build-and-publish.yml | 29 +++++++++++++------- .github/workflows/release.yml | 23 ++++++++++++++++ .release-please-manifest.json | 3 +++ docs/runbooks/versioned-deploy.md | 36 +++++++++++++++++++++++++ release-please-config.json | 10 +++++++ renovate.json | 26 ++++++++++++++++++ 6 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .release-please-manifest.json create mode 100644 docs/runbooks/versioned-deploy.md create mode 100644 release-please-config.json create mode 100644 renovate.json diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index ad9c8df3..936e0793 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -1,5 +1,5 @@ -# Builds + publishes container images to ghcr.io with `:latest` and -# `:` tags. Cluster rollout is handled out-of-band: Keel polls +# Builds + publishes container images to ghcr.io with `:latest`, +# `:`, and release tags. Cluster rollout is handled out-of-band: Keel polls # ghcr every 2 minutes and rolls the matching Deployment when the # `:latest` digest changes, while Flux reconciles manifest changes # from `main`. Neither is this workflow's responsibility — the name @@ -9,6 +9,7 @@ name: Build & Publish on: push: branches: [main] + tags: ['v*.*.*'] # Manual trigger for the cases where a push event never fires the # workflow (rare GitHub oddity — observed once with the `#308` # merge: workflow silently absent for that SHA) or where a merge @@ -52,7 +53,7 @@ jobs: - uses: dorny/paths-filter@v4.0.1 id: filter with: - base: ${{ github.event.before }} + base: ${{ github.ref_type == 'tag' && github.sha || github.event.before }} filters: | workflow: - '.github/workflows/build-and-publish.yml' @@ -136,11 +137,8 @@ jobs: # # Multi-arch is gated on `main` only. The arm64 leg builds under # QEMU emulation and roughly doubles the UI build time; we don't - # want to pay that on every reconcile / dispatch / future trigger - # that someone might add. Today the workflow's `on:` only fires - # for push to main, but this guard makes the intent explicit and - # forward-safe — if you add workflow_dispatch later, feature - # branches still build amd64-only. + # want to pay that on every reconcile, dispatch, or release-tag + # trigger; feature branches and tags still build amd64-only. AMD64=linux/amd64 if [[ "${{ github.ref }}" == "refs/heads/main" && "${{ github.event_name }}" == "push" ]]; then UI_PLATFORMS=linux/amd64,linux/arm64 @@ -150,8 +148,12 @@ jobs: fi # `force_all=true` on workflow_dispatch overrides every - # per-service filter so all images rebuild in one go. + # per-service filter so all images rebuild in one go. Release tags + # also rebuild every service so each image gets the version tag. FORCE_ALL="${{ github.event.inputs.force_all || 'false' }}" + if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" == v*.*.* ]]; then + FORCE_ALL=true + fi [[ "$(any "$FORCE_ALL" "$KOTLIN_SHARED" "$FILTER_AUTH_API")" == "true" ]] && append auth-api services/auth-api/Dockerfile "$AMD64" [[ "$(any "$FORCE_ALL" "$KOTLIN_SHARED" "$FILTER_ASSISTANT_API")" == "true" ]] && append assistant-api services/assistant-api/Dockerfile "$AMD64" @@ -197,6 +199,14 @@ jobs: - uses: actions/checkout@v6.0.2 - name: Set image prefix (lowercase) run: echo "IMAGE_PREFIX=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV" + - name: Compute release version + id: release-version + run: | + if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" == v*.*.* ]]; then + echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + else + echo "version=" >> "$GITHUB_OUTPUT" + fi # QEMU is only needed when a service builds for a non-runner arch # (the UIs build linux/arm64 alongside amd64). No-op for the # amd64-only JVM services. @@ -225,5 +235,6 @@ jobs: tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ matrix.service.name }}:${{ github.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ matrix.service.name }}:latest + ${{ steps.release-version.outputs.version && format('{0}/{1}/{2}:{3}', env.REGISTRY, env.IMAGE_PREFIX, matrix.service.name, steps.release-version.outputs.version) || '' }} cache-from: type=gha,scope=${{ matrix.service.name }} cache-to: type=gha,mode=max,scope=${{ matrix.service.name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..613855a9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,23 @@ +name: Release + +on: + push: + branches: [main] + +permissions: + contents: write + pull-requests: write + +concurrency: + group: release-please + cancel-in-progress: false + +jobs: + release: + name: Release Please + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..466df71c --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.1.0" +} diff --git a/docs/runbooks/versioned-deploy.md b/docs/runbooks/versioned-deploy.md new file mode 100644 index 00000000..a928e092 --- /dev/null +++ b/docs/runbooks/versioned-deploy.md @@ -0,0 +1,36 @@ +# Versioned Deploy Model + +Production deploys are driven by explicit version pins in Git, not by pushes to +`main` or mutable `:latest` tags. + +## Target Model + +- `main` is the integration branch. Merging to `main` does not by itself define + the production version. +- release-please creates release tags in the form `vX.Y.Z`. +- `build-and-publish` publishes in-house container images with version tags that + match the release tag. +- A release PR bumps explicit image tags under `platform/cluster/flux/**` from + the previous version to the new version. +- Flux deploys the commit that pins those image tags. +- Rollback is a Git revert of the release PR that bumped the image tags. + +## Deploying a Version + +To deploy a specific release, reconcile the commit that pins the desired +`vX.Y.Z` image tags in `platform/cluster/flux/**`. The deployed state is the +Flux-applied Git state, so the image tags in the cluster manifests are the +source of truth. + +## Rollback + +Rollback is performed by reverting the tag-bump commit or PR. Flux reconciles +the reverted manifests and returns the affected workloads to the previously +pinned image versions. + +## Keel Removal Sequencing + +Keel-based `:latest` auto-rolls are removed from the in-house app deploy path in +a separate, sequenced PR. That Flux/Keel change must land only after the first +versioned in-house images have been published, so every workload can be pinned +to an existing version tag before `:latest` automation is removed. diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..a422ca91 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,10 @@ +{ + "packages": { + ".": { + "release-type": "simple", + "bump-minor-pre-major": true, + "changelog-path": "CHANGELOG.md", + "include-component-in-tag": false + } + } +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..3ec76d2a --- /dev/null +++ b/renovate.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":semanticCommits", + ":dependencyDashboard" + ], + "rangeStrategy": "pin", + "labels": ["dependencies"], + "minimumReleaseAge": "3 days", + "packageRules": [ + { + "description": "Group all ExtraToast shared artifacts so a coherent platform bump lands in one PR.", + "matchPackagePatterns": ["^dev\\.extratoast", "^@extratoast/"], + "matchDepNames": ["ExtraToast/github-workflows"], + "groupName": "extratoast shared artifacts", + "labels": ["dependencies", "extratoast-platform"] + }, + { + "description": "Pin GitHub Actions to immutable refs.", + "matchManagers": ["github-actions"], + "pinDigests": true + } + ], + "lockFileMaintenance": { "enabled": true } +} From 64fa98a776ffd9b53790fe72feebd752cc3f4f5f Mon Sep 17 00:00:00 2001 From: "personal-stack-agents[bot]" Date: Mon, 8 Jun 2026 12:29:40 +0000 Subject: [PATCH 2/2] style: prettier-format renovate.json --- renovate.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/renovate.json b/renovate.json index 3ec76d2a..b21816ab 100644 --- a/renovate.json +++ b/renovate.json @@ -1,10 +1,6 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended", - ":semanticCommits", - ":dependencyDashboard" - ], + "extends": ["config:recommended", ":semanticCommits", ":dependencyDashboard"], "rangeStrategy": "pin", "labels": ["dependencies"], "minimumReleaseAge": "3 days",