mirror of
https://github.com/alphaXiv/OpenResearch.git
synced 2026-10-02 01:34:34 +08:00
fix(release): attach macOS DMG to releases (repo var + PAT cascade + manual dispatch) (#186)
The signed macOS DMG never made it onto releases, so releases/latest/download/OpenResearch.dmg 404s. Two root causes: 1. The cheap gate reads `vars.MACOS_SIGNING_ENABLED`, which only sees repo/org variables, but the variable was created at environment scope. Fixed out-of-band by adding the repo-level variable. 2. `release-on-bump` dispatches `release.yml` with GITHUB_TOKEN, so GitHub's anti-recursion rule suppresses the `workflow_run` cascade and the macOS attach never runs for real releases (only PR dry-runs, which correctly skip). The workflow's comment claiming `workflow_run` fires regardless of the upstream author was wrong. Changes: - release-on-bump.yml: dispatch release.yml with a PAT (RELEASE_DISPATCH_TOKEN) so the Release run is owned by a real token and its completion cascades to the attach. Falls back to GITHUB_TOKEN when the PAT is missing or invalid so a bad PAT never blocks a release (the DMG is then a manual dispatch). - release-macos-app.yml: add a `workflow_dispatch` (tag input) trigger as the manual/recovery path; the gate and checkout handle both trigger types. This is how releases cut before the pipeline worked get their DMG. - DISTRIBUTION.md: document the repo-variable scope, the PAT secret and rotation, the manual re-attach command, and the required-reviewer gate. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7edd229964
commit
4f0fb0bf12
@@ -1,14 +1,15 @@
|
||||
# Attaches the signed, notarized OpenResearch.app (as a DMG) to each GitHub
|
||||
# Release. cargo-dist's "Release" workflow (release.yml) creates the Release with
|
||||
# the default GITHUB_TOKEN, and GitHub's anti-recursion rule blocks a
|
||||
# `release: published` event from a GITHUB_TOKEN action from triggering other
|
||||
# workflows (the same constraint release-on-bump.yml documents). So we trigger on
|
||||
# the Release workflow *completing* via `workflow_run`, which fires regardless of
|
||||
# what authored the upstream run.
|
||||
# Attaches the signed, notarized OpenResearch.app (as a DMG) to a GitHub Release.
|
||||
#
|
||||
# release.yml also runs on pull_request (a dry-run that publishes nothing), so we
|
||||
# only proceed for a successful `workflow_dispatch` run (a real release) and then
|
||||
# confirm the tag's release actually exists before uploading.
|
||||
# Two triggers:
|
||||
# - workflow_run: fires when the "Release" workflow (release.yml) completes —
|
||||
# the automatic path. IMPORTANT: GitHub suppresses workflow_run when the
|
||||
# upstream run was authored by GITHUB_TOKEN (its anti-recursion rule), so
|
||||
# release.yml must be dispatched by a PAT (see release-on-bump.yml) for this
|
||||
# to fire on a real release. release.yml also runs on pull_request (a dry-run
|
||||
# that publishes nothing), so we only proceed for a successful
|
||||
# `workflow_dispatch` run and then confirm the tag's release actually exists.
|
||||
# - workflow_dispatch: manually re-attach the DMG for a given tag — the recovery
|
||||
# path, and how a release cut before this pipeline worked gets its DMG.
|
||||
#
|
||||
# Signing secrets live in the `release-signing` environment (not repo secrets),
|
||||
# so only the reviewed, environment-gated `macos-app` job can read them. The
|
||||
@@ -23,6 +24,12 @@ on:
|
||||
workflow_run:
|
||||
workflows: ["Release"]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release tag to attach the macOS app to (e.g. v0.1.99)
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write # upload release assets
|
||||
@@ -32,12 +39,13 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# Cheap ubuntu gate: only a real, successful release with signing enabled
|
||||
# (the non-secret MACOS_SIGNING_ENABLED variable) and a Release that actually
|
||||
# exists — so a macOS runner (billed 10x) never boots otherwise, and no secret
|
||||
# is ever read here. Resolves the tag from the released commit.
|
||||
# Cheap ubuntu gate: only proceed for signing-enabled runs that map to a real
|
||||
# release — an automatic workflow_run from a successful workflow_dispatch
|
||||
# Release, or a manual workflow_dispatch of this workflow. Keys on the
|
||||
# non-secret MACOS_SIGNING_ENABLED variable and confirms the release exists, so
|
||||
# a macOS runner (billed 10x) never boots otherwise and no secret is read here.
|
||||
check:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'workflow_dispatch' && vars.MACOS_SIGNING_ENABLED == 'true' }}
|
||||
if: ${{ vars.MACOS_SIGNING_ENABLED == 'true' && (github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'workflow_dispatch')) }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
ok: ${{ steps.gate.outputs.ok }}
|
||||
@@ -47,12 +55,20 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
ref: ${{ github.event.workflow_run.head_sha || inputs.tag }}
|
||||
- id: gate
|
||||
env:
|
||||
INPUT_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="$(grep -m1 '^version = ' Cargo.toml | sed -E 's/version = "(.*)"/\1/')"
|
||||
tag="v${version}"
|
||||
# Manual dispatch names the tag; the automatic path derives it from the
|
||||
# released commit's Cargo.toml.
|
||||
if [ -n "$INPUT_TAG" ]; then
|
||||
tag="$INPUT_TAG"
|
||||
else
|
||||
version="$(grep -m1 '^version = ' Cargo.toml | sed -E 's/version = "(.*)"/\1/')"
|
||||
tag="v${version}"
|
||||
fi
|
||||
if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
echo "::notice::No release $tag found (dry-run dispatch?) — nothing to attach."
|
||||
echo "ok=false" >> "$GITHUB_OUTPUT"; exit 0
|
||||
@@ -72,7 +88,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
ref: ${{ github.event.workflow_run.head_sha || inputs.tag }}
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
|
||||
@@ -10,8 +10,16 @@
|
||||
# GITHUB_TOKEN don't trigger other workflows (GitHub's anti-recursion rule),
|
||||
# so a GITHUB_TOKEN-pushed tag would never start release.yml. workflow_dispatch
|
||||
# is the documented exception ("workflow_dispatch and repository_dispatch
|
||||
# events always create workflow runs"), so the default token is sufficient and
|
||||
# no PAT/secret is needed.
|
||||
# events always create workflow runs").
|
||||
#
|
||||
# Why a PAT (RELEASE_DISPATCH_TOKEN) and not the default GITHUB_TOKEN: the
|
||||
# Release run's *completion* must cascade to release-macos-app.yml via
|
||||
# workflow_run, and GitHub applies the same anti-recursion rule there — a run
|
||||
# authored by GITHUB_TOKEN does not fire workflow_run. Dispatching with a PAT
|
||||
# makes the Release run owned by a real token, so its completion triggers the
|
||||
# macOS attach. The PAT needs Actions: read and write (to dispatch release.yml).
|
||||
# If the secret is missing or invalid, releases still publish but the DMG must be
|
||||
# attached manually via release-macos-app.yml's workflow_dispatch. See DISTRIBUTION.md.
|
||||
#
|
||||
# This never increments a version: the version is always a human decision made
|
||||
# in a reviewed PR, and "merge a PR that bumps Cargo.toml" is the release act.
|
||||
@@ -29,7 +37,7 @@ concurrency:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write # to dispatch release.yml
|
||||
actions: write # github.token fallback path dispatches release.yml
|
||||
|
||||
jobs:
|
||||
dispatch:
|
||||
@@ -40,7 +48,12 @@ jobs:
|
||||
|
||||
- name: Dispatch the release workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
# Dispatching with the PAT makes the Release run cascade to the macOS
|
||||
# attach (see header). GITHUB_TOKEN is the fallback so a missing OR
|
||||
# invalid/expired PAT never blocks a release — it just ships without
|
||||
# the auto-attach (recover via release-macos-app.yml's workflow_dispatch).
|
||||
DISPATCH_PAT: ${{ secrets.RELEASE_DISPATCH_TOKEN }}
|
||||
FALLBACK_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -56,6 +69,11 @@ jobs:
|
||||
fi
|
||||
|
||||
echo "Dispatching release.yml with tag=${tag} (version is unreleased)."
|
||||
gh workflow run release.yml --ref main -f tag="${tag}"
|
||||
echo "Dispatched. The release will appear at:"
|
||||
if [ -n "$DISPATCH_PAT" ] && GH_TOKEN="$DISPATCH_PAT" gh workflow run release.yml --ref main -f tag="${tag}"; then
|
||||
echo "Dispatched with RELEASE_DISPATCH_TOKEN — the macOS DMG will attach automatically."
|
||||
else
|
||||
[ -n "$DISPATCH_PAT" ] && echo "::warning::RELEASE_DISPATCH_TOKEN failed; used GITHUB_TOKEN. Release ships but the DMG won't auto-attach — run release-macos-app.yml manually for ${tag}."
|
||||
GH_TOKEN="$FALLBACK_TOKEN" gh workflow run release.yml --ref main -f tag="${tag}"
|
||||
fi
|
||||
echo "The release will appear at:"
|
||||
echo " https://github.com/${GITHUB_REPOSITORY}/actions/workflows/release.yml"
|
||||
|
||||
+29
-3
@@ -2,14 +2,32 @@
|
||||
|
||||
`scripts/build-macos-app.sh` builds the app; `scripts/package-macos-app.sh`
|
||||
signs, notarizes, and packages it into a DMG. CI
|
||||
(`.github/workflows/release-macos-app.yml`) runs both after each release and
|
||||
attaches `OpenResearch.dmg`:
|
||||
(`.github/workflows/release-macos-app.yml`) runs both after a release (see the
|
||||
trigger caveat below) and attaches `OpenResearch.dmg`:
|
||||
|
||||
```
|
||||
https://github.com/alphaXiv/openresearch-cli/releases/latest/download/OpenResearch.dmg
|
||||
```
|
||||
|
||||
The release job is a no-op until the `MACOS_SIGNING_ENABLED` variable is `true`.
|
||||
The release job is a no-op until the **repository** variable
|
||||
`MACOS_SIGNING_ENABLED` is `true` (a repo variable, not an environment one — the
|
||||
cheap gate job has no `environment:` and only sees repo/org variables).
|
||||
|
||||
The attach runs automatically only when the `Release` workflow was dispatched by
|
||||
a PAT (see below); GitHub suppresses the `workflow_run` cascade for runs authored
|
||||
by `GITHUB_TOKEN`. To attach a DMG to a release that missed it, run the **Attach
|
||||
macOS app to release** workflow manually (Actions → Run workflow → enter the tag,
|
||||
e.g. `v0.1.99`), or:
|
||||
|
||||
```bash
|
||||
gh workflow run release-macos-app.yml -f tag=v0.1.99
|
||||
```
|
||||
|
||||
The manual path checks out `inputs.tag` and runs the signing scripts under the
|
||||
`release-signing` environment, so the **required reviewer approving the run is
|
||||
the real gate on the certificate** — verify the tag points at trusted code (the
|
||||
`main`-only deployment-branch restriction covers the workflow file, not the
|
||||
checked-out tag).
|
||||
|
||||
## Configure signing (CI)
|
||||
|
||||
@@ -32,6 +50,14 @@ From it you produce the six values below.
|
||||
|
||||
2. Set repo **variable** `MACOS_SIGNING_ENABLED = true` to switch the pipeline on.
|
||||
|
||||
3. Add repo **secret** `RELEASE_DISPATCH_TOKEN` — a fine-grained PAT scoped to
|
||||
this repo with **Actions: read and write** (classic: `repo` + `workflow`).
|
||||
`release-on-bump.yml` dispatches `release.yml` with it so the Release run is
|
||||
owned by a real token and its completion cascades to the macOS attach. If it's
|
||||
missing or invalid, releases still ship but the DMG is a manual dispatch.
|
||||
Fine-grained PATs expire (≤1yr) — rotate it before then, or releases keep
|
||||
shipping without the auto-attach until it's renewed.
|
||||
|
||||
Also enable **Require a pull request** + **Require review from Code Owners** on
|
||||
`main` (see `.github/CODEOWNERS`) so the signing scripts can't change unreviewed.
|
||||
Never commit the `.p12`.
|
||||
|
||||
Reference in New Issue
Block a user