Skip to content

Commit 64bdb3e

Browse files
committed
ci: add Goal 3 iPhone build workflow
1 parent 3332f8e commit 64bdb3e

2 files changed

Lines changed: 375 additions & 0 deletions

File tree

Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
name: RustFerry physical iPhone
2+
3+
on:
4+
push:
5+
branches:
6+
- 'rustferry/goal3/builds/**'
7+
8+
permissions: {}
9+
10+
concurrency:
11+
group: 'rustferry-iphone-${{ github.repository_id }}'
12+
cancel-in-progress: false
13+
14+
env:
15+
DEVELOPER_DIR: '/Applications/Xcode.app/Contents/Developer'
16+
CARGO_TERM_COLOR: 'never'
17+
RUST_BACKTRACE: '0'
18+
RUSTUP_TOOLCHAIN: '1.92.0'
19+
RUSTFERRY_WORKER_REPOSITORY: 'https://github.com/shiroksh/rustferry'
20+
RUSTFERRY_WORKER_REVISION: '3332f8ed850a009e70465be1a7938db68c02cec4'
21+
RUSTFERRY_WORKER_VERSION: '0.1.0'
22+
RUSTFERRY_TRUSTED_SOURCE_REF: 'refs/heads/goal3/macless-iphone-builds'
23+
RUSTFERRY_TEMPORARY_REF_PREFIX: 'refs/heads/rustferry/goal3/builds'
24+
25+
jobs:
26+
worker:
27+
name: Build trusted macOS worker from exact source revision
28+
runs-on: macos-15
29+
timeout-minutes: 30
30+
permissions:
31+
contents: read
32+
outputs:
33+
sha256: '${{ steps.worker.outputs.sha256 }}'
34+
env:
35+
CARGO_HOME: '${{ github.workspace }}/.rustferry-worker-cargo-home'
36+
CARGO_TARGET_DIR: '${{ github.workspace }}/.rustferry-worker-target'
37+
steps:
38+
- name: Checkout exact trusted worker revision
39+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
40+
with:
41+
repository: 'shiroksh/rustferry'
42+
ref: '3332f8ed850a009e70465be1a7938db68c02cec4'
43+
fetch-depth: 1
44+
persist-credentials: false
45+
path: .rustferry-worker-source
46+
- name: Install exact Rust toolchain
47+
shell: bash
48+
run: |
49+
set -euo pipefail
50+
rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal --no-self-update
51+
actual="$(rustup run "$RUSTUP_TOOLCHAIN" rustc --version)"
52+
[[ "$actual" == "rustc 1.92.0 "* ]]
53+
- name: Build only the trusted RustFerry worker binary
54+
shell: bash
55+
run: |
56+
set -euo pipefail
57+
source_root="$GITHUB_WORKSPACE/.rustferry-worker-source"
58+
actual_revision="$(/usr/bin/git -C "$source_root" rev-parse --verify 'HEAD^{commit}')"
59+
if [[ "$actual_revision" != "$RUSTFERRY_WORKER_REVISION" ]]; then
60+
echo 'Trusted worker checkout revision mismatch.' >&2
61+
exit 1
62+
fi
63+
cargo build --locked --release \
64+
--manifest-path "$source_root/Cargo.toml" \
65+
--package rustferry-worker-macos \
66+
--bin ferry-worker-macos
67+
- name: Verify and package trusted RustFerry worker
68+
id: worker
69+
shell: bash
70+
run: |
71+
set -euo pipefail
72+
candidate="$CARGO_TARGET_DIR/release/ferry-worker-macos"
73+
artifact="$RUNNER_TEMP/rustferry-worker-artifact/ferry-worker-macos"
74+
test -f "$candidate" -a ! -L "$candidate"
75+
"$candidate" version --expect "$RUSTFERRY_WORKER_VERSION"
76+
install -d -m 0700 "$(dirname "$artifact")"
77+
install -m 0500 "$candidate" "$artifact"
78+
actual="$(shasum -a 256 "$artifact" | awk '{print $1}')"
79+
[[ "$actual" =~ ^[0-9a-f]{64}$ ]]
80+
"$artifact" version --expect "$RUSTFERRY_WORKER_VERSION"
81+
printf 'sha256=%s\n' "$actual" >> "$GITHUB_OUTPUT"
82+
- name: Upload immutable trusted worker artifact
83+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
84+
with:
85+
name: 'rustferry-worker-${{ github.run_id }}-${{ github.run_attempt }}'
86+
path: '${{ runner.temp }}/rustferry-worker-artifact/ferry-worker-macos'
87+
if-no-files-found: error
88+
compression-level: 0
89+
retention-days: 3
90+
91+
compile:
92+
name: Phase A - compile without signing secrets
93+
needs: worker
94+
runs-on: macos-15
95+
timeout-minutes: 90
96+
permissions:
97+
actions: read
98+
contents: read
99+
outputs:
100+
operation_id: '${{ steps.request.outputs.operation_id }}'
101+
project_path: '${{ steps.request.outputs.project_path }}'
102+
sealed_sha256: '${{ steps.sealed.outputs.sha256 }}'
103+
signing_mode: '${{ steps.request.outputs.signing_mode }}'
104+
source_revision: '${{ steps.request.outputs.source_revision }}'
105+
env:
106+
CARGO_HOME: '${{ github.workspace }}/.rustferry-cargo-home'
107+
CARGO_TARGET_DIR: '${{ github.workspace }}/.rustferry-cargo-target'
108+
steps:
109+
- name: Download exact trusted worker artifact
110+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
111+
with:
112+
name: 'rustferry-worker-${{ github.run_id }}-${{ github.run_attempt }}'
113+
path: '${{ runner.temp }}/rustferry-worker-download'
114+
- name: Verify exact trusted worker artifact
115+
shell: bash
116+
env:
117+
RUSTFERRY_EXPECTED_WORKER_SHA256: '${{ needs.worker.outputs.sha256 }}'
118+
run: |
119+
set -euo pipefail
120+
candidate="$RUNNER_TEMP/rustferry-worker-download/ferry-worker-macos"
121+
[[ "$RUSTFERRY_EXPECTED_WORKER_SHA256" =~ ^[0-9a-f]{64}$ ]]
122+
test -f "$candidate" -a ! -L "$candidate"
123+
actual="$(shasum -a 256 "$candidate" | awk '{print $1}')"
124+
if [[ "$actual" != "$RUSTFERRY_EXPECTED_WORKER_SHA256" ]]; then
125+
echo 'Trusted worker artifact integrity check failed.' >&2
126+
exit 1
127+
fi
128+
install -d -m 0700 "$(dirname "$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos")"
129+
install -m 0500 "$candidate" "$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos"
130+
installed="$(shasum -a 256 "$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos" | awk '{print $1}')"
131+
[[ "$installed" == "$RUSTFERRY_EXPECTED_WORKER_SHA256" ]]
132+
"$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos" version --expect "$RUSTFERRY_WORKER_VERSION"
133+
- name: Install exact Rust toolchain and physical-iPhone target
134+
shell: bash
135+
run: |
136+
set -euo pipefail
137+
rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal --no-self-update
138+
rustup target add --toolchain "$RUSTUP_TOOLCHAIN" aarch64-apple-ios
139+
actual="$(rustup run "$RUSTUP_TOOLCHAIN" rustc --version)"
140+
[[ "$actual" == "rustc 1.92.0 "* ]]
141+
- name: Checkout immutable dispatch request
142+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
143+
with:
144+
ref: '${{ github.sha }}'
145+
fetch-depth: 1
146+
persist-credentials: false
147+
path: .rustferry-dispatch
148+
- name: Checkout trusted source policy ref
149+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
150+
with:
151+
ref: '${{ env.RUSTFERRY_TRUSTED_SOURCE_REF }}'
152+
fetch-depth: 0
153+
persist-credentials: false
154+
path: .rustferry-trusted-source
155+
- name: Validate dispatch request and workflow revision
156+
id: request
157+
shell: bash
158+
run: |
159+
set -euo pipefail
160+
"$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos" github-request \
161+
--event "$GITHUB_EVENT_PATH" \
162+
--dispatch-root "$GITHUB_WORKSPACE/.rustferry-dispatch" \
163+
--trusted-source-root "$GITHUB_WORKSPACE/.rustferry-trusted-source" \
164+
--workflow-path ".github/workflows/rustferry-goal3-iphone.yml" \
165+
--push-manifest "$GITHUB_WORKSPACE/.rustferry-dispatch/.rustferry/goal3/request.json" \
166+
--trusted-source-ref "$RUSTFERRY_TRUSTED_SOURCE_REF" \
167+
--temporary-ref-prefix "$RUSTFERRY_TEMPORARY_REF_PREFIX" \
168+
--output-manifest "$RUNNER_TEMP/rustferry-request.json" \
169+
--github-output "$GITHUB_OUTPUT"
170+
- name: Checkout exact requested source revision
171+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
172+
with:
173+
ref: '${{ steps.request.outputs.source_revision }}'
174+
fetch-depth: 1
175+
persist-credentials: false
176+
path: source
177+
- name: Restore source-revision-scoped Cargo cache
178+
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
179+
with:
180+
path: |
181+
${{ env.CARGO_HOME }}/registry/index
182+
${{ env.CARGO_HOME }}/registry/cache
183+
${{ env.CARGO_HOME }}/git/db
184+
${{ env.CARGO_TARGET_DIR }}
185+
key: 'rustferry-ios-${{ runner.os }}-0.1.0-${{ steps.request.outputs.source_revision }}-${{ hashFiles(''source/Cargo.lock'') }}'
186+
- name: Compile and seal unsigned iPhone archive with worker
187+
shell: bash
188+
env:
189+
RUSTFERRY_OPERATION_ID: '${{ steps.request.outputs.operation_id }}'
190+
RUSTFERRY_PROJECT_PATH: '${{ steps.request.outputs.project_path }}'
191+
RUSTFERRY_SOURCE_REVISION: '${{ steps.request.outputs.source_revision }}'
192+
run: |
193+
set -euo pipefail
194+
"$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos" run-job \
195+
--phase compile \
196+
--manifest "$RUNNER_TEMP/rustferry-request.json" \
197+
--source-root "$GITHUB_WORKSPACE/source" \
198+
--trusted-source-root "$GITHUB_WORKSPACE/.rustferry-trusted-source" \
199+
--job-root "$RUNNER_TEMP/rustferry-compile-job" \
200+
--output-directory "$RUNNER_TEMP/rustferry-handoff"
201+
- name: Record sealed archive digest
202+
id: sealed
203+
shell: bash
204+
run: |
205+
set -euo pipefail
206+
archive="$RUNNER_TEMP/rustferry-handoff/unsigned-archive.zip"
207+
test -f "$archive" -a ! -L "$archive"
208+
digest="$(shasum -a 256 "$archive" | awk '{print $1}')"
209+
[[ "$digest" =~ ^[0-9a-f]{64}$ ]]
210+
printf 'sha256=%s\n' "$digest" >> "$GITHUB_OUTPUT"
211+
- name: Upload sealed unsigned handoff
212+
id: unsigned_artifact
213+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
214+
with:
215+
name: 'rustferry-unsigned-${{ github.run_id }}-${{ github.run_attempt }}'
216+
path: |
217+
${{ runner.temp }}/rustferry-handoff/unsigned-archive.zip
218+
${{ runner.temp }}/rustferry-handoff/sealed-archive.json
219+
${{ runner.temp }}/rustferry-handoff/compile-report.json
220+
${{ runner.temp }}/rustferry-handoff/sanitized-compile-log.txt
221+
if-no-files-found: error
222+
compression-level: 0
223+
retention-days: 3
224+
- name: Clean compile job workspace
225+
if: '${{ always() }}'
226+
shell: bash
227+
run: |
228+
set -euo pipefail
229+
if [[ -x "$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos" ]]; then
230+
"$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos" cleanup \
231+
--job-root "$RUNNER_TEMP/rustferry-compile-job" \
232+
--require-complete
233+
elif [[ -d "$RUNNER_TEMP/rustferry-compile-job" ]]; then
234+
echo 'Compile cleanup could not run because the trusted worker is unavailable.' >&2
235+
exit 1
236+
fi
237+
238+
sign:
239+
name: Phase B - protected development signing
240+
needs:
241+
- worker
242+
- compile
243+
if: needs.compile.outputs.signing_mode == 'manual_development' && github.event.repository.private == true
244+
runs-on: macos-15
245+
timeout-minutes: 30
246+
environment: 'rustferry-goal3-signing'
247+
permissions:
248+
actions: read
249+
steps:
250+
- name: Download exact trusted worker artifact
251+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
252+
with:
253+
name: 'rustferry-worker-${{ github.run_id }}-${{ github.run_attempt }}'
254+
path: '${{ runner.temp }}/rustferry-worker-download'
255+
- name: Verify exact trusted worker artifact
256+
shell: bash
257+
env:
258+
RUSTFERRY_EXPECTED_WORKER_SHA256: '${{ needs.worker.outputs.sha256 }}'
259+
run: |
260+
set -euo pipefail
261+
candidate="$RUNNER_TEMP/rustferry-worker-download/ferry-worker-macos"
262+
[[ "$RUSTFERRY_EXPECTED_WORKER_SHA256" =~ ^[0-9a-f]{64}$ ]]
263+
test -f "$candidate" -a ! -L "$candidate"
264+
actual="$(shasum -a 256 "$candidate" | awk '{print $1}')"
265+
if [[ "$actual" != "$RUSTFERRY_EXPECTED_WORKER_SHA256" ]]; then
266+
echo 'Trusted worker artifact integrity check failed.' >&2
267+
exit 1
268+
fi
269+
install -d -m 0700 "$(dirname "$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos")"
270+
install -m 0500 "$candidate" "$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos"
271+
installed="$(shasum -a 256 "$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos" | awk '{print $1}')"
272+
[[ "$installed" == "$RUSTFERRY_EXPECTED_WORKER_SHA256" ]]
273+
"$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos" version --expect "$RUSTFERRY_WORKER_VERSION"
274+
- name: Install exact Rust toolchain and physical-iPhone target
275+
shell: bash
276+
run: |
277+
set -euo pipefail
278+
rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal --no-self-update
279+
rustup target add --toolchain "$RUSTUP_TOOLCHAIN" aarch64-apple-ios
280+
actual="$(rustup run "$RUSTUP_TOOLCHAIN" rustc --version)"
281+
[[ "$actual" == "rustc 1.92.0 "* ]]
282+
- name: Download exact sealed unsigned handoff
283+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
284+
with:
285+
name: 'rustferry-unsigned-${{ github.run_id }}-${{ github.run_attempt }}'
286+
path: '${{ runner.temp }}/rustferry-handoff'
287+
- name: Verify sealed handoff digest before signing
288+
shell: bash
289+
env:
290+
RUSTFERRY_EXPECTED_SHA256: '${{ needs.compile.outputs.sealed_sha256 }}'
291+
run: |
292+
set -euo pipefail
293+
archive="$RUNNER_TEMP/rustferry-handoff/unsigned-archive.zip"
294+
[[ "$RUSTFERRY_EXPECTED_SHA256" =~ ^[0-9a-f]{64}$ ]]
295+
test -f "$archive" -a ! -L "$archive"
296+
actual="$(shasum -a 256 "$archive" | awk '{print $1}')"
297+
if [[ "$actual" != "$RUSTFERRY_EXPECTED_SHA256" ]]; then
298+
echo 'Sealed unsigned archive failed integrity verification.' >&2
299+
exit 1
300+
fi
301+
- name: Sign, export, and independently validate IPA with worker
302+
id: signed
303+
shell: bash
304+
env:
305+
RUSTFERRY_SIGNING_CERTIFICATE_P12: '${{ secrets.RUSTFERRY_GOAL3_IOS_CERTIFICATE_P12 }}'
306+
RUSTFERRY_SIGNING_CERTIFICATE_PASSWORD: '${{ secrets.RUSTFERRY_GOAL3_IOS_CERTIFICATE_PASSWORD }}'
307+
RUSTFERRY_SIGNING_PROVISIONING_PROFILE: '${{ secrets.RUSTFERRY_GOAL3_IOS_PROVISIONING_PROFILE }}'
308+
RUSTFERRY_EXPECTED_SHA256: '${{ needs.compile.outputs.sealed_sha256 }}'
309+
RUSTFERRY_OPERATION_ID: '${{ needs.compile.outputs.operation_id }}'
310+
RUSTFERRY_SOURCE_REVISION: '${{ needs.compile.outputs.source_revision }}'
311+
run: |
312+
set -euo pipefail
313+
printf '%s\0%s\0%s' \
314+
"$RUSTFERRY_SIGNING_CERTIFICATE_P12" \
315+
"$RUSTFERRY_SIGNING_CERTIFICATE_PASSWORD" \
316+
"$RUSTFERRY_SIGNING_PROVISIONING_PROFILE" |
317+
/usr/bin/env -i \
318+
"DEVELOPER_DIR=$DEVELOPER_DIR" \
319+
"HOME=$HOME" \
320+
'LC_ALL=C' \
321+
"PATH=$HOME/.cargo/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin" \
322+
"RUSTFERRY_WORKER_ROOT=$RUNNER_TEMP" \
323+
"RUSTUP_TOOLCHAIN=$RUSTUP_TOOLCHAIN" \
324+
"TMPDIR=$RUNNER_TEMP" \
325+
"$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos" run-job \
326+
--phase sign \
327+
--sealed-directory "$RUNNER_TEMP/rustferry-handoff" \
328+
--expected-sealed-sha256 "$RUSTFERRY_EXPECTED_SHA256" \
329+
--source-revision "$RUSTFERRY_SOURCE_REVISION" \
330+
--operation-id "$RUSTFERRY_OPERATION_ID" \
331+
--job-root "$RUNNER_TEMP/rustferry-sign-job" \
332+
--output-directory "$RUNNER_TEMP/rustferry-signed" \
333+
--certificate-p12-reference 'RUSTFERRY_GOAL3_IOS_CERTIFICATE_P12' \
334+
--certificate-password-reference 'RUSTFERRY_GOAL3_IOS_CERTIFICATE_PASSWORD' \
335+
--provisioning-profile-reference 'RUSTFERRY_GOAL3_IOS_PROVISIONING_PROFILE'
336+
- name: Remove signing material and temporary keychain
337+
if: '${{ always() }}'
338+
shell: bash
339+
run: |
340+
set -euo pipefail
341+
if [[ -x "$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos" ]]; then
342+
"$RUNNER_TEMP/rustferry-worker-runtime/ferry-worker-macos" cleanup \
343+
--job-root "$RUNNER_TEMP/rustferry-sign-job" \
344+
--require-complete
345+
elif [[ -d "$RUNNER_TEMP/rustferry-sign-job" ]]; then
346+
echo 'Signing cleanup could not run because the trusted worker is unavailable.' >&2
347+
exit 1
348+
fi
349+
- name: Upload validated development IPA
350+
if: '${{ success() }}'
351+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
352+
with:
353+
name: 'rustferry-iphone-${{ github.run_id }}-${{ github.run_attempt }}'
354+
path: |
355+
${{ runner.temp }}/rustferry-signed/application-development.ipa
356+
${{ runner.temp }}/rustferry-signed/artifact-manifest.json
357+
${{ runner.temp }}/rustferry-signed/signing-report.json
358+
${{ runner.temp }}/rustferry-signed/validation-report.json
359+
if-no-files-found: error
360+
compression-level: 0
361+
retention-days: 3

0 commit comments

Comments
 (0)