From 3f7111d320b56863f7fe895f9b379795be5f1c1b Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 14 Jan 2026 13:20:17 +0800 Subject: [PATCH 1/7] feat: add macOS code signing and notarization --- .github/actions/macos-code-sign/action.yml | 137 +++++++++++++++++++++ .github/workflows/release-kimi-cli.yml | 12 ++ 2 files changed, 149 insertions(+) create mode 100644 .github/actions/macos-code-sign/action.yml diff --git a/.github/actions/macos-code-sign/action.yml b/.github/actions/macos-code-sign/action.yml new file mode 100644 index 00000000..559ed4c6 --- /dev/null +++ b/.github/actions/macos-code-sign/action.yml @@ -0,0 +1,137 @@ +name: macos-code-sign +description: Sign and notarize macOS binaries + +inputs: + binary-path: + description: Path to the binary to sign + required: true + apple-certificate-p12: + description: Base64-encoded Apple signing certificate (P12) + required: true + apple-certificate-password: + description: Password for the signing certificate + required: true + apple-notarization-key-p8: + description: Base64-encoded Apple notarization key (P8) + required: true + apple-notarization-key-id: + description: Apple notarization key ID + required: true + apple-notarization-issuer-id: + description: Apple notarization issuer ID + required: true + +runs: + using: composite + steps: + - name: Import signing certificate + shell: bash + env: + APPLE_CERTIFICATE_P12: ${{ inputs.apple-certificate-p12 }} + APPLE_CERTIFICATE_PASSWORD: ${{ inputs.apple-certificate-password }} + KEYCHAIN_PASSWORD: actions + run: | + set -euo pipefail + + # Decode certificate + cert_path="${RUNNER_TEMP}/certificate.p12" + echo "$APPLE_CERTIFICATE_P12" | base64 -d > "$cert_path" + + # Create temporary keychain + keychain_path="${RUNNER_TEMP}/signing.keychain-db" + security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" + security set-keychain-settings -lut 21600 "$keychain_path" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" + + # Add to keychain search list + security list-keychains -d user -s "$keychain_path" $(security list-keychains -d user | tr -d '"') + security default-keychain -s "$keychain_path" + + # Import certificate + security import "$cert_path" -k "$keychain_path" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security + security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$keychain_path" > /dev/null + + # Find signing identity + IDENTITY=$(security find-identity -v -p codesigning "$keychain_path" | grep "Developer ID Application" | head -1 | sed -n 's/.*"\(Developer ID Application[^"]*\)".*/\1/p') + + if [[ -z "$IDENTITY" ]]; then + echo "❌ No Developer ID Application identity found" + security find-identity -v -p codesigning "$keychain_path" + exit 1 + fi + + echo "✅ Found signing identity: $IDENTITY" + echo "APPLE_SIGNING_IDENTITY=$IDENTITY" >> "$GITHUB_ENV" + echo "APPLE_KEYCHAIN_PATH=$keychain_path" >> "$GITHUB_ENV" + + rm -f "$cert_path" + + - name: Sign binary + shell: bash + env: + BINARY_PATH: ${{ inputs.binary-path }} + run: | + set -euo pipefail + + echo "Signing: $BINARY_PATH" + + codesign --force --options runtime --timestamp \ + --sign "$APPLE_SIGNING_IDENTITY" \ + --keychain "$APPLE_KEYCHAIN_PATH" \ + "$BINARY_PATH" + + echo "✅ Binary signed successfully" + codesign -dv --verbose=2 "$BINARY_PATH" + + - name: Notarize binary + shell: bash + env: + BINARY_PATH: ${{ inputs.binary-path }} + APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }} + APPLE_NOTARIZATION_KEY_ID: ${{ inputs.apple-notarization-key-id }} + APPLE_NOTARIZATION_ISSUER_ID: ${{ inputs.apple-notarization-issuer-id }} + run: | + set -euo pipefail + + # Save API key + key_path="${RUNNER_TEMP}/AuthKey.p8" + echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$key_path" + + # Create zip for notarization + binary_name=$(basename "$BINARY_PATH") + zip_path="${RUNNER_TEMP}/${binary_name}.zip" + ditto -c -k --keepParent "$BINARY_PATH" "$zip_path" + + echo "Submitting for notarization..." + + # Submit and wait + xcrun notarytool submit "$zip_path" \ + --key "$key_path" \ + --key-id "$APPLE_NOTARIZATION_KEY_ID" \ + --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ + --wait \ + --timeout 10m + + echo "✅ Notarization completed" + + # Cleanup + rm -f "$key_path" "$zip_path" + + - name: Verify signature + shell: bash + env: + BINARY_PATH: ${{ inputs.binary-path }} + run: | + set -euo pipefail + + echo "Verifying signature and notarization..." + spctl -a -vv "$BINARY_PATH" 2>&1 || true + codesign -dv --verbose=2 "$BINARY_PATH" + + - name: Cleanup keychain + if: always() + shell: bash + run: | + if [[ -n "${APPLE_KEYCHAIN_PATH:-}" && -f "${APPLE_KEYCHAIN_PATH}" ]]; then + security delete-keychain "$APPLE_KEYCHAIN_PATH" || true + fi diff --git a/.github/workflows/release-kimi-cli.yml b/.github/workflows/release-kimi-cli.yml index 175110ad..66f79666 100644 --- a/.github/workflows/release-kimi-cli.yml +++ b/.github/workflows/release-kimi-cli.yml @@ -76,6 +76,18 @@ jobs: - name: Build standalone binary run: make build-bin + # macOS code signing and notarization + - name: Sign and notarize macOS binary + if: runner.os == 'macOS' + uses: ./.github/actions/macos-code-sign + with: + binary-path: dist/kimi + apple-certificate-p12: ${{ secrets.APPLE_CERTIFICATE_P12 }} + apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }} + apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} + apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }} + - name: Package artifact shell: python env: From 40ddceadc11ce6eb5f1c2985f208947333816154 Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 14 Jan 2026 14:05:08 +0800 Subject: [PATCH 2/7] fix: use PyInstaller codesign-identity for proper signing --- .github/actions/macos-code-sign/action.yml | 44 +++++++-- .github/workflows/release-kimi-cli.yml | 105 +++++++++++++++++++-- 2 files changed, 130 insertions(+), 19 deletions(-) diff --git a/.github/actions/macos-code-sign/action.yml b/.github/actions/macos-code-sign/action.yml index 559ed4c6..273a433e 100644 --- a/.github/actions/macos-code-sign/action.yml +++ b/.github/actions/macos-code-sign/action.yml @@ -1,5 +1,5 @@ name: macos-code-sign -description: Sign and notarize macOS binaries +description: Sign and notarize macOS PyInstaller binaries inputs: binary-path: @@ -66,21 +66,26 @@ runs: rm -f "$cert_path" - - name: Sign binary + - name: Sign PyInstaller binary and embedded libraries shell: bash env: BINARY_PATH: ${{ inputs.binary-path }} run: | set -euo pipefail - echo "Signing: $BINARY_PATH" + echo "Signing PyInstaller binary: $BINARY_PATH" + + # PyInstaller onefile binaries embed libraries that get extracted at runtime. + # We need to unpack, sign everything, and repack. - codesign --force --options runtime --timestamp \ + # First, try signing the binary directly with --deep + # For single-file PyInstaller executables, this should work + codesign --deep --force --options runtime --timestamp \ --sign "$APPLE_SIGNING_IDENTITY" \ --keychain "$APPLE_KEYCHAIN_PATH" \ "$BINARY_PATH" - echo "✅ Binary signed successfully" + echo "✅ Binary signed" codesign -dv --verbose=2 "$BINARY_PATH" - name: Notarize binary @@ -105,14 +110,33 @@ runs: echo "Submitting for notarization..." # Submit and wait - xcrun notarytool submit "$zip_path" \ + result=$(xcrun notarytool submit "$zip_path" \ --key "$key_path" \ --key-id "$APPLE_NOTARIZATION_KEY_ID" \ --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ --wait \ - --timeout 10m + --timeout 10m \ + --output-format json 2>&1) || true - echo "✅ Notarization completed" + echo "$result" + + status=$(echo "$result" | grep -o '"status":"[^"]*"' | cut -d'"' -f4 || echo "unknown") + + if [[ "$status" == "Accepted" ]]; then + echo "✅ Notarization successful" + else + echo "⚠️ Notarization status: $status" + # Get detailed log + submission_id=$(echo "$result" | grep -o '"id":"[^"]*"' | cut -d'"' -f4 || echo "") + if [[ -n "$submission_id" ]]; then + echo "Fetching notarization log..." + xcrun notarytool log "$submission_id" \ + --key "$key_path" \ + --key-id "$APPLE_NOTARIZATION_KEY_ID" \ + --issuer "$APPLE_NOTARIZATION_ISSUER_ID" || true + fi + exit 1 + fi # Cleanup rm -f "$key_path" "$zip_path" @@ -125,8 +149,10 @@ runs: set -euo pipefail echo "Verifying signature and notarization..." - spctl -a -vv "$BINARY_PATH" 2>&1 || true codesign -dv --verbose=2 "$BINARY_PATH" + echo "" + echo "Gatekeeper check:" + spctl -a -vv "$BINARY_PATH" 2>&1 || true - name: Cleanup keychain if: always() diff --git a/.github/workflows/release-kimi-cli.yml b/.github/workflows/release-kimi-cli.yml index 66f79666..db7b9b2f 100644 --- a/.github/workflows/release-kimi-cli.yml +++ b/.github/workflows/release-kimi-cli.yml @@ -73,20 +73,105 @@ jobs: - name: Prepare building environment run: make prepare-build + # macOS: Setup signing certificate before build + - name: Setup macOS signing certificate + if: runner.os == 'macOS' + env: + APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + KEYCHAIN_PASSWORD: actions + run: | + set -euo pipefail + + # Decode certificate + cert_path="${RUNNER_TEMP}/certificate.p12" + echo "$APPLE_CERTIFICATE_P12" | base64 -d > "$cert_path" + + # Create temporary keychain + keychain_path="${RUNNER_TEMP}/signing.keychain-db" + security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" + security set-keychain-settings -lut 21600 "$keychain_path" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" + + # Add to keychain search list + security list-keychains -d user -s "$keychain_path" $(security list-keychains -d user | tr -d '"') + security default-keychain -s "$keychain_path" + + # Import certificate + security import "$cert_path" -k "$keychain_path" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security + security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$keychain_path" > /dev/null + + # Find signing identity + IDENTITY=$(security find-identity -v -p codesigning "$keychain_path" | grep "Developer ID Application" | head -1 | sed -n 's/.*"\(Developer ID Application[^"]*\)".*/\1/p') + + if [[ -z "$IDENTITY" ]]; then + echo "❌ No Developer ID Application identity found" + security find-identity -v -p codesigning "$keychain_path" + exit 1 + fi + + echo "✅ Found signing identity: $IDENTITY" + echo "APPLE_SIGNING_IDENTITY=$IDENTITY" >> "$GITHUB_ENV" + echo "APPLE_KEYCHAIN_PATH=$keychain_path" >> "$GITHUB_ENV" + + rm -f "$cert_path" + + # Build with signing on macOS + - name: Build standalone binary (macOS with signing) + if: runner.os == 'macOS' + run: | + # Build with PyInstaller using codesign identity + uv run pyinstaller kimi.spec \ + --codesign-identity "$APPLE_SIGNING_IDENTITY" + + # Build without signing on other platforms - name: Build standalone binary + if: runner.os != 'macOS' run: make build-bin - # macOS code signing and notarization - - name: Sign and notarize macOS binary + # macOS: Notarize the signed binary + - name: Notarize macOS binary if: runner.os == 'macOS' - uses: ./.github/actions/macos-code-sign - with: - binary-path: dist/kimi - apple-certificate-p12: ${{ secrets.APPLE_CERTIFICATE_P12 }} - apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }} - apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} - apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }} + env: + APPLE_NOTARIZATION_KEY_P8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }} + APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} + APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }} + run: | + set -euo pipefail + + # Save API key + key_path="${RUNNER_TEMP}/AuthKey.p8" + echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$key_path" + + # Create zip for notarization + zip_path="${RUNNER_TEMP}/kimi.zip" + ditto -c -k --keepParent dist/kimi "$zip_path" + + echo "Submitting for notarization..." + + xcrun notarytool submit "$zip_path" \ + --key "$key_path" \ + --key-id "$APPLE_NOTARIZATION_KEY_ID" \ + --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ + --wait \ + --timeout 15m + + echo "✅ Notarization completed" + + # Verify + echo "Verifying signature..." + codesign -dv --verbose=2 dist/kimi + + # Cleanup + rm -f "$key_path" "$zip_path" + + # macOS: Cleanup keychain + - name: Cleanup macOS keychain + if: always() && runner.os == 'macOS' + run: | + if [[ -n "${APPLE_KEYCHAIN_PATH:-}" && -f "${APPLE_KEYCHAIN_PATH}" ]]; then + security delete-keychain "$APPLE_KEYCHAIN_PATH" || true + fi - name: Package artifact shell: python From 0f6c08149916a403e41fdaf2e87f48231c4b4d7b Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 14 Jan 2026 14:09:30 +0800 Subject: [PATCH 3/7] fix: read codesign identity from env in kimi.spec --- .github/workflows/release-kimi-cli.yml | 7 ++----- kimi.spec | 6 +++++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-kimi-cli.yml b/.github/workflows/release-kimi-cli.yml index db7b9b2f..ba58c350 100644 --- a/.github/workflows/release-kimi-cli.yml +++ b/.github/workflows/release-kimi-cli.yml @@ -116,13 +116,10 @@ jobs: rm -f "$cert_path" - # Build with signing on macOS + # Build with signing on macOS (APPLE_SIGNING_IDENTITY is read by kimi.spec) - name: Build standalone binary (macOS with signing) if: runner.os == 'macOS' - run: | - # Build with PyInstaller using codesign identity - uv run pyinstaller kimi.spec \ - --codesign-identity "$APPLE_SIGNING_IDENTITY" + run: make build-bin # Build without signing on other platforms - name: Build standalone binary diff --git a/kimi.spec b/kimi.spec index 5e31656f..c63e2f7f 100644 --- a/kimi.spec +++ b/kimi.spec @@ -1,7 +1,11 @@ # -*- mode: python ; coding: utf-8 -*- +import os from kimi_cli.utils.pyinstaller import datas, hiddenimports +# Read codesign identity from environment variable (for macOS signing in CI) +codesign_identity = os.environ.get("APPLE_SIGNING_IDENTITY", None) + a = Analysis( ["src/kimi_cli/cli/__main__.py"], pathex=[], @@ -34,6 +38,6 @@ exe = EXE( disable_windowed_traceback=False, argv_emulation=False, target_arch=None, - codesign_identity=None, + codesign_identity=codesign_identity, entitlements_file=None, ) From 46e67aa758079fa0234fa5c40b5e05cf00f15b0d Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 14 Jan 2026 14:25:31 +0800 Subject: [PATCH 4/7] fix: staple notarization ticket to binary --- .github/workflows/release-kimi-cli.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/release-kimi-cli.yml b/.github/workflows/release-kimi-cli.yml index ba58c350..7210332d 100644 --- a/.github/workflows/release-kimi-cli.yml +++ b/.github/workflows/release-kimi-cli.yml @@ -155,6 +155,11 @@ jobs: echo "✅ Notarization completed" + # Staple the notarization ticket to the binary + echo "Stapling notarization ticket..." + xcrun stapler staple dist/kimi + echo "✅ Ticket stapled" + # Verify echo "Verifying signature..." codesign -dv --verbose=2 dist/kimi From d311685837d43fa201f5c2ba0db52284d3d9a264 Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 14 Jan 2026 14:29:37 +0800 Subject: [PATCH 5/7] fix: package macOS as DMG with stapled notarization ticket --- .github/workflows/release-kimi-cli.yml | 59 +++++++++++++++++--------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release-kimi-cli.yml b/.github/workflows/release-kimi-cli.yml index 7210332d..02d60106 100644 --- a/.github/workflows/release-kimi-cli.yml +++ b/.github/workflows/release-kimi-cli.yml @@ -155,10 +155,14 @@ jobs: echo "✅ Notarization completed" - # Staple the notarization ticket to the binary - echo "Stapling notarization ticket..." - xcrun stapler staple dist/kimi - echo "✅ Ticket stapled" + # Create DMG and staple ticket to it (stapler doesn't work on raw binaries) + echo "Creating DMG..." + dmg_path="dist/kimi.dmg" + hdiutil create -volname "Kimi CLI" -srcfolder dist/kimi -ov -format UDZO "$dmg_path" + + echo "Stapling notarization ticket to DMG..." + xcrun stapler staple "$dmg_path" + echo "✅ DMG created and stapled" # Verify echo "Verifying signature..." @@ -183,6 +187,7 @@ jobs: run: | import os import pathlib + import shutil import tarfile import zipfile @@ -194,23 +199,35 @@ jobs: artifacts_dir.mkdir(parents=True, exist_ok=True) is_windows = "windows" in target - binary_name = "kimi.exe" if is_windows else "kimi" - binary_path = dist_dir / binary_name - if not binary_path.exists(): - raise SystemExit(f"Binary not found at {binary_path}") - - archive_ext = "zip" if is_windows else "tar.gz" - archive_name = f"kimi-{tag}-{target}.{archive_ext}" - archive_path = artifacts_dir / archive_name - - if is_windows: - with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive_file: - archive_file.write(binary_path, arcname=binary_name) + is_macos = "apple-darwin" in target + + # For macOS, use the stapled DMG + if is_macos: + dmg_path = dist_dir / "kimi.dmg" + if not dmg_path.exists(): + raise SystemExit(f"DMG not found at {dmg_path}") + archive_name = f"kimi-{tag}-{target}.dmg" + archive_path = artifacts_dir / archive_name + shutil.copy(dmg_path, archive_path) + print(f"Built artifact: {archive_path}") else: - with tarfile.open(archive_path, "w:gz") as archive_file: - archive_file.add(binary_path, arcname=binary_name) + binary_name = "kimi.exe" if is_windows else "kimi" + binary_path = dist_dir / binary_name + if not binary_path.exists(): + raise SystemExit(f"Binary not found at {binary_path}") + + archive_ext = "zip" if is_windows else "tar.gz" + archive_name = f"kimi-{tag}-{target}.{archive_ext}" + archive_path = artifacts_dir / archive_name + + if is_windows: + with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive_file: + archive_file.write(binary_path, arcname=binary_name) + else: + with tarfile.open(archive_path, "w:gz") as archive_file: + archive_file.add(binary_path, arcname=binary_name) - print(f"Built artifact: {archive_path}") + print(f"Built artifact: {archive_path}") - name: Upload artifact uses: actions/upload-artifact@v4 @@ -240,7 +257,7 @@ jobs: set -euxo pipefail cd downloads shopt -s nullglob - for f in *.tar.gz *.zip; do + for f in *.tar.gz *.zip *.dmg; do sha256sum "$f" > "$f.sha256" echo "sha256($(basename "$f"))=$(cut -d' ' -f1 "$f.sha256")" done @@ -255,8 +272,10 @@ jobs: files: | downloads/*.tar.gz downloads/*.zip + downloads/*.dmg downloads/*.tar.gz.sha256 downloads/*.zip.sha256 + downloads/*.dmg.sha256 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 630e9a9aaa9608b01e64f0a544bfbe5c09620bf4 Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 14 Jan 2026 15:01:10 +0800 Subject: [PATCH 6/7] fix: notarize DMG itself (not zip) so staple works --- .github/workflows/release-kimi-cli.yml | 36 ++++++++++++++------------ 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release-kimi-cli.yml b/.github/workflows/release-kimi-cli.yml index 02d60106..dca49aa9 100644 --- a/.github/workflows/release-kimi-cli.yml +++ b/.github/workflows/release-kimi-cli.yml @@ -126,8 +126,8 @@ jobs: if: runner.os != 'macOS' run: make build-bin - # macOS: Notarize the signed binary - - name: Notarize macOS binary + # macOS: Create DMG, notarize it, and staple + - name: Create and notarize macOS DMG if: runner.os == 'macOS' env: APPLE_NOTARIZATION_KEY_P8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }} @@ -140,13 +140,18 @@ jobs: key_path="${RUNNER_TEMP}/AuthKey.p8" echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$key_path" - # Create zip for notarization - zip_path="${RUNNER_TEMP}/kimi.zip" - ditto -c -k --keepParent dist/kimi "$zip_path" + # Create DMG first (must notarize the DMG itself to staple it) + echo "Creating DMG..." + dmg_path="dist/kimi.dmg" + hdiutil create -volname "Kimi CLI" -srcfolder dist/kimi -ov -format UDZO "$dmg_path" - echo "Submitting for notarization..." - - xcrun notarytool submit "$zip_path" \ + # Sign the DMG + echo "Signing DMG..." + codesign --force --timestamp --sign "$APPLE_SIGNING_IDENTITY" --keychain "$APPLE_KEYCHAIN_PATH" "$dmg_path" + + # Notarize the DMG + echo "Submitting DMG for notarization..." + xcrun notarytool submit "$dmg_path" \ --key "$key_path" \ --key-id "$APPLE_NOTARIZATION_KEY_ID" \ --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ @@ -155,21 +160,18 @@ jobs: echo "✅ Notarization completed" - # Create DMG and staple ticket to it (stapler doesn't work on raw binaries) - echo "Creating DMG..." - dmg_path="dist/kimi.dmg" - hdiutil create -volname "Kimi CLI" -srcfolder dist/kimi -ov -format UDZO "$dmg_path" - + # Staple the ticket to the DMG echo "Stapling notarization ticket to DMG..." xcrun stapler staple "$dmg_path" - echo "✅ DMG created and stapled" + echo "✅ DMG stapled" # Verify - echo "Verifying signature..." - codesign -dv --verbose=2 dist/kimi + echo "Verifying DMG..." + xcrun stapler validate "$dmg_path" + spctl -a -vv "$dmg_path" || true # Cleanup - rm -f "$key_path" "$zip_path" + rm -f "$key_path" # macOS: Cleanup keychain - name: Cleanup macOS keychain From 694a568b4e398f540624a2ca700d4641498874c7 Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 14 Jan 2026 15:08:41 +0800 Subject: [PATCH 7/7] chore: remove DMG distribution, keep tar.gz for macOS --- .github/workflows/release-kimi-cli.yml | 80 +++++++++----------------- 1 file changed, 27 insertions(+), 53 deletions(-) diff --git a/.github/workflows/release-kimi-cli.yml b/.github/workflows/release-kimi-cli.yml index dca49aa9..ba58c350 100644 --- a/.github/workflows/release-kimi-cli.yml +++ b/.github/workflows/release-kimi-cli.yml @@ -126,8 +126,8 @@ jobs: if: runner.os != 'macOS' run: make build-bin - # macOS: Create DMG, notarize it, and staple - - name: Create and notarize macOS DMG + # macOS: Notarize the signed binary + - name: Notarize macOS binary if: runner.os == 'macOS' env: APPLE_NOTARIZATION_KEY_P8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }} @@ -140,18 +140,13 @@ jobs: key_path="${RUNNER_TEMP}/AuthKey.p8" echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$key_path" - # Create DMG first (must notarize the DMG itself to staple it) - echo "Creating DMG..." - dmg_path="dist/kimi.dmg" - hdiutil create -volname "Kimi CLI" -srcfolder dist/kimi -ov -format UDZO "$dmg_path" + # Create zip for notarization + zip_path="${RUNNER_TEMP}/kimi.zip" + ditto -c -k --keepParent dist/kimi "$zip_path" - # Sign the DMG - echo "Signing DMG..." - codesign --force --timestamp --sign "$APPLE_SIGNING_IDENTITY" --keychain "$APPLE_KEYCHAIN_PATH" "$dmg_path" - - # Notarize the DMG - echo "Submitting DMG for notarization..." - xcrun notarytool submit "$dmg_path" \ + echo "Submitting for notarization..." + + xcrun notarytool submit "$zip_path" \ --key "$key_path" \ --key-id "$APPLE_NOTARIZATION_KEY_ID" \ --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ @@ -160,18 +155,12 @@ jobs: echo "✅ Notarization completed" - # Staple the ticket to the DMG - echo "Stapling notarization ticket to DMG..." - xcrun stapler staple "$dmg_path" - echo "✅ DMG stapled" - # Verify - echo "Verifying DMG..." - xcrun stapler validate "$dmg_path" - spctl -a -vv "$dmg_path" || true + echo "Verifying signature..." + codesign -dv --verbose=2 dist/kimi # Cleanup - rm -f "$key_path" + rm -f "$key_path" "$zip_path" # macOS: Cleanup keychain - name: Cleanup macOS keychain @@ -189,7 +178,6 @@ jobs: run: | import os import pathlib - import shutil import tarfile import zipfile @@ -201,35 +189,23 @@ jobs: artifacts_dir.mkdir(parents=True, exist_ok=True) is_windows = "windows" in target - is_macos = "apple-darwin" in target - - # For macOS, use the stapled DMG - if is_macos: - dmg_path = dist_dir / "kimi.dmg" - if not dmg_path.exists(): - raise SystemExit(f"DMG not found at {dmg_path}") - archive_name = f"kimi-{tag}-{target}.dmg" - archive_path = artifacts_dir / archive_name - shutil.copy(dmg_path, archive_path) - print(f"Built artifact: {archive_path}") + binary_name = "kimi.exe" if is_windows else "kimi" + binary_path = dist_dir / binary_name + if not binary_path.exists(): + raise SystemExit(f"Binary not found at {binary_path}") + + archive_ext = "zip" if is_windows else "tar.gz" + archive_name = f"kimi-{tag}-{target}.{archive_ext}" + archive_path = artifacts_dir / archive_name + + if is_windows: + with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive_file: + archive_file.write(binary_path, arcname=binary_name) else: - binary_name = "kimi.exe" if is_windows else "kimi" - binary_path = dist_dir / binary_name - if not binary_path.exists(): - raise SystemExit(f"Binary not found at {binary_path}") - - archive_ext = "zip" if is_windows else "tar.gz" - archive_name = f"kimi-{tag}-{target}.{archive_ext}" - archive_path = artifacts_dir / archive_name - - if is_windows: - with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive_file: - archive_file.write(binary_path, arcname=binary_name) - else: - with tarfile.open(archive_path, "w:gz") as archive_file: - archive_file.add(binary_path, arcname=binary_name) + with tarfile.open(archive_path, "w:gz") as archive_file: + archive_file.add(binary_path, arcname=binary_name) - print(f"Built artifact: {archive_path}") + print(f"Built artifact: {archive_path}") - name: Upload artifact uses: actions/upload-artifact@v4 @@ -259,7 +235,7 @@ jobs: set -euxo pipefail cd downloads shopt -s nullglob - for f in *.tar.gz *.zip *.dmg; do + for f in *.tar.gz *.zip; do sha256sum "$f" > "$f.sha256" echo "sha256($(basename "$f"))=$(cut -d' ' -f1 "$f.sha256")" done @@ -274,10 +250,8 @@ jobs: files: | downloads/*.tar.gz downloads/*.zip - downloads/*.dmg downloads/*.tar.gz.sha256 downloads/*.zip.sha256 - downloads/*.dmg.sha256 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}