Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions .github/actions/macos-code-sign/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: macos-code-sign
description: Sign and notarize macOS PyInstaller 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 PyInstaller binary and embedded libraries
shell: bash
env:
BINARY_PATH: ${{ inputs.binary-path }}
run: |
set -euo pipefail

echo "Signing PyInstaller binary: $BINARY_PATH"

# PyInstaller onefile binaries embed libraries that get extracted at runtime.
# We need to unpack, sign everything, and repack.

# 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"
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
result=$(xcrun notarytool submit "$zip_path" \
--key "$key_path" \
--key-id "$APPLE_NOTARIZATION_KEY_ID" \
--issuer "$APPLE_NOTARIZATION_ISSUER_ID" \
--wait \
--timeout 10m \
--output-format json 2>&1) || true

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"

- name: Verify signature
shell: bash
env:
BINARY_PATH: ${{ inputs.binary-path }}
run: |
set -euo pipefail

echo "Verifying signature and notarization..."
codesign -dv --verbose=2 "$BINARY_PATH"
echo ""
echo "Gatekeeper check:"
spctl -a -vv "$BINARY_PATH" 2>&1 || true

- 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
94 changes: 94 additions & 0 deletions .github/workflows/release-kimi-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,103 @@ 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 (APPLE_SIGNING_IDENTITY is read by kimi.spec)
- name: Build standalone binary (macOS with signing)
if: runner.os == 'macOS'
run: make build-bin

# Build without signing on other platforms
- name: Build standalone binary
if: runner.os != 'macOS'
run: make build-bin
Comment on lines +119 to 127
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems no need to have two different step?


# macOS: Notarize the signed binary
- name: Notarize macOS binary
if: runner.os == 'macOS'
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
env:
Expand Down
6 changes: 5 additions & 1 deletion kimi.spec
Original file line number Diff line number Diff line change
@@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
codesign_identity = os.environ.get("APPLE_SIGNING_IDENTITY", None)
codesign_identity = os.getenv("APPLE_SIGNING_IDENTITY")


a = Analysis(
["src/kimi_cli/cli/__main__.py"],
pathex=[],
Expand Down Expand Up @@ -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,
)