Skip to content

Commit 2e5d52c

Browse files
authored
[release] Add a dmg target for MacOS (#8207)
Add a dmg target that bundles the codex and codex responses api proxy binaries for MacOS. this target is signed and notarized. Verified by triggering a build here: https://github.com/openai/codex/actions/runs/20318136302/job/58367155205. Downloaded the artifact and verified that the dmg is signed and notarized, and the codex binary contained works as expected.
1 parent be274cb commit 2e5d52c

File tree

3 files changed

+166
-21
lines changed

3 files changed

+166
-21
lines changed

.github/actions/macos-code-sign/action.yml

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ inputs:
44
target:
55
description: Rust compilation target triple (e.g. aarch64-apple-darwin).
66
required: true
7+
sign-binaries:
8+
description: Whether to sign and notarize the macOS binaries.
9+
required: false
10+
default: "true"
11+
sign-dmg:
12+
description: Whether to sign and notarize the macOS dmg.
13+
required: false
14+
default: "true"
715
apple-certificate:
816
description: Base64-encoded Apple signing certificate (P12).
917
required: true
@@ -107,6 +115,7 @@ runs:
107115
echo "::add-mask::$APPLE_CODESIGN_IDENTITY"
108116
109117
- name: Sign macOS binaries
118+
if: ${{ inputs.sign-binaries == 'true' }}
110119
shell: bash
111120
run: |
112121
set -euo pipefail
@@ -127,6 +136,7 @@ runs:
127136
done
128137
129138
- name: Notarize macOS binaries
139+
if: ${{ inputs.sign-binaries == 'true' }}
130140
shell: bash
131141
env:
132142
APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }}
@@ -149,6 +159,8 @@ runs:
149159
}
150160
trap cleanup_notary EXIT
151161
162+
source "$GITHUB_ACTION_PATH/notary_helpers.sh"
163+
152164
notarize_binary() {
153165
local binary="$1"
154166
local source_path="codex-rs/target/${{ inputs.target }}/release/${binary}"
@@ -162,31 +174,53 @@ runs:
162174
rm -f "$archive_path"
163175
ditto -c -k --keepParent "$source_path" "$archive_path"
164176
165-
submission_json=$(xcrun notarytool submit "$archive_path" \
166-
--key "$notary_key_path" \
167-
--key-id "$APPLE_NOTARIZATION_KEY_ID" \
168-
--issuer "$APPLE_NOTARIZATION_ISSUER_ID" \
169-
--output-format json \
170-
--wait)
171-
172-
status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"')
173-
submission_id=$(printf '%s\n' "$submission_json" | jq -r '.id // ""')
177+
notarize_submission "$binary" "$archive_path" "$notary_key_path"
178+
}
174179
175-
if [[ -z "$submission_id" ]]; then
176-
echo "Failed to retrieve submission ID for $binary"
177-
exit 1
178-
fi
180+
notarize_binary "codex"
181+
notarize_binary "codex-responses-api-proxy"
179182
180-
echo "::notice title=Notarization::$binary submission ${submission_id} completed with status ${status}"
183+
- name: Sign and notarize macOS dmg
184+
if: ${{ inputs.sign-dmg == 'true' }}
185+
shell: bash
186+
env:
187+
APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }}
188+
APPLE_NOTARIZATION_KEY_ID: ${{ inputs.apple-notarization-key-id }}
189+
APPLE_NOTARIZATION_ISSUER_ID: ${{ inputs.apple-notarization-issuer-id }}
190+
run: |
191+
set -euo pipefail
181192
182-
if [[ "$status" != "Accepted" ]]; then
183-
echo "Notarization failed for ${binary} (submission ${submission_id}, status ${status})"
193+
for var in APPLE_CODESIGN_IDENTITY APPLE_NOTARIZATION_KEY_P8 APPLE_NOTARIZATION_KEY_ID APPLE_NOTARIZATION_ISSUER_ID; do
194+
if [[ -z "${!var:-}" ]]; then
195+
echo "$var is required"
184196
exit 1
185197
fi
198+
done
199+
200+
notary_key_path="${RUNNER_TEMP}/notarytool.key.p8"
201+
echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$notary_key_path"
202+
cleanup_notary() {
203+
rm -f "$notary_key_path"
186204
}
205+
trap cleanup_notary EXIT
187206
188-
notarize_binary "codex"
189-
notarize_binary "codex-responses-api-proxy"
207+
source "$GITHUB_ACTION_PATH/notary_helpers.sh"
208+
209+
dmg_path="codex-rs/target/${{ inputs.target }}/release/codex-${{ inputs.target }}.dmg"
210+
211+
if [[ ! -f "$dmg_path" ]]; then
212+
echo "dmg $dmg_path not found"
213+
exit 1
214+
fi
215+
216+
keychain_args=()
217+
if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" && -f "${APPLE_CODESIGN_KEYCHAIN}" ]]; then
218+
keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}")
219+
fi
220+
221+
codesign --force --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$dmg_path"
222+
notarize_submission "codex-${{ inputs.target }}.dmg" "$dmg_path" "$notary_key_path"
223+
xcrun stapler staple "$dmg_path"
190224
191225
- name: Remove signing keychain
192226
if: ${{ always() }}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
3+
notarize_submission() {
4+
local label="$1"
5+
local path="$2"
6+
local notary_key_path="$3"
7+
8+
if [[ -z "${APPLE_NOTARIZATION_KEY_ID:-}" || -z "${APPLE_NOTARIZATION_ISSUER_ID:-}" ]]; then
9+
echo "APPLE_NOTARIZATION_KEY_ID and APPLE_NOTARIZATION_ISSUER_ID are required for notarization"
10+
exit 1
11+
fi
12+
13+
if [[ -z "$notary_key_path" || ! -f "$notary_key_path" ]]; then
14+
echo "Notary key file $notary_key_path not found"
15+
exit 1
16+
fi
17+
18+
if [[ ! -f "$path" ]]; then
19+
echo "Notarization payload $path not found"
20+
exit 1
21+
fi
22+
23+
local submission_json
24+
submission_json=$(xcrun notarytool submit "$path" \
25+
--key "$notary_key_path" \
26+
--key-id "$APPLE_NOTARIZATION_KEY_ID" \
27+
--issuer "$APPLE_NOTARIZATION_ISSUER_ID" \
28+
--output-format json \
29+
--wait)
30+
31+
local status submission_id
32+
status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"')
33+
submission_id=$(printf '%s\n' "$submission_json" | jq -r '.id // ""')
34+
35+
if [[ -z "$submission_id" ]]; then
36+
echo "Failed to retrieve submission ID for $label"
37+
exit 1
38+
fi
39+
40+
echo "::notice title=Notarization::$label submission ${submission_id} completed with status ${status}"
41+
42+
if [[ "$status" != "Accepted" ]]; then
43+
echo "Notarization failed for ${label} (submission ${submission_id}, status ${status})"
44+
exit 1
45+
fi
46+
}

