Skip to content

Commit bbc5777

Browse files
committed
ci: harden release pipeline and add homebrew bump workflow
- release.yml: default-deny top-level perms with job-level grants, github.repository guard against fork workflow_dispatch, and persist-credentials=false on every checkout step to avoid leaking GITHUB_TOKEN into .git/config on the self-hosted runner pool. - pipeline-parallel-ci.yml: fork PR guard on both jobs (two-host-logical and three-host-real-model) so canonical self-hosted runners and the CI fixture release stay accessible only to in-org pull requests and workflow_dispatch runs. - update_homebrew_formula.yml: new workflow ported from lablup/all-smi that updates Formula/mlxcel.rb on lablup/homebrew-tap after each Release completes (workflow_run) or via workflow_dispatch. Scopes the brew artifact to mlxcel-macos-aarch64.zip (Linux CUDA variants stay raw release assets), aborts on undersized downloads, and is gated to lablup/mlxcel via the same repository guard pattern.
1 parent de64cb8 commit bbc5777

3 files changed

Lines changed: 155 additions & 8 deletions

File tree

.github/workflows/pipeline-parallel-ci.yml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ jobs:
7878
two-host-logical:
7979
name: 2-host logical (${{ matrix.os }})
8080
runs-on: ${{ matrix.os }}
81+
# Public-mirror guard: skip on forks. PRs opened from a fork would
82+
# otherwise execute fork-author code against this canonical repository's
83+
# GITHUB_TOKEN and CI fixture release, which we intentionally disallow on
84+
# the public release mirror. Internal PRs (head repo == this repo) and
85+
# `workflow_dispatch` still run normally.
86+
if: >-
87+
github.repository == 'lablup/mlxcel' &&
88+
(github.event_name != 'pull_request' ||
89+
github.event.pull_request.head.repo.full_name == github.repository)
8190
strategy:
8291
fail-fast: false
8392
matrix:
@@ -88,6 +97,9 @@ jobs:
8897
steps:
8998
- name: Checkout code
9099
uses: actions/checkout@v6
100+
with:
101+
# Don't leave GITHUB_TOKEN in .git/config; CI jobs only fetch.
102+
persist-credentials: false
91103

92104
- name: Install Rust toolchain
93105
uses: dtolnay/rust-toolchain@stable
@@ -194,10 +206,13 @@ jobs:
194206
# stays queued until the runner becomes available or the PR is
195207
# updated — it does not auto-fail.
196208
if: |
197-
(github.event_name == 'pull_request' &&
198-
contains(github.event.pull_request.labels.*.name, 'ci:pp-three-host')) ||
199-
(github.event_name == 'workflow_dispatch' &&
200-
github.event.inputs.run_three_host == 'true')
209+
github.repository == 'lablup/mlxcel' && (
210+
(github.event_name == 'pull_request' &&
211+
github.event.pull_request.head.repo.full_name == github.repository &&
212+
contains(github.event.pull_request.labels.*.name, 'ci:pp-three-host')) ||
213+
(github.event_name == 'workflow_dispatch' &&
214+
github.event.inputs.run_three_host == 'true')
215+
)
201216
runs-on:
202217
- self-hosted
203218
- pp-three-host
@@ -213,6 +228,9 @@ jobs:
213228
steps:
214229
- name: Checkout code
215230
uses: actions/checkout@v6
231+
with:
232+
# Don't leave GITHUB_TOKEN in .git/config; CI jobs only fetch.
233+
persist-credentials: false
216234

217235
- name: Resolve model root
218236
id: model

.github/workflows/release.yml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ on:
99
description: 'Release tag to upload artifacts to (e.g. v0.1.0)'
1010
required: false
1111

12-
permissions:
13-
contents: write
12+
# Default-deny top-level permissions; each job grants only what it needs.
13+
permissions: {}
1414

