Repository navigation
265 lines (251 loc) · 13 KB
/
Copy pathrelease.yml
File metadata and controls
265 lines (251 loc) · 13 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
name: release
# Builds every release target twice and fails unless each pair is byte-identical (ADR-0050).
# The second build changes the checkout, CARGO_HOME and RUSTUP_HOME, as another machine would.
# A tag also attests the binaries and drafts a GitHub Release, which the owner publishes (ADR-0049 item 6).
on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
prove:
description: "Drop the CARGO_HOME remap; the gate must then fail"
type: boolean
default: false
permissions:
contents: read
jobs:
build:
name: build (${{ matrix.gui && 'gui, ' || '' }}${{ matrix.target }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { target: x86_64-unknown-linux-musl, os: ubuntu-24.04 }
- { target: aarch64-unknown-linux-musl, os: ubuntu-24.04-arm }
- { target: aarch64-apple-darwin, os: macos-15 }
- { target: x86_64-apple-darwin, os: macos-15 }
- { target: x86_64-pc-windows-msvc, os: windows-2025 }
# GUI artifacts are named `gui-*` so the release job's `strypt-*` pattern skips them:
# it attaches the packages built from them, and the Windows .exe by name.
# Built in Ubuntu 22.04, so glibc 2.35 is the floor, not the runner's 2.39.
- { gui: true, container: true, target: x86_64-unknown-linux-gnu, os: ubuntu-24.04 }
- { gui: true, container: true, target: aarch64-unknown-linux-gnu, os: ubuntu-24.04-arm }
- { gui: true, target: aarch64-apple-darwin, os: macos-15 }
- { gui: true, target: x86_64-apple-darwin, os: macos-15 }
- { gui: true, target: x86_64-pc-windows-msvc, os: windows-2025 }
permissions:
contents: read
id-token: write
attestations: write
artifact-metadata: write
defaults:
run:
shell: bash
env:
T: ${{ matrix.target }}
GUI: ${{ matrix.gui && '--gui' || '' }}
CONTAINER: ${{ matrix.container == true }}
PROVE: ${{ inputs.prove == true }}
steps:
# Actions on the release path are pinned by commit (ADR-0050 decision 4).
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { path: a, persist-credentials: false }
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { path: second checkout, persist-credentials: false }
- name: Plant a path leak
if: env.PROVE == 'true'
run: sed -i.bak '/remap-path-prefix=\$CARGO=/d' a/scripts/build-release.sh "second checkout/scripts/build-release.sh"
- name: Build
run: |
if [ "$CONTAINER" = true ]; then LD_NOTE="$RUNNER_TEMP/linker" bash a/scripts/build-in-container.sh "$T" out-a
else bash a/scripts/build-release.sh $GUI "$T" out-a; fi
- name: Build again, as another machine would
run: |
export CARGO_HOME="$RUNNER_TEMP/cargo-b" RUSTUP_HOME="$RUNNER_TEMP/rustup-b"
if [ "$CONTAINER" = true ]; then bash "second checkout/scripts/build-in-container.sh" "$T" out-b
else bash "second checkout/scripts/build-release.sh" $GUI "$T" out-b; fi
- name: Compare
run: |
a=$(ls out-a); b=$(ls out-b)
if [ "$a" = "$b" ] && cmp "out-a/$a" "out-b/$b"; then same=true; else same=false; fi
if [ "$PROVE" = true ]; then
[ $same = false ] || { echo "::error::the gate passed a planted path leak"; exit 1; }
# Any difference fails the gate, so check this one is the planted leak.
grep -qa cargo-b "out-b/$b" || { echo "::error::the builds differ, but not by the leak"; exit 1; }
echo "the gate caught the planted leak"
else
[ $same = true ] || { echo "::error::$T is not reproducible"; exit 1; }
(command -v sha256sum >/dev/null && sha256sum "out-a/$a") || shasum -a 256 "out-a/$a"
if [ "$CONTAINER" = true ]; then
need=$(objdump -T "out-a/$a" | grep -o 'GLIBC_[0-9.]*' | sort -uV | tail -1)
[ "$(printf '%s\n' "$need" GLIBC_2.35 | sort -V | tail -1)" = GLIBC_2.35 ] ||
{ echo "::error::$T needs $need, above the 2.35 floor"; exit 1; }
echo "highest glibc symbol: $need"
fi
fi
- if: startsWith(github.ref, 'refs/tags/') && env.PROVE != 'true'
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with: { subject-path: out-a/* }
- if: env.PROVE != 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: { name: "${{ matrix.gui && 'gui' || 'strypt' }}-${{ matrix.target }}", path: out-a/*, if-no-files-found: error }
# A verifier needs the same image and linker, not only the commit (ADR-0050).
- name: Record the toolchain
if: env.PROVE != 'true'
run: |
case $T in
*-apple-darwin) l=$(ld -v 2>&1 | sed -n '1s/.*PROJECT://p') ;;
*-linux-gnu) l="$(cat "$RUNNER_TEMP/linker"), in \`build-in-container.sh\`'s image" ;;
*-linux-*) l=$(ld --version | head -1) ;;
*) l="Visual Studio $("/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" -latest -property catalog_productDisplayVersion)" ;;
esac
mkdir meta; printf '| `%s`%s | %s %s | %s |\n' "$T" "${GUI:+ (GUI)}" "$ImageOS" "$ImageVersion" "$l" > "meta/${GUI:+gui-}$T.md"
- if: env.PROVE != 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: { name: "${{ matrix.gui && 'gui-meta' || 'meta' }}-${{ matrix.target }}", path: meta/*, if-no-files-found: error }
# One universal strypt.app in a .dmg (ADR-0062). The .app is packaged twice and compared; the
# .dmg cannot be reproduced, so it is attested only.
package-macos:
needs: build
if: inputs.prove != true
runs-on: macos-15
permissions:
contents: read
id-token: write
attestations: write
artifact-metadata: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { path: a, persist-credentials: false }
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { path: second checkout, persist-credentials: false }
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: { pattern: "gui-*-apple-darwin", path: bins, merge-multiple: true }
- name: Package twice, as another machine would
run: |
set -- bins/strypt-gui-*-aarch64-apple-darwin bins/strypt-gui-*-x86_64-apple-darwin
bash a/scripts/package-macos.sh "$@" out-a
export CARGO_HOME="$RUNNER_TEMP/cargo-b" RUSTUP_HOME="$RUNNER_TEMP/rustup-b"
bash "second checkout/scripts/package-macos.sh" "$@" out-b
diff -r out-a/strypt.app out-b/strypt.app || { echo "::error::strypt.app is not reproducible"; exit 1; }
shasum -a 256 out-a/*.dmg
- if: startsWith(github.ref, 'refs/tags/')
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with: { subject-path: out-a/*.dmg }
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: { name: gui-universal-apple-darwin, path: out-a/*.dmg, if-no-files-found: error }
# An AppImage per Linux GUI build (ADR-0062 decision 3), packaged twice and compared.
package-linux:
needs: build
if: inputs.prove != true
runs-on: ubuntu-24.04
permissions:
contents: read
id-token: write
attestations: write
artifact-metadata: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { path: a, persist-credentials: false }
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { path: second checkout, persist-credentials: false }
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: { pattern: "gui-*-linux-gnu", path: bins, merge-multiple: true }
- name: Package twice, as another machine would
run: |
sudo apt-get update -q && sudo apt-get install -y --no-install-recommends desktop-file-utils
for b in bins/strypt-gui-*; do
bash a/scripts/package-linux.sh "$b" out-a
CARGO_HOME="$RUNNER_TEMP/cargo-b" RUSTUP_HOME="$RUNNER_TEMP/rustup-b" \
bash "second checkout/scripts/package-linux.sh" "$b" out-b
done
[ "$(ls out-a | wc -l)" = 2 ] || { echo "::error::expected two AppImages"; exit 1; }
for f in out-a/*; do
cmp "$f" "out-b/${f#out-a/}" || { echo "::error::${f#out-a/} is not reproducible"; exit 1; }
done
sha256sum out-a/*
- if: startsWith(github.ref, 'refs/tags/')
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with: { subject-path: out-a/* }
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: { name: gui-linux-appimage, path: out-a/*, if-no-files-found: error }
release:
needs: [build, package-macos, package-linux]
if: inputs.prove != true
runs-on: ubuntu-24.04
permissions:
contents: write
id-token: write
attestations: write
artifact-metadata: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: { pattern: "strypt-*", path: dist, merge-multiple: true }
# The GUI ships packaged, and bare only on Windows (ADR-0062).
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: { name: gui-universal-apple-darwin, path: dist }
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: { name: gui-linux-appimage, path: dist }
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: { name: gui-x86_64-pc-windows-msvc, path: dist }
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: { pattern: "*meta-*", path: meta, merge-multiple: true }
# SBOMs read Cargo.lock and build nothing, so one host writes all nine (ADR-0054).
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { path: a, persist-credentials: false }
- name: SBOMs
run: |
cargo install cargo-cyclonedx --version 0.5.9 --locked
bash a/scripts/sbom.sh dist x86_64-unknown-linux-musl aarch64-unknown-linux-musl \
aarch64-apple-darwin x86_64-apple-darwin x86_64-pc-windows-msvc
bash a/scripts/sbom.sh --gui dist x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu \
universal-apple-darwin x86_64-pc-windows-msvc
- name: Checksums and notes
run: |
cd dist
bins=$(ls | grep -v '\.cdx\.json$')
[ "$(echo "$bins" | wc -l)" = 9 ] || { echo "::error::expected nine release files"; exit 1; }
for b in $bins; do
s=${b%.exe}; s=${s%.dmg}; s=${s%.AppImage}
[ -f "$s.cdx.json" ] || { echo "::error::no SBOM for $b"; exit 1; }
done
if [ "$GITHUB_REF_TYPE" = tag ]; then
v=${GITHUB_REF_NAME#v}
[ "$(ls strypt-"$v"-* strypt-gui-"$v"-* | wc -l)" = 18 ] || { echo "::error::$GITHUB_REF_NAME does not match the crate version"; exit 1; }
fi
sha256sum -- * > ../SHA256SUMS && mv ../SHA256SUMS .
{
echo "Built from \`$GITHUB_SHA\` by \`scripts/build-release.sh\`; each binary was built twice, byte-identical."
echo "The GUI's \`.app\` and AppImages were packaged twice, byte-identical; the \`.dmg\` around the \`.app\` is not reproducible."
echo "Each file has a CycloneDX SBOM, \`<file>.cdx.json\`, listed in \`SHA256SUMS\`."
echo
echo '```sh'
echo "sha256sum -c SHA256SUMS --ignore-missing"
echo "gh attestation verify <file> -R $GITHUB_REPOSITORY"
echo '```'
echo
echo "To rebuild, use this commit's \`rust-toolchain.toml\` on the same image and linker:"
echo
echo "| Target | Runner image | Linker |"
echo "|---|---|---|"
cat ../meta/[!g]*.md ../meta/gui-*.md
} > ../notes.md
cat SHA256SUMS ../notes.md
- if: github.ref_type == 'tag'
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with: { subject-path: dist/SHA256SUMS }
- name: Draft the release
if: github.ref_type == 'tag'
env: { GH_TOKEN: "${{ github.token }}" }
run: >
gh release create "$GITHUB_REF_NAME" -R "$GITHUB_REPOSITORY" --draft --verify-tag
--title "strypt $GITHUB_REF_NAME" --notes-file notes.md dist/*
- if: github.ref_type != 'tag'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-preview
path: |
dist/SHA256SUMS
dist/*.cdx.json
notes.md