-
Notifications
You must be signed in to change notification settings - Fork 0
381 lines (352 loc) · 14.5 KB
/
Copy pathpackage.yml
File metadata and controls
381 lines (352 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# Project: macbash
# File: .github/workflows/package.yml
# Purpose: Post-release packaging - runs after binaries land in R2.
# Generates linux tarballs from hyperi-ci's raw binaries, builds
# deb/rpm via nfpm, renders homebrew + scoop manifests, and
# uploads all artefacts to R2 under the same versioned path.
#
# License: Apache-2.0
# Copyright: (c) 2025-2026 HYPERI PTY LIMITED
#
# Trigger: - workflow_run after Cross-build succeeds (the standard path)
# - workflow_dispatch with tag input (manual fallback)
#
# On workflow_run the tag is derived from the most recent v* tag,
# same pattern as cross-build.yml's resolve-tag job. The linux
# tarball jobs already poll R2 for the raw linux binary so a race
# with hyperi-ci's slow publish step is tolerated.
#
# Status: Linux + Darwin packaging is production. Scoop manifest is
# EXPERIMENTAL - not in the formal test path.
name: Package
"on":
workflow_run:
workflows: [Cross-build]
types: [completed]
workflow_dispatch:
inputs:
tag:
type: string
required: true
description: "Tag to package (e.g. v1.5.2). cross-build + main CI must have completed for this tag."
permissions:
contents: read
env:
R2_ENDPOINT: https://98d20454e2af7a9397ad9366a1641659.r2.cloudflarestorage.com
R2_BUCKET: bin-repo
R2_PUBLIC: https://downloads.hyperi.io
jobs:
resolve-tag:
# workflow_run gives us no inputs - derive the tag from the latest v* tag,
# same pattern as cross-build.yml's resolve-tag job.
name: Resolve tag to package
runs-on: ubuntu-latest
if: >-
${{ github.event_name == 'workflow_dispatch'
|| github.event.workflow_run.conclusion == 'success' }}
outputs:
tag: ${{ steps.pick.outputs.tag }}
steps:
- name: Checkout (with tags)
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
fetch-tags: true
- name: Pick tag
id: pick
shell: bash
# Indirect via env: to keep untrusted github-context out of run:; semgrep
# run-shell-injection.
env:
INPUT_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
if [ -n "$INPUT_TAG" ]; then
tag="$INPUT_TAG"
else
git fetch --tags --force origin
tag=$(git tag --sort=-creatordate --list 'v*' | head -1)
fi
if [ -z "$tag" ]; then
echo "::error::no tag to package"
exit 1
fi
echo "Resolved tag: $tag"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
linux-tarball:
name: Linux ${{ matrix.arch }} tarball + sha
needs: resolve-tag
runs-on: ubuntu-latest
# The resolved tag comes from `git tag --list` and git permits backticks
# and $() in a ref name, so it is untrusted input. Bind it once here and
# read "$TAG" in the shell -- never interpolate it into a run: body.
env:
TAG: ${{ needs.resolve-tag.outputs.tag }}
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
steps:
- name: Checkout (default branch - packaging code lives here, not in the tagged source)
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Wait for raw linux binary on R2
shell: bash
run: |
set -euo pipefail
URL="${R2_PUBLIC}/macbash/${TAG}/macbash-linux-${{ matrix.arch }}"
for i in $(seq 1 20); do
if curl -fsI "$URL" >/dev/null 2>&1; then exit 0; fi
echo "attempt $i: not yet at $URL, sleeping 15s"; sleep 15
done
echo "::error::raw linux binary never appeared at $URL"
exit 1
- name: Download raw binary
shell: bash
run: |
set -euo pipefail
mkdir -p dist
curl -fsSL "${R2_PUBLIC}/macbash/${TAG}/macbash-linux-${{ matrix.arch }}" \
-o "dist/macbash-linux-${{ matrix.arch }}"
chmod +x "dist/macbash-linux-${{ matrix.arch }}"
- name: Build tarball
shell: bash
run: |
set -euo pipefail
stage="dist/macbash-linux-${{ matrix.arch }}-stage"
mkdir -p "$stage"
cp "dist/macbash-linux-${{ matrix.arch }}" "$stage/macbash"
[ -f LICENSE ] && cp LICENSE "$stage/" || true
tar -czf "dist/macbash-linux-${{ matrix.arch }}.tar.gz" -C dist "$(basename "$stage")"
rm -rf "$stage"
( cd dist && shasum -a 256 "macbash-linux-${{ matrix.arch }}" "macbash-linux-${{ matrix.arch }}.tar.gz" ) \
> "dist/macbash-linux-${{ matrix.arch }}.sha256"
ls -la dist
- name: Upload to R2
env:
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
shell: bash
run: |
set -euo pipefail
aws configure set aws_access_key_id "$R2_ACCESS_KEY_ID"
aws configure set aws_secret_access_key "$R2_SECRET_ACCESS_KEY"
aws configure set region auto
for dest in "s3://${R2_BUCKET}/macbash/${TAG}/" "s3://${R2_BUCKET}/macbash/latest/"; do
aws s3 cp --endpoint-url "$R2_ENDPOINT" \
"dist/macbash-linux-${{ matrix.arch }}.tar.gz" "$dest"
aws s3 cp --endpoint-url "$R2_ENDPOINT" \
"dist/macbash-linux-${{ matrix.arch }}.sha256" "$dest"
done
deb-rpm:
name: nfpm deb + rpm (${{ matrix.arch }})
runs-on: ubuntu-latest
needs: [resolve-tag, linux-tarball]
env:
TAG: ${{ needs.resolve-tag.outputs.tag }}
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
steps:
- name: Checkout (default branch - packaging code lives here, not in the tagged source)
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install nfpm
shell: bash
run: |
set -euo pipefail
NFPM_VERSION="2.41.3"
curl -fsSL -o /tmp/nfpm.deb \
"https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_amd64.deb"
sudo dpkg -i /tmp/nfpm.deb
- name: Download raw binary from R2
shell: bash
run: |
set -euo pipefail
mkdir -p dist
curl -fsSL "${R2_PUBLIC}/macbash/${TAG}/macbash-linux-${{ matrix.arch }}" \
-o "dist/macbash-linux-${{ matrix.arch }}"
chmod +x "dist/macbash-linux-${{ matrix.arch }}"
- name: Render nfpm.yaml
shell: bash
env:
NFPM_ARCH: ${{ matrix.arch }}
run: |
set -euo pipefail
version="${TAG}"; version="${version#v}"
./packaging/render-release.sh "$version" --from-r2 || true
# render-release outputs packaging/dist/release/nfpm.yaml; confirm
test -f packaging/dist/release/nfpm.yaml
- name: Build deb + rpm
shell: bash
run: |
set -euo pipefail
mkdir -p out
nfpm package -f packaging/dist/release/nfpm.yaml -p deb -t out/
nfpm package -f packaging/dist/release/nfpm.yaml -p rpm -t out/
ls -la out
# Per-arch sha for the packages
( cd out && shasum -a 256 ./* ) > "out/macbash-linux-${{ matrix.arch }}.packages.sha256"
- name: Upload to R2
env:
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
shell: bash
run: |
set -euo pipefail
aws configure set aws_access_key_id "$R2_ACCESS_KEY_ID"
aws configure set aws_secret_access_key "$R2_SECRET_ACCESS_KEY"
aws configure set region auto
for dest in "s3://${R2_BUCKET}/macbash/${TAG}/" "s3://${R2_BUCKET}/macbash/latest/"; do
for f in out/*; do
aws s3 cp --endpoint-url "$R2_ENDPOINT" "$f" "$dest"
done
done
manifests:
name: Render homebrew + scoop manifests + static install scripts
runs-on: ubuntu-latest
needs: [resolve-tag, linux-tarball]
env:
TAG: ${{ needs.resolve-tag.outputs.tag }}
steps:
- name: Checkout (default branch - packaging code lives here, not in the tagged source)
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Render static install scripts
shell: bash
run: |
set -euo pipefail
./packaging/render.sh
ls -la packaging/dist
- name: Render per-version manifests
shell: bash
run: |
set -euo pipefail
version="${TAG}"; version="${version#v}"
./packaging/render-release.sh "$version" --from-r2
find packaging/dist/release -type f | sort
- name: Upload manifests + install scripts to R2
env:
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
shell: bash
run: |
set -euo pipefail
aws configure set aws_access_key_id "$R2_ACCESS_KEY_ID"
aws configure set aws_secret_access_key "$R2_SECRET_ACCESS_KEY"
aws configure set region auto
# Per-version manifests under /<tag>/manifests/ + /latest/manifests/
for dest in "s3://${R2_BUCKET}/macbash/${TAG}/manifests/" \
"s3://${R2_BUCKET}/macbash/latest/manifests/"; do
aws s3 cp --recursive --endpoint-url "$R2_ENDPOINT" \
packaging/dist/release/ "$dest"
done
# Static install scripts at /macbash/install.sh (curl-pipe target).
# Also mirror to per-version path for reproducibility - pinning a
# tag-specific install.sh URL lets users install a known-good copy
# of the script that downloaded the same-version binary.
for f in install.sh install.ps1 uninstall.sh uninstall.ps1; do
for dest in "s3://${R2_BUCKET}/macbash/$f" \
"s3://${R2_BUCKET}/macbash/${TAG}/$f"; do
# Use text/plain content-type so curl/iex render in browser too.
ct="text/x-shellscript"
case "$f" in *.ps1) ct="text/plain" ;; esac
aws s3 cp --endpoint-url "$R2_ENDPOINT" \
--content-type "$ct" \
"packaging/dist/$f" "$dest"
done
done
publish-tap-bucket:
name: Push formula + scoop manifest to tap / bucket repos
runs-on: ubuntu-latest
needs: [resolve-tag, manifests]
env:
PROJECT: macbash
TAP_REPO: hyperi-io/homebrew-tap
BUCKET_REPO: hyperi-io/scoop-bucket
TAG: ${{ needs.resolve-tag.outputs.tag }}
steps:
# HOMEBREW_APP_* not GH_APP_*: the GH_APP_* org secrets are
# visibility=selected and macbash is not in the grant list, so they
# arrive empty and create-github-app-token fails with "client-id must be
# non-empty". HOMEBREW_APP_* is visibility=all and is the app installed
# on the tap.
#
# Homebrew (production) and Scoop (EXPERIMENTAL) get SEPARATE token mints
# on purpose. create-github-app-token fails the WHOLE request if any
# named repo is missing or out of the app's installation -- and
# scoop-bucket does not exist yet. A single combined mint let the missing
# experimental bucket take the production homebrew push down with it.
- name: Mint homebrew-tap token
id: tap-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.HOMEBREW_APP_CLIENT_ID }}
private-key: ${{ secrets.HOMEBREW_APP_PRIVATE_KEY }}
owner: hyperi-io
repositories: homebrew-tap
# Best-effort: fails (and is skipped) until scoop-bucket exists and the
# app is installed on it. Does not gate the job.
- name: Mint scoop-bucket token
id: bucket-token
continue-on-error: true
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.HOMEBREW_APP_CLIENT_ID }}
private-key: ${{ secrets.HOMEBREW_APP_PRIVATE_KEY }}
owner: hyperi-io
repositories: scoop-bucket
- name: Fetch rendered manifests from R2
shell: bash
run: |
set -euo pipefail
mkdir -p staging
curl -fsSL "${R2_PUBLIC}/macbash/${TAG}/manifests/homebrew/${PROJECT}.rb" \
-o staging/formula.rb
curl -fsSL "${R2_PUBLIC}/macbash/${TAG}/manifests/scoop/${PROJECT}.json" \
-o staging/manifest.json
- name: Configure git identity
shell: bash
run: |
git config --global user.name "hyperi-bot"
git config --global user.email "bot@hyperi.io"
- name: Push to homebrew-tap
shell: bash
env:
GH_TOKEN: ${{ steps.tap-token.outputs.token }}
TAG: ${{ needs.resolve-tag.outputs.tag }}
run: |
set -euo pipefail
git clone "https://x-access-token:${GH_TOKEN}@github.com/${TAP_REPO}.git" tap
mkdir -p tap/Formula
cp staging/formula.rb "tap/Formula/${PROJECT}.rb"
cd tap
if git diff --quiet -- "Formula/${PROJECT}.rb"; then
echo "homebrew-tap: ${PROJECT}.rb unchanged for ${TAG}"
else
git add "Formula/${PROJECT}.rb"
git commit -m "${PROJECT} ${TAG#v}"
git push origin HEAD
echo "homebrew-tap: pushed ${PROJECT} ${TAG}"
fi
- name: Push to scoop-bucket
# Skipped, not failed, while the bucket token could not be minted --
# scoop is EXPERIMENTAL and the bucket repo is not created yet. The
# rendered manifest still lands on R2 for manual bucket setup.
if: steps.bucket-token.outcome == 'success'
shell: bash
env:
GH_TOKEN: ${{ steps.bucket-token.outputs.token }}
TAG: ${{ needs.resolve-tag.outputs.tag }}
run: |
set -euo pipefail
git clone "https://x-access-token:${GH_TOKEN}@github.com/${BUCKET_REPO}.git" bucket
mkdir -p bucket/bucket
cp staging/manifest.json "bucket/bucket/${PROJECT}.json"
cd bucket
if git diff --quiet -- "bucket/${PROJECT}.json"; then
echo "scoop-bucket: ${PROJECT}.json unchanged for ${TAG}"
else
git add "bucket/${PROJECT}.json"
git commit -m "${PROJECT} ${TAG#v}"
git push origin HEAD
echo "scoop-bucket: pushed ${PROJECT} ${TAG}"
fi