1515
jobs:
1616
# ============================================================================
@@ -21,13 +21,24 @@ jobs:
2121
runs-on: self-hosted-macos-26-arm64 # Self-hosted Apple Silicon runner with macOS 26 SDK + Metal 4
2222
environment: packaging
2323
timeout-minutes: 90
24+
# Block forks from invoking this workflow against the canonical self-hosted
25+
# runner pool. `workflow_dispatch` can be triggered from any branch the
26+
# actor has push access to, so this guard is the last line of defense
27+
# against a forked copy of the repo dispatching a build with the
28+
# canonical-org runner labels.
29+
if: github.repository == 'lablup/mlxcel'
30+
permissions:
31+
contents: write
2432

2533
env:
2634
MACOSX_DEPLOYMENT_TARGET: "14.0"
2735

2836
steps:
2937
- name: Checkout code
3038
uses: actions/checkout@v6
39+
with:
40+
# Don't leave GITHUB_TOKEN in .git/config; release jobs only fetch.
41+
persist-credentials: false
3142

3243
# Use persistent cache paths for self-hosted runner (outside workspace)
3344
- name: Setup persistent cache paths (self-hosted)
@@ -184,6 +195,9 @@ jobs:
184195
name: Build Linux ARM64 CUDA (${{ matrix.variant }})
185196
runs-on: GB10
186197
environment: packaging
198+
if: github.repository == 'lablup/mlxcel'
199+
permissions:
200+
contents: write
187201

188202
strategy:
189203
fail-fast: false
@@ -204,6 +218,9 @@ jobs:
204218
steps:
205219
- name: Checkout code
206220
uses: actions/checkout@v6
221+
with:
222+
# Don't leave GITHUB_TOKEN in .git/config; release jobs only fetch.
223+
persist-credentials: false
207224

208225
# Use persistent cache paths for self-hosted runner (outside workspace)
209226
- name: Setup persistent cache paths (self-hosted)
@@ -295,7 +312,7 @@ jobs:
295312
notify-teams:
296313
name: Notify Teams on release
297314
needs: [build-macos, build-linux-cuda]
298-
if: github.event_name == 'release'
315+
if: github.repository == 'lablup/mlxcel' && github.event_name == 'release'
299316
runs-on: ubuntu-latest
300317
permissions: {}
301318