.github/workflows/rust-release.yml

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,72 @@ jobs:
128128
account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
129129
certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }}
130130

131-
- if: ${{ matrix.runner == 'macos-15-xlarge' }}
132-
name: MacOS code signing
131+
- if: ${{ runner.os == 'macOS' }}
132+
name: MacOS code signing (binaries)
133133
uses: ./.github/actions/macos-code-sign
134134
with:
135135
target: ${{ matrix.target }}
136+
sign-binaries: "true"
137+
sign-dmg: "false"
138+
apple-certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }}
139+
apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
140+
apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
141+
apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
142+
apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
143+
144+
- if: ${{ runner.os == 'macOS' }}
145+
name: Build macOS dmg
146+
shell: bash
147+
run: |
148+
set -euo pipefail
149+
150+
target="${{ matrix.target }}"
151+
release_dir="target/${target}/release"
152+
dmg_root="${RUNNER_TEMP}/codex-dmg-root"
153+
volname="Codex (${target})"
154+
dmg_path="${release_dir}/codex-${target}.dmg"
155+
156+
# The previous "MacOS code signing (binaries)" step signs + notarizes the
157+
# built artifacts in `${release_dir}`. This step packages *those same*
158+
# signed binaries into a dmg.
159+
codex_binary_path="${release_dir}/codex"
160+
proxy_binary_path="${release_dir}/codex-responses-api-proxy"
161+
162+
rm -rf "$dmg_root"
163+
mkdir -p "$dmg_root"
164+
165+
if [[ ! -f "$codex_binary_path" ]]; then
166+
echo "Binary $codex_binary_path not found"
167+
exit 1
168+
fi
169+
if [[ ! -f "$proxy_binary_path" ]]; then
170+
echo "Binary $proxy_binary_path not found"
171+
exit 1
172+
fi
173+
174+
ditto "$codex_binary_path" "${dmg_root}/codex"
175+
ditto "$proxy_binary_path" "${dmg_root}/codex-responses-api-proxy"
176+
177+
rm -f "$dmg_path"
178+
hdiutil create \
179+
-volname "$volname" \
180+
-srcfolder "$dmg_root" \
181+
-format UDZO \
182+
-ov \
183+
"$dmg_path"
184+
185+
if [[ ! -f "$dmg_path" ]]; then
186+
echo "dmg $dmg_path not found after build"
187+
exit 1
188+
fi
189+
190+
- if: ${{ runner.os == 'macOS' }}
191+
name: MacOS code signing (dmg)
192+
uses: ./.github/actions/macos-code-sign
193+
with:
194+
target: ${{ matrix.target }}
195+
sign-binaries: "false"
196+
sign-dmg: "true"
136197
apple-certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }}
137198
apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
138199
apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
@@ -160,6 +221,10 @@ jobs:
160221
cp target/${{ matrix.target }}/release/codex-responses-api-proxy.sigstore "$dest/codex-responses-api-proxy-${{ matrix.target }}.sigstore"
161222
fi
162223
224+
if [[ "${{ matrix.target }}" == *apple-darwin ]]; then
225+
cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg"
226+
fi
227+
163228
- if: ${{ matrix.runner == 'windows-11-arm' }}
164229
name: Install zstd
165230
shell: powershell
@@ -194,7 +259,7 @@ jobs:
194259
base="$(basename "$f")"
195260
# Skip files that are already archives (shouldn't happen, but be
196261
# safe).
197-
if [[ "$base" == *.tar.gz || "$base" == *.zip ]]; then
262+
if [[ "$base" == *.tar.gz || "$base" == *.zip || "$base" == *.dmg ]]; then
198263
continue
199264
fi
200265

0 commit comments

Comments
 (0)