From e17b1680bdb32d9edf12179d2bcebc879ecd1556 Mon Sep 17 00:00:00 2001 From: Mikhail Yenuchenko Date: Fri, 14 Aug 2026 19:48:20 +0300 Subject: [PATCH] ci: dispatch release build from release-on-publish workflow GitHub recorded release published at 16:44:21Z but no release.yml run started. A thin release-on-publish workflow listens for publish and starts release.yml via workflow_dispatch (always creates a run). release.yml now checks out vX.Y.Z from the version input, not main HEAD. Co-Authored-By: Cursor --- .github/workflows/release-on-publish.yml | 46 ++++++++++++++++++++++++ .github/workflows/release.yml | 30 ++++++---------- docs/development.md | 20 +++++++---- 3 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/release-on-publish.yml diff --git a/.github/workflows/release-on-publish.yml b/.github/workflows/release-on-publish.yml new file mode 100644 index 0000000..483ede6 --- /dev/null +++ b/.github/workflows/release-on-publish.yml @@ -0,0 +1,46 @@ +name: release-on-publish + +# Listens for a published GitHub Release and starts release.yml via +# workflow_dispatch. GitHub documents that workflow_dispatch always creates a +# new run (even when triggered by GITHUB_TOKEN), unlike some release:published +# deliveries that never started release.yml in this repo (see repo events API +# 2026-08-14T16:44:21Z vs Actions run list). + +on: + release: + types: [published] + +permissions: + actions: write + contents: read + +jobs: + dispatch: + runs-on: ubuntu-latest + steps: + - name: Derive SemVer from release tag + id: version + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + set -euo pipefail + raw="${RELEASE_TAG#v}" + if ! [[ "$raw" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::release tag is not SemVer vX.Y.Z (${RELEASE_TAG})" + exit 1 + fi + #region agent log + echo "::group::agent-log release-on-publish" + echo "release_tag=${RELEASE_TAG} version=${raw} release_id=${{ github.event.release.id }} draft=${{ github.event.release.draft }}" + echo "::endgroup::" + #endregion + echo "version=${raw}" >> "$GITHUB_OUTPUT" + + - name: Start release workflow + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh workflow run release.yml \ + --repo "${{ github.repository }}" \ + --ref main \ + -f "version=${{ steps.version.outputs.version }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ca2d64..edb5832 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,10 @@ name: release # Publishes an immutable, version-tagged image on ghcr.io (spec 10.1). -# Ordinary commits and bare git tag pushes do not publish anything. The -# workflow runs when a GitHub Release is published (not while it is still a -# draft), or on workflow_dispatch with an explicit SemVer X.Y.Z version. A -# dispatch from main without that input must fail in prepare — it must not -# publish ghcr.io/...:main. The version is the single source that goes into -# both the image tag and the panel binary's -ldflags version, so the two can -# never drift apart (the invariant restore's version check in spec 7.5.A -# depends on). +# Ordinary commits and bare git tag pushes do not publish anything. A published +# GitHub Release starts release-on-publish.yml, which dispatches this workflow +# with the release version. You can also run it manually via workflow_dispatch +# with an explicit SemVer X.Y.Z version. # # Native per-architecture builds (see docs/development.md), not qemu: # running the full Postfix/OpenDKIM stack under emulation for the e2e gate @@ -20,13 +16,11 @@ name: release # in the registry as a side effect (harmless — the version tag's immutability, # spec 10.1, is about that tag, not these). on: - release: - types: [published] workflow_dispatch: inputs: version: - description: "Image version as X.Y.Z (no v prefix). Required on workflow_dispatch." - required: false + description: "Image version as X.Y.Z (no v prefix)." + required: true type: string permissions: @@ -43,22 +37,18 @@ jobs: id: version env: INPUT_VERSION: ${{ github.event.inputs.version }} - RELEASE_TAG: ${{ github.event.release.tag_name }} EVENT_NAME: ${{ github.event_name }} run: | set -euo pipefail raw="${INPUT_VERSION:-}" - if [ -z "$raw" ] && [ "${EVENT_NAME}" = "release" ] && [ -n "${RELEASE_TAG:-}" ]; then - raw="${RELEASE_TAG}" - fi raw="${raw#v}" if ! [[ "$raw" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::release version is not SemVer X.Y.Z (event=${EVENT_NAME}; release tag=${RELEASE_TAG:-none}; pass inputs.version on workflow_dispatch). Refusing to publish ghcr.io/${{ github.repository }}:${raw:-?}" + echo "::error::release version is not SemVer X.Y.Z (event=${EVENT_NAME}; pass inputs.version on workflow_dispatch). Refusing to publish ghcr.io/${{ github.repository }}:${raw:-?}" exit 1 fi #region agent log echo "::group::agent-log prepare version" - echo "event=${EVENT_NAME} release_tag=${RELEASE_TAG:-none} version=${raw}" + echo "event=${EVENT_NAME} version=${raw} checkout_ref=v${raw}" echo "::endgroup::" #endregion echo "version=${raw}" >> "$GITHUB_OUTPUT" @@ -77,7 +67,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.release.tag_name || github.ref }} + ref: v${{ needs.prepare.outputs.version }} - uses: docker/setup-buildx-action@v3 @@ -144,7 +134,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.release.tag_name || github.ref }} + ref: v${{ needs.prepare.outputs.version }} - name: Debug — snapshot GitHub Release state (agent) env: diff --git a/docs/development.md b/docs/development.md index b2aeac7..9121540 100644 --- a/docs/development.md +++ b/docs/development.md @@ -205,8 +205,9 @@ the binaries so they cannot drift apart. local-trial image references) in the **same** release commit. 2. Create and push git tag `vX.Y.Z` on that commit. 3. Publish the GitHub Release for `vX.Y.Z` (not a draft). -4. Workflow [release.yml](../.github/workflows/release.yml) builds, e2e-gates, - and publishes `ghcr.io/mixeme/selfpost:X.Y.Z`. +4. Workflow [release-on-publish.yml](../.github/workflows/release-on-publish.yml) + starts [release.yml](../.github/workflows/release.yml) with that version; + the build checks out tag `vX.Y.Z` (not `main` HEAD). **GitHub Release vs GHCR.** The public [Releases](https://github.com/mixeme/selfpost/releases) page lists only **published** releases. A draft is visible to maintainers only — @@ -306,13 +307,18 @@ Workflows in [.github/workflows/](../.github/workflows/). What each job runs — ### `release.yml` — published GitHub Release, or `workflow_dispatch` with SemVer -`prepare` takes the version from the published release tag (`v1.2.5` → `1.2.5`) -or from the `workflow_dispatch` `version` input. A bare git tag push does not -run this workflow. A dispatch whose input is missing or not `X.Y.Z` fails in -`prepare` — it must not publish `ghcr.io/...:main`. +Publishing a GitHub Release runs [release-on-publish.yml](../.github/workflows/release-on-publish.yml), +which dispatches `release.yml` with the version parsed from the release tag. +You can also run `release.yml` manually via `workflow_dispatch` and an +explicit `X.Y.Z` input. A bare git tag push does not run either workflow. +The build always checks out `vX.Y.Z`, not `main` HEAD. + +`prepare` takes the version from the `workflow_dispatch` `version` input. A +dispatch whose input is missing or not `X.Y.Z` fails in `prepare`. ``` -prepare (version from published release tag or workflow_dispatch input) +release-on-publish (release: published → workflow_dispatch) +prepare (version from workflow_dispatch input; checkout vX.Y.Z) → build [matrix: ubuntu-latest / ubuntu-24.04-arm] → docker build --load (VERSION from prepare) → e2e (test/e2e)