@@ -349,7 +366,7 @@ jobs:
349366
promote-release:
350367
name: Promote to full release
351368
needs: [build-macos, build-linux-cuda]
352-
if: always() && github.event_name == 'release' && github.event.release.prerelease && needs.build-macos.result == 'success' && needs.build-linux-cuda.result == 'success'
369+
if: always() && github.repository == 'lablup/mlxcel' && github.event_name == 'release' && github.event.release.prerelease && needs.build-macos.result == 'success' && needs.build-linux-cuda.result == 'success'
353370
runs-on: ubuntu-latest
354371
permissions:
355372
contents: write
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
name: Update Homebrew Formula
2+
3+
# Adapted from lablup/all-smi's update_homebrew_formula.yml. mlxcel ships a
4+
# single brew-supported artifact (macOS Apple Silicon); the Linux CUDA
5+
# variants are hardware-specific and continue to be distributed only as raw
6+
# GitHub Release assets.
7+
8+
on:
9+
workflow_dispatch:
10+
inputs:
11+
release_tag:
12+
description: "Release tag (e.g. v0.0.27)"
13+
required: false
14+
15+
workflow_run:
16+
workflows: ["Release"]
17+
types:
18+
- completed
19+
20+
# Default-deny top-level permissions; each job grants only what it needs.
21+
permissions: {}
22+
23+
jobs:
24+
update-homebrew:
25+
name: Update Homebrew Formula
26+
runs-on: macos-latest
27+
environment: packaging
28+
# Pin this job to the canonical public repository. `workflow_run` cannot
29+
# be triggered from a fork's Release workflow (workflow_run only observes
30+
# workflows defined on the default branch of the same repo), but
31+
# `workflow_dispatch` can be invoked from any branch the actor has push
32+
# access to. The repository guard removes that residual attack surface.
33+
if: >-
34+
github.repository == 'lablup/mlxcel' &&
35+
(github.event_name == 'workflow_dispatch' ||
36+
github.event.workflow_run.conclusion == 'success')
37+
38+
permissions:
39+
contents: read
40+
41+
steps:
42+
- name: Install gnu-sed
43+
run: brew install gnu-sed
44+
45+
- name: Determine version tag
46+
run: |
47+
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ github.event.inputs.release_tag }}" ]; then
48+
echo "VERSION=${{ github.event.inputs.release_tag }}" >> $GITHUB_ENV
49+
else
50+
TAG=$(gh api repos/${{ github.repository }}/releases/latest --jq .tag_name)
51+
echo "VERSION=$TAG" >> $GITHUB_ENV
52+
fi
53+
env:
54+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55+
56+
- name: Clone Homebrew tap repository
57+
env:
58+
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
59+
run: |
60+
git clone "https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/lablup/homebrew-tap.git"
61+
cd homebrew-tap
62+
git config user.name "GitHub Action"
63+
git config user.email "actions@github.com"
64+
65+
- name: Download release artifact and calculate SHA256
66+
run: |
67+
cd homebrew-tap
68+
RAW_VERSION="${{ env.VERSION }}"
69+
VERSION="${RAW_VERSION#v}" # Strip leading 'v' if present
70+
echo "VERSION_NO_V=$VERSION" >> $GITHUB_ENV
71+
72+
MAC_URL="https://github.com/${{ github.repository }}/releases/download/v${VERSION}/mlxcel-macos-aarch64.zip"
73+
74+
mkdir -p tmp
75+
# -f: fail on HTTP errors (so a missing release surfaces here, not as
76+
# a silent zero-byte artifact whose SHA256 ends up committed to the
77+
# tap).
78+
curl -fLs --retry 3 "$MAC_URL" -o tmp/mac.zip
79+
80+
# Sanity check: a non-zero file with a valid zip magic. mlxcel's
81+
# macOS asset is around 60 MB at v0.0.27, so anything tiny is a
82+
# release-pipeline regression and we abort before mutating the
83+
# formula.
84+
ACTUAL_SIZE=$(stat -f%z tmp/mac.zip)
85+
if [ "$ACTUAL_SIZE" -lt 1048576 ]; then
86+
echo "::error::Downloaded artifact is suspiciously small ($ACTUAL_SIZE B); aborting bump."
87+
exit 1
88+
fi
89+
90+
echo "mac_url=$MAC_URL" >> $GITHUB_ENV
91+
echo "mac_sha=$(shasum -a 256 tmp/mac.zip | awk '{print $1}')" >> $GITHUB_ENV
92+
93+
- name: Update formula
94+
run: |
95+
cd homebrew-tap
96+
VERSION="${{ env.VERSION_NO_V }}"
97+
98+
gsed -i "s/^ version .*/ version \"${VERSION}\"/" Formula/mlxcel.rb
99+
100+
gsed -i "s|https://github.com/.*/mlxcel-macos-aarch64.zip|${mac_url}|" Formula/mlxcel.rb
101+
gsed -i "/mlxcel-macos-aarch64.zip\"/!b;n;c\ sha256 \"${mac_sha}\"" Formula/mlxcel.rb
102+
103+
- name: Commit and push changes to tap
104+
run: |
105+
cd homebrew-tap
106+
if git diff --quiet Formula/mlxcel.rb; then
107+
echo "Formula already up to date for v${{ env.VERSION_NO_V }} — nothing to push."
108+
exit 0
109+
fi
110+
git add Formula/mlxcel.rb
111+
git commit -m "bump: mlxcel to v${{ env.VERSION_NO_V }}"
112+
git push origin main

0 commit comments

Comments
 (0)