Repository navigation
254 lines (229 loc) · 11 KB
/
Copy pathoffline-cli-release.yml
File metadata and controls
254 lines (229 loc) · 11 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
name: Offline CLI release
# Builds the air-gapped signing CLI (offline-cli/) for all supported platforms
# and uploads the binaries to a GitHub Release, so users download one
# canonical, checksummed artifact instead of trusting a backend-served binary
# (see docs/offline-audit-guide.md — the CLI is the program that ends up handling
# MINA_PRIVATE_KEY, so its provenance matters more than anything else in the
# offline flow).
#
# Artifact names intentionally match what the UI's download instructions
# expect: mina-guard-cli-<platform> (plus .exe on Windows). A SHA256SUMS file
# and the contracts verification-key hash are attached alongside the binaries:
# the CLI inlines the contracts circuit, so a release is only usable against
# deployments built from the same circuit — minaguard-vk-hash.txt is the
# fingerprint to compare.
#
# Triggers:
# * push of a tag matching `offline-cli-v*` (e.g. offline-cli-v0.1.0-rc1)
# → builds, smoke-tests, and publishes a DRAFT release; review and publish
# it manually.
# * manual `workflow_dispatch` → dry run that builds and smoke-tests all
# platforms and uploads the binaries as CI artifacts, without a release.
on:
push:
tags:
- 'offline-cli-v*'
workflow_dispatch: {}
# Never cancel a half-finished release build; let each ref's run complete so
# we don't end up with a partially-populated draft release.
concurrency:
group: offline-cli-release-${{ github.ref }}
cancel-in-progress: false
# Read-only by default. Only the release job may write, and only what it
# needs: the Release itself and the build-provenance attestations. Every
# third-party action below is pinned to a commit SHA, not a tag, because a
# moved tag would run someone else's code with these permissions.
permissions:
contents: read
# Exact Bun toolchain for every job here. Provenance of the CLI binary is the
# whole point of this workflow (see header), so the compiler version must be
# fixed and reproducible, not floating. Bump intentionally; the release notes
# echo this so the published artifact records what built it.
env:
BUN_VERSION: 1.3.10
jobs:
vk-hashes:
uses: ./.github/workflows/vk-hashes.yml
build:
name: Build (${{ matrix.os }})
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
# Each leg builds its own platform's targets natively (bun can
# cross-compile, but native builds let us actually execute the binary
# as a smoke test — which exercises the embedded Kimchi WASM shim and
# o1js module init on the real platform). `smoke` is the one binary in
# `targets` that matches the runner's own OS/arch.
include:
- os: ubuntu-latest
targets: 'linux-x64 linux-arm64'
smoke: mina-guard-cli-linux-x64
- os: macos-latest # arm64 runner
targets: 'macos-arm64 macos-x64'
smoke: mina-guard-cli-macos-arm64
- os: windows-latest
targets: 'windows-x64'
smoke: mina-guard-cli-windows-x64.exe
runs-on: ${{ matrix.os }}
steps:
# Pin the Bun toolchain exactly (BUN_VERSION at the workflow level). This
# binary ends up handling MINA_PRIVATE_KEY, so its provenance is the point
# (see header): a floating `@v2` would let the compiler change under us
# between runs and make a rebuild of the same tag non-reproducible. The
# release notes echo this version so the artifact records what built it.
- name: Install Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Git checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
# --frozen-lockfile pins bun.lock exactly for this signed release, bun
# does not freeze in CI by default. --ignore-scripts keeps dependency
# lifecycle scripts from running inside the release build; nothing the
# CLI compiles needs them.
- name: Install dependencies
run: bun install --frozen-lockfile --ignore-scripts
- name: Build contracts
run: bun run --filter contracts build
# Same compile invocation as docs/offline-audit-guide.md; macos-* release
# names map to bun's darwin targets, and bun appends .exe to the Windows
# outfile itself. The --define keeps o1js off Node-specific code paths
# that don't exist in the Bun runtime.
- name: Compile CLI binaries
working-directory: offline-cli
shell: bash
run: |
for t in ${{ matrix.targets }}; do
case "$t" in
macos-arm64) bt=bun-darwin-arm64 ;;
macos-x64) bt=bun-darwin-x64 ;;
*) bt="bun-$t" ;;
esac
echo "::group::$bt"
bun build --compile --target="$bt" src/index.ts \
--outfile "dist/mina-guard-cli-$t" --define 'process.versions.node=""'
echo "::endgroup::"
done
ls -lh dist/
# macOS binaries must carry JIT entitlements or the embedded WASM fails
# to load at runtime (see PR #87 / the equivalent step in ci.yml). Ad-hoc
# signing is enough; the smoke test below would fail without this.
- name: Sign macOS binaries with JIT entitlements
if: runner.os == 'macOS'
working-directory: offline-cli
run: |
cat > /tmp/entitlements.plist << 'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>
PLIST
for t in ${{ matrix.targets }}; do
codesign -s - --force --entitlements /tmp/entitlements.plist "dist/mina-guard-cli-$t"
done
# Run the native-arch binary with no arguments: it must reach the usage
# message (module init, embedded WASM, and arg parsing all execute). A
# binary that can't even print usage must never ship.
- name: Smoke test native binary
working-directory: offline-cli
shell: bash
env:
# Keep the binary network-neutral; set a domain only when executing it.
MINA_NETWORK_DOMAIN: testnet
run: |
out="$(./dist/${{ matrix.smoke }} 2>&1 || true)"
echo "$out"
grep -q "Usage:" <<<"$out"
- name: Upload binaries
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: offline-cli-${{ matrix.os }}
if-no-files-found: error
retention-days: 7
path: offline-cli/dist/*
# Tag builds only: collect the three legs' binaries, checksum them once, and
# publish everything to a draft release. A single aggregation job (instead
# of per-leg uploads) avoids release-creation races and yields one canonical
# SHA256SUMS covering every artifact.
release:
name: Publish draft release
if: startsWith(github.ref, 'refs/tags/offline-cli-v')
needs: [build, vk-hashes]
timeout-minutes: 10
runs-on: ubuntu-latest
permissions:
contents: write # create the draft Release and upload its assets
id-token: write # sign the provenance attestations with this run's OIDC identity
attestations: write # store them on the repo for `gh attestation verify`
steps:
- name: Git checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
- name: Collect binaries
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: offline-cli-*
merge-multiple: true
path: dist
- name: Download VK manifest for this commit
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: minaguard-vk-manifest
path: contracts
- name: Verify VK manifest source
run: bash contracts/scripts/verify-vk-manifest.sh
# The CLI inlines the contracts circuit; attach the verification-key
# hash generated from the same source so operators can check the
# binary matches the deployment they are signing for.
- name: Attach VK hash + checksums
run: |
cp contracts/.vk-hash dist/minaguard-vk-hash.txt
cd dist
sha256sum mina-guard-cli-* minaguard-vk-hash.txt > SHA256SUMS
cat SHA256SUMS
# SHA256SUMS only proves a download is the file this job had; it is
# written here, so a binary tampered with earlier in the run would get a
# matching checksum. The signed provenance attestation binds each file's
# digest to this repository, workflow, run and tagged commit. Verifiers
# must pin the signing workflow and the tag (the release notes below
# give the exact command): `--repo` alone accepts any attestation this
# repository ever produced, including other workflows' and older tags'.
- name: Attest build provenance
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
with:
subject-path: |
dist/mina-guard-cli-*
dist/SHA256SUMS
dist/minaguard-vk-hash.txt
# Record the exact toolchain in the release body: reproducing this binary
# from source (the fallback trust story in docs/offline-audit-guide.md)
# requires knowing which Bun compiled it. The VK hash was verified against
# the tagged circuit in the build job before we got here.
- name: Create draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "$GITHUB_REF_NAME" \
--draft \
--verify-tag \
--title "Offline CLI $GITHUB_REF_NAME" \
--notes "$(cat <<EOF
Air-gapped signing CLI for MinaGuard. Usage and security model: \`docs/offline-audit-guide.md\`.
**Verify your download** before moving it to the air-gapped machine. Check it against \`SHA256SUMS\`, then check its signed build provenance, pinned to this repository's release workflow and to this tag:
\`\`\`
gh attestation verify mina-guard-cli-<platform> --repo ${GITHUB_REPOSITORY} \\
--signer-workflow ${GITHUB_REPOSITORY}/.github/workflows/offline-cli-release.yml \\
--source-ref refs/tags/${GITHUB_REF_NAME}
\`\`\`
**Circuit compatibility:** this binary inlines the MinaGuard circuit; it only produces valid proofs for contracts whose verification-key hash matches \`minaguard-vk-hash.txt\`.
**Built with** Bun \`${BUN_VERSION}\` (pin it to reproduce these binaries from source).
macOS: \`xattr -d com.apple.quarantine mina-guard-cli-macos-*\` then \`chmod +x\`. Linux: \`chmod +x\`.
EOF
)" \
dist/*