mirror of
https://github.com/openai/codex.git
synced 2026-10-26 18:38:47 +00:00
Bundle signed voice resources in macOS releases (#43983)
## What changed - Build, sign, and notarize `codex-voice-host` and its native runtime for Apple Silicon and Intel macOS release packages, granting the helper audio-input access. - Include voice resources in primary package archives and DMGs, with a root-level `codex` symlink to `bin/codex` so the runtime can be located. - Seal runtime receipts with post-signing hashes, require matching release versions and source builds, and bundle dependency notices, licenses, and source metadata. - Keep voice resources out of Python wheels to preserve their older macOS compatibility; the native voice build targets macOS 14. ## Testing Add packaging tests for alpha, beta, and stable versions, signed-byte preservation, license hashes, receipt validation, tamper detection, and exclusion of unlisted files. Extend release verification to check voice architectures, signatures, build identity, package hashes, and DMG contents. GitOrigin-RevId: 9f9415a8b2532d655a9a8740bcdf64066ddb7472
This commit is contained in:
committed by
copyberry
parent
dafb6781ee
commit
721f46a07a
@@ -480,8 +480,61 @@ jobs:
|
||||
path: |
|
||||
codex-rs/dist/${{ matrix.target }}/*
|
||||
|
||||
build-macos-voice:
|
||||
needs: tag-check
|
||||
name: Build voice runtime - ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 120
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
runner: macos-15
|
||||
prefix: macos_aarch64
|
||||
- target: x86_64-apple-darwin
|
||||
runner: macos-15-intel
|
||||
prefix: macos_x86_64
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0
|
||||
with:
|
||||
bazelisk-version: 1.28.1
|
||||
- name: Build matching helper and native runtime
|
||||
shell: bash
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
PREFIX: ${{ matrix.prefix }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bazel build //codex-rs/voice-host:codex-voice-host //third_party/voice:native_runtime
|
||||
runtime="bazel-bin/third_party/voice/native_runtime_${PREFIX}"
|
||||
PYTHONPATH=third_party/voice python3 - "$runtime" "$TARGET" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from package_runtime import runtime_files
|
||||
runtime_files(Path(sys.argv[1]), sys.argv[2])
|
||||
PY
|
||||
mkdir -p "voice-unsigned/${TARGET}"
|
||||
cp -R "$runtime" "voice-unsigned/${TARGET}/runtime"
|
||||
cp bazel-bin/codex-rs/voice-host/codex-voice-host "voice-unsigned/${TARGET}/codex-voice-host"
|
||||
tar -C "voice-unsigned/${TARGET}" -czf "voice-unsigned-${TARGET}.tar.gz" runtime codex-voice-host
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: voice-${{ matrix.target }}-unsigned
|
||||
path: voice-unsigned-${{ matrix.target }}.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
sign-macos-binaries:
|
||||
needs: build
|
||||
needs: [build, build-macos-voice]
|
||||
if: ${{ always() && needs.build.result == 'success' && needs.build-macos-voice.result == 'success' }}
|
||||
name: Sign macOS binaries - ${{ matrix.target }} - ${{ matrix.bundle }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
@@ -524,6 +577,13 @@ jobs:
|
||||
name: ${{ matrix.artifact_name }}-unsigned
|
||||
path: ${{ runner.temp }}/unsigned-macos
|
||||
|
||||
- name: Download unsigned voice runtime
|
||||
if: ${{ matrix.bundle == 'primary' }}
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: voice-${{ matrix.target }}-unsigned
|
||||
path: ${{ runner.temp }}/unsigned-voice
|
||||
|
||||
- name: Set up AKV PKCS11 macOS signing
|
||||
uses: ./.github/actions/setup-akv-pkcs11-codesigning
|
||||
with:
|
||||
@@ -647,6 +707,57 @@ jobs:
|
||||
--report-dir "$report_dir"
|
||||
done
|
||||
|
||||
- name: Sign and notarize voice runtime
|
||||
if: ${{ matrix.bundle == 'primary' }}
|
||||
shell: bash
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }}
|
||||
APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
unsigned="${RUNNER_TEMP}/unsigned-voice/extracted"
|
||||
signed="${GITHUB_WORKSPACE}/signed-voice/${TARGET}"
|
||||
mkdir -p "$unsigned" "$signed"
|
||||
tar -xzf "${RUNNER_TEMP}/unsigned-voice/voice-unsigned-${TARGET}.tar.gz" -C "$unsigned"
|
||||
python3 third_party/voice/release_runtime.py stage \
|
||||
--target "$TARGET" --source "$unsigned/runtime" --output "$signed/runtime"
|
||||
cp "$unsigned/codex-voice-host" "$signed/codex-voice-host"
|
||||
chmod 0755 "$signed/codex-voice-host"
|
||||
|
||||
while IFS= read -r -d '' library; do
|
||||
name="$(basename "$library")"
|
||||
.github/scripts/macos-signing/sign_macos_code.sh \
|
||||
--target "$library" --identity unused --deep false \
|
||||
--identifier "com.openai.codex.voice.${name}" \
|
||||
--options runtime --timestamp true
|
||||
done < <(find "$signed/runtime" -name '*.dylib' -type f -print0)
|
||||
.github/scripts/macos-signing/sign_macos_code.sh \
|
||||
--target "$signed/codex-voice-host" --identity unused --deep false \
|
||||
--identifier com.openai.codex.voice-host --options runtime --timestamp true \
|
||||
--entitlements .github/scripts/macos-signing/codex-voice-host.entitlements.plist
|
||||
|
||||
# Notarize the actual signed closure together, then hash those bytes.
|
||||
(cd "$signed" && zip -qr "${RUNNER_TEMP}/voice-${TARGET}.zip" runtime codex-voice-host)
|
||||
report_dir="${GITHUB_WORKSPACE}/macos-binary-signing-verification/${TARGET}/voice"
|
||||
mkdir -p "$report_dir"
|
||||
python3 .github/scripts/macos-signing/notarize_with_akv.py \
|
||||
--file "${RUNNER_TEMP}/voice-${TARGET}.zip" \
|
||||
--report-log "${report_dir}/notarization.json" \
|
||||
--max-wait-seconds 600
|
||||
python3 third_party/voice/release_runtime.py seal \
|
||||
--target "$TARGET" --output "$signed/runtime"
|
||||
tar -C "$signed" -czf "${GITHUB_WORKSPACE}/signed-voice-${TARGET}.tar.gz" \
|
||||
runtime codex-voice-host
|
||||
|
||||
- name: Upload signed voice runtime
|
||||
if: ${{ matrix.bundle == 'primary' }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: voice-${{ matrix.target }}-signed
|
||||
path: signed-voice-${{ matrix.target }}.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload signed macOS binaries
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
@@ -723,6 +834,41 @@ jobs:
|
||||
name: ${{ matrix.target }}-signed-resources
|
||||
path: codex-rs/signed-resources/${{ matrix.target }}
|
||||
|
||||
- name: Download signed voice runtime
|
||||
if: ${{ matrix.bundle == 'primary' }}
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: voice-${{ matrix.target }}-signed
|
||||
path: ${{ runner.temp }}/signed-voice
|
||||
|
||||
- name: Verify signed voice runtime
|
||||
if: ${{ matrix.bundle == 'primary' }}
|
||||
shell: bash
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
root="${RUNNER_TEMP}/signed-voice/${TARGET}"
|
||||
mkdir -p "$root"
|
||||
tar -xzf "${RUNNER_TEMP}/signed-voice/signed-voice-${TARGET}.tar.gz" -C "$root"
|
||||
case "$TARGET" in
|
||||
aarch64-apple-darwin) arch=arm64 ;;
|
||||
x86_64-apple-darwin) arch=x86_64 ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
PYTHONPATH="${GITHUB_WORKSPACE}/third_party/voice" python3 - "$root/runtime" "$TARGET" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from package_runtime import runtime_files
|
||||
runtime_files(Path(sys.argv[1]), sys.argv[2], public_release=True)
|
||||
PY
|
||||
while IFS= read -r -d '' binary; do
|
||||
lipo "$binary" -verify_arch "$arch"
|
||||
codesign --verify --strict --verbose=2 "$binary"
|
||||
done < <(find "$root/runtime" -name '*.dylib' -type f -print0)
|
||||
lipo "$root/codex-voice-host" -verify_arch "$arch"
|
||||
codesign --verify --strict --verbose=2 "$root/codex-voice-host"
|
||||
|
||||
- name: Verify signed macOS binaries
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -739,6 +885,43 @@ jobs:
|
||||
codesign --verify --strict --verbose=2 "$resource_path"
|
||||
done
|
||||
|
||||
- name: Stage macOS artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
dest="dist/${{ matrix.target }}"
|
||||
mkdir -p "$dest"
|
||||
|
||||
for binary in ${{ matrix.binaries }}; do
|
||||
# Both variants package the host, but only the primary bundle publishes
|
||||
# standalone binary archives to avoid duplicate release asset names.
|
||||
if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
|
||||
continue
|
||||
fi
|
||||
cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}"
|
||||
done
|
||||
|
||||
- name: Build Codex package archive
|
||||
shell: bash
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
BUNDLE: ${{ matrix.bundle }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
voice_args=()
|
||||
if [[ "$BUNDLE" == "primary" ]]; then
|
||||
voice_args+=(--voice-signed-dir "${RUNNER_TEMP}/signed-voice/${TARGET}")
|
||||
voice_args+=(--release-version "${GITHUB_REF_NAME#rust-v}")
|
||||
fi
|
||||
bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
|
||||
--target "$TARGET" \
|
||||
--bundle "$BUNDLE" \
|
||||
--entrypoint-dir "target/${TARGET}/release" \
|
||||
--archive-dir "dist/${TARGET}" \
|
||||
--rg-bin "signed-resources/${TARGET}/rg" \
|
||||
--zsh-bin "signed-resources/${TARGET}/zsh" \
|
||||
"${voice_args[@]}"
|
||||
|
||||
- name: Build unsigned macOS DMG
|
||||
if: ${{ matrix.build_dmg == 'true' }}
|
||||
shell: bash
|
||||
@@ -763,6 +946,15 @@ jobs:
|
||||
ditto "$binary_path" "${dmg_root}/${binary}"
|
||||
done
|
||||
|
||||
if [[ "${{ matrix.bundle }}" == "primary" ]]; then
|
||||
# Keep the root-level invocation, but resolve it into the canonical
|
||||
# package layout so InstallContext finds its adjacent voice runtime.
|
||||
package="${RUNNER_TEMP}/codex-package-voice-${target}"
|
||||
ditto "$package" "$dmg_root"
|
||||
rm "${dmg_root}/codex"
|
||||
ln -s bin/codex "${dmg_root}/codex"
|
||||
fi
|
||||
|
||||
rm -f "$dmg_path"
|
||||
hdiutil create \
|
||||
-volname "$volname" \
|
||||
@@ -784,37 +976,6 @@ jobs:
|
||||
path: codex-rs/target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Stage macOS artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
dest="dist/${{ matrix.target }}"
|
||||
mkdir -p "$dest"
|
||||
|
||||
for binary in ${{ matrix.binaries }}; do
|
||||
# Both variants package the host, but only the primary bundle publishes
|
||||
# standalone binary archives to avoid duplicate release asset names.
|
||||
if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
|
||||
continue
|
||||
fi
|
||||
cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}"
|
||||
done
|
||||
|
||||
- name: Build Codex package archive
|
||||
shell: bash
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
BUNDLE: ${{ matrix.bundle }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
|
||||
--target "$TARGET" \
|
||||
--bundle "$BUNDLE" \
|
||||
--entrypoint-dir "target/${TARGET}/release" \
|
||||
--archive-dir "dist/${TARGET}" \
|
||||
--rg-bin "signed-resources/${TARGET}/rg" \
|
||||
--zsh-bin "signed-resources/${TARGET}/zsh"
|
||||
|
||||
- name: Build Python runtime wheel
|
||||
if: ${{ matrix.bundle == 'primary' }}
|
||||
shell: bash
|
||||
@@ -837,13 +998,32 @@ jobs:
|
||||
python3 -m venv "${RUNNER_TEMP}/python-runtime-build-venv"
|
||||
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m pip install build
|
||||
|
||||
# The wheel keeps its macOS 10.9/11 tags. The native voice build
|
||||
# currently targets macOS 14, so keep it in the release archives and
|
||||
# DMG but leave this older-OS-compatible wheel unchanged.
|
||||
wheel_archives="${RUNNER_TEMP}/voice-free-wheel/${{ matrix.target }}"
|
||||
bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
|
||||
--target "${{ matrix.target }}" \
|
||||
--bundle primary \
|
||||
--entrypoint-dir "target/${{ matrix.target }}/release" \
|
||||
--archive-dir "$wheel_archives" \
|
||||
--rg-bin "signed-resources/${{ matrix.target }}/rg" \
|
||||
--zsh-bin "signed-resources/${{ matrix.target }}/zsh"
|
||||
wheel_archive="${wheel_archives}/codex-package-${{ matrix.target }}.tar.gz"
|
||||
python3 - "$wheel_archive" <<'PY'
|
||||
import sys
|
||||
import tarfile
|
||||
with tarfile.open(sys.argv[1]) as archive:
|
||||
assert not any("codex-resources/voice/" in item.name for item in archive)
|
||||
PY
|
||||
|
||||
stage_dir="${RUNNER_TEMP}/openai-codex-cli-bin-${{ matrix.target }}"
|
||||
wheel_dir="${GITHUB_WORKSPACE}/python-runtime-dist/${{ matrix.target }}"
|
||||
python3 \
|
||||
"${GITHUB_WORKSPACE}/sdk/python/scripts/update_sdk_artifacts.py" \
|
||||
stage-runtime \
|
||||
"$stage_dir" \
|
||||
"dist/${{ matrix.target }}/codex-package-${{ matrix.target }}.tar.gz" \
|
||||
"$wheel_archive" \
|
||||
--codex-version "${GITHUB_REF_NAME}" \
|
||||
--platform-tag "$platform_tag"
|
||||
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m build --wheel --outdir "$wheel_dir" "$stage_dir"
|
||||
@@ -1135,6 +1315,33 @@ jobs:
|
||||
rm -f "$entitlements"
|
||||
done
|
||||
|
||||
if [[ "${{ matrix.bundle }}" == "primary" ]]; then
|
||||
voice="${package_dir}/codex-resources/voice"
|
||||
[[ -f "${voice}/lib/libgstreamer-1.0.0.dylib" ]]
|
||||
export VOICE_BUILD_COMMIT="$(git rev-parse HEAD)"
|
||||
PYTHONPATH="${GITHUB_WORKSPACE}/third_party/voice" python3 - "$voice" "$target" "$package_dir" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from package_runtime import runtime_files
|
||||
from runtime import digest
|
||||
|
||||
voice, target, package = map(Path, sys.argv[1:])
|
||||
runtime_files(voice, str(target), public_release=True)
|
||||
manifest = json.loads((voice / "manifest.json").read_text())
|
||||
assert manifest["buildCommit"] == __import__("os").environ["VOICE_BUILD_COMMIT"]
|
||||
for relative, expected in manifest["sha256"].items():
|
||||
assert digest(package / relative) == expected, relative
|
||||
PY
|
||||
helper="${voice}/bin/codex-voice-host"
|
||||
[[ "$("$helper" --build-commit)" == "$VOICE_BUILD_COMMIT" ]]
|
||||
while IFS= read -r -d '' library; do
|
||||
lipo "$library" -verify_arch "$expected_arch"
|
||||
codesign --verify --strict --verbose=2 "$library"
|
||||
done < <(find "$voice" -name '*.dylib' -type f -print0)
|
||||
verify_signed_binary "$helper" "codex-voice-host"
|
||||
fi
|
||||
|
||||
if [[ "${{ matrix.verify_dmg }}" != "true" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
@@ -1162,6 +1369,15 @@ jobs:
|
||||
verify_signed_binary "${mount_dir}/${binary}" "$binary"
|
||||
done
|
||||
|
||||
if [[ "${{ matrix.bundle }}" == "primary" ]]; then
|
||||
[[ "$(readlink "${mount_dir}/codex")" == "bin/codex" ]]
|
||||
cmp "${mount_dir}/codex-package.json" "${package_dir}/codex-package.json"
|
||||
cmp "${mount_dir}/codex-resources/voice/manifest.json" \
|
||||
"${package_dir}/codex-resources/voice/manifest.json"
|
||||
cmp "${mount_dir}/codex-resources/voice/bin/codex-voice-host" \
|
||||
"${package_dir}/codex-resources/voice/bin/codex-voice-host"
|
||||
fi
|
||||
|
||||
cleanup_mount
|
||||
trap - EXIT
|
||||
cp "$dmg_path" "dist/${target}/codex-${target}.dmg"
|
||||
|
||||
Reference in New Issue
Block a user