From 2b3bc36f9c3eb015d488bed32c9bf50ce59e1498 Mon Sep 17 00:00:00 2001 From: Jhen-Jie Hong Date: Thu, 13 Aug 2026 11:36:59 +0800 Subject: [PATCH] Publish releases from this repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The artifacts split existed because a private source repo cannot serve the anonymous downloads Homebrew and `curl | sh` need; this repository is public, so `multiplex-cli-releases` has nothing left to do. The publish job now targets `github.repository` under GITHUB_TOKEN — the whole cross-repo RELEASE_TOKEN dance was only ever paying for that split — and takes `contents: write` on itself alone, leaving the build matrix that runs third-party toolchains read-only. v0.1.0–v0.1.2 are mirrored here byte-for-byte, so a pinned `MPX_VERSION=v0.1.1` and the `/latest` redirect both resolve from this repo today rather than from the next tag onward. The old repository keeps serving the URLs it already published. RELEASE_TOKEN stays for the tap bump, and now wants write on the tap alone. --- .github/workflows/release.yml | 59 ++++++++++++++++++----------------- README.md | 14 +++++---- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 665b410..cbce64f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,15 +1,17 @@ name: Release # Tag-driven. `git tag v0.1.0 && git push --tags` builds every platform, -# publishes them as a GitHub Release in the *releases* repo, and opens the -# tap's formula bump. +# publishes them as a GitHub Release *here*, and opens the tap's formula bump. # -# Why the artifacts land in another repo: this one holds the source and stays -# private until the app ships, while `curl | sh` and Homebrew both need -# anonymous downloads. `multiplex-term/multiplex-cli-releases` exists to be -# the public half. That split is also why GITHUB_TOKEN is not enough — it is -# scoped to this repository, so cross-repo publishing needs RELEASE_TOKEN -# (see README's "Releasing" section for the exact scopes). +# Artifacts used to land in `multiplex-term/multiplex-cli-releases`, the public +# half of a then-private source repo. This repository is public, so downloads +# are anonymous from here and that split is gone: publishing needs nothing but +# GITHUB_TOKEN. The releases repo keeps serving the URLs it already published +# (v0.1.0–v0.1.2, also mirrored here) and takes no new ones. +# +# The tap is still another repository, so bumping its formula still needs +# RELEASE_TOKEN — which now wants write on `multiplex-term/homebrew-tap` and +# nothing else. Whatever access it held on the releases repo can go. on: push: tags: ["v*"] @@ -21,11 +23,10 @@ on: env: CARGO_TERM_COLOR: always - ARTIFACT_REPO: multiplex-term/multiplex-cli-releases -# GITHUB_TOKEN reads this repository and nothing else; every write here goes -# to another repo under RELEASE_TOKEN. Spelling that out means a compromised -# step cannot quietly push to this one. +# Read-only by default, so the build matrix — the jobs that run third-party +# toolchains over the tag's own code — cannot write anything. Only `publish` +# raises this, to `contents: write` and only for itself. permissions: contents: read @@ -160,11 +161,14 @@ jobs: if-no-files-found: error publish: - name: publish to releases repo + name: publish release needs: [resolve, build] runs-on: ubuntu-latest + # The one job that writes to this repository, and it writes exactly one + # thing: the release for the tag `resolve` already shape-checked. + permissions: + contents: write steps: - - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: path: dist @@ -186,8 +190,6 @@ jobs: { echo "\`mpx\` $TAG — the companion CLI for Multiplex." echo - echo "Source lives in the private \`multiplex-cli\` repo until the app ships; this repository exists to host the downloadable builds." - echo echo '```sh' echo "brew install multiplex-term/tap/mpx # macOS or Linux" echo "curl -fsSL https://multiplexterm.dev/install-mpx-cli | sh # macOS or Linux" @@ -197,21 +199,18 @@ jobs: } > NOTES.md - name: publish env: - GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} TAG: ${{ needs.resolve.outputs.tag }} run: | set -euo pipefail - if [ -z "${GH_TOKEN:-}" ]; then - echo "::error::RELEASE_TOKEN is not set — see the Releasing section of README.md" - exit 1 - fi # Idempotent: a re-run replaces the assets rather than failing, so a # single bad platform build can be fixed without minting a new tag. - if gh release view "$TAG" --repo "$ARTIFACT_REPO" >/dev/null 2>&1; then - gh release upload "$TAG" dist/* --repo "$ARTIFACT_REPO" --clobber + if gh release view "$TAG" --repo "$REPO" >/dev/null 2>&1; then + gh release upload "$TAG" dist/* --repo "$REPO" --clobber else gh release create "$TAG" dist/* \ - --repo "$ARTIFACT_REPO" \ + --repo "$REPO" \ --title "mpx $TAG" \ --notes-file NOTES.md fi @@ -235,10 +234,12 @@ jobs: path: tap # checkout leaves the token in tap/.git/config unless told not to, # and the very next step runs bump.py — code from that repository. - # RELEASE_TOKEN can also write the public releases repo, so a - # compromised tap could read the credential out of the checkout it - # was handed and replace published release assets. The token now - # appears only in the push step, after tap code has already run. + # A tap-scoped RELEASE_TOKEN can still push straight to the tap's + # main, which is the branch every `brew install` resolves and the + # reason the bump goes through a PR at all — so a compromised tap + # reading the credential out of the checkout it was handed would + # skip its own review gate. The token now appears only in the push + # step, after tap code has already run. persist-credentials: false - name: checksums # Recomputed rather than carried over: SHA256SUMS is written inside @@ -292,4 +293,4 @@ jobs: # reaches anyone. gh pr create --fill --base main \ --title "mpx $VERSION" \ - --body "Automated bump from multiplex-cli $TAG. Checksums come from that release's SHA256SUMS." + --body "Automated bump from mpx-cli $TAG. Checksums come from that release's SHA256SUMS." diff --git a/README.md b/README.md index 4a5e7d1..9e2f1f7 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,7 @@ git tag v0.1.0 && git push origin v0.1.0 `{x86_64,aarch64}-unknown-linux-musl` (static, so one Linux binary covers glibc distributions and Alpine) and `{x86_64,aarch64}-apple-darwin` — packages each with the `multiplex` alias, the README and the licence, writes -a `SHA256SUMS`, publishes a GitHub Release into -[`multiplex-cli-releases`](https://github.com/multiplex-term/multiplex-cli-releases), +a `SHA256SUMS`, publishes a GitHub Release [here](https://github.com/multiplex-term/mpx-cli/releases), and opens a formula bump against [`multiplex-term/homebrew-tap`](https://github.com/multiplex-term/homebrew-tap) (the repo behind the `multiplex-term/tap` tap — the `homebrew-` prefix is @@ -115,11 +114,14 @@ Homebrew's own resolution rule, not a choice). Re-running a tag is safe: assets are uploaded with `--clobber`, so one bad platform build can be fixed without minting a new version. -### Why artifacts live in another repository +### Releases before this repository was public -Homebrew and `curl | sh` both need *anonymous* downloads, and release -artifacts are large and rewritten every tag. `multiplex-cli-releases` -carries them; it holds no source. +Homebrew and `curl | sh` both need *anonymous* downloads, which a private +source repository cannot serve — so v0.1.0 through v0.1.2 were published to +[`multiplex-cli-releases`](https://github.com/multiplex-term/multiplex-cli-releases/releases), +a repository that held nothing but the artifacts. Those three releases are +mirrored here byte-for-byte, and the old repository keeps serving the +download URLs it already published; it takes no new ones. ## Protocol