feat(ci): add macOS code signing and Apple notarization to release workflow

- Import Developer ID Application certificate into temporary keychain
- Inject APPLE_SIGNING_IDENTITY/APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID
  into Tauri build step for automatic signing and notarization
- Staple notarization tickets to both .app and .dmg (hard-fail)
- Add verification step: codesign --verify + spctl -a + stapler validate
  for both .app and .dmg, gating the release on success
- Collect .dmg alongside .tar.gz and .zip in release assets
- Clean up temporary keychain with original default restored
- Update release notes to recommend .dmg and note Apple notarization
- Remove all xattr workarounds and "unidentified developer" warnings
  from README, README_ZH, installation guides, and FAQ (EN/ZH/JA)
This commit is contained in:
Jason
2026-03-23 22:43:41 +08:00
parent 0a301a497c
commit 44b6eacf87
10 changed files with 216 additions and 108 deletions
+159 -10
View File
@@ -150,9 +150,56 @@ jobs:
fi
echo "✅ Tauri signing key prepared"
- name: Import Apple signing certificate
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
# Decode .p12 certificate from base64
CERT_PATH="$RUNNER_TEMP/certificate.p12"
printf '%s' "${{ secrets.APPLE_CERTIFICATE }}" | (base64 --decode 2>/dev/null || base64 -D) > "$CERT_PATH"
# Save original default keychain for cleanup
ORIGINAL_DEFAULT_KEYCHAIN=$(security default-keychain -d user | tr -d '"' | xargs)
echo "ORIGINAL_DEFAULT_KEYCHAIN=$ORIGINAL_DEFAULT_KEYCHAIN" >> "$GITHUB_ENV"
# Create temporary keychain
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db"
security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" "$KEYCHAIN_PATH"
# Import certificate
security import "$CERT_PATH" \
-k "$KEYCHAIN_PATH" \
-P "${{ secrets.APPLE_CERTIFICATE_PASSWORD }}" \
-T /usr/bin/codesign \
-T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${{ secrets.KEYCHAIN_PASSWORD }}" "$KEYCHAIN_PATH"
# Dynamically resolve signing identity (must be "Developer ID Application")
IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" \
| grep "Developer ID Application" | grep -oE '"[^"]+"' | head -1 | tr -d '"')
if [ -z "$IDENTITY" ]; then
echo "❌ No 'Developer ID Application' identity found — listing all identities:" >&2
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
exit 1
fi
echo "✅ Signing identity: $IDENTITY"
echo "APPLE_SIGNING_IDENTITY=$IDENTITY" >> "$GITHUB_ENV"
# Cleanup certificate file
rm -f "$CERT_PATH"
- name: Build Tauri App (macOS)
if: runner.os == 'macOS'
run: pnpm tauri build --target universal-apple-darwin
env:
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Build Tauri App (Windows)
if: runner.os == 'Windows'
@@ -169,38 +216,127 @@ jobs:
set -euxo pipefail
mkdir -p release-assets
VERSION="${GITHUB_REF_NAME}" # e.g., v3.5.0
echo "Looking for updater artifact (.tar.gz) and .app for zip..."
TAR_GZ=""; APP_PATH=""
# Locate bundle directory
BUNDLE_DIR=""
TAR_GZ=""; APP_PATH=""; DMG_PATH=""
for path in \
"src-tauri/target/universal-apple-darwin/release/bundle/macos" \
"src-tauri/target/aarch64-apple-darwin/release/bundle/macos" \
"src-tauri/target/x86_64-apple-darwin/release/bundle/macos" \
"src-tauri/target/release/bundle/macos"; do
if [ -d "$path" ]; then
[ -z "$TAR_GZ" ] && TAR_GZ=$(find "$path" -maxdepth 1 -name "*.tar.gz" -type f | head -1 || true)
BUNDLE_DIR="$path"
[ -z "$TAR_GZ" ] && TAR_GZ=$(find "$path" -maxdepth 1 -name "*.tar.gz" -type f | head -1 || true)
[ -z "$APP_PATH" ] && APP_PATH=$(find "$path" -maxdepth 1 -name "*.app" -type d | head -1 || true)
[ -z "$DMG_PATH" ] && DMG_PATH=$(find "$path" -maxdepth 1 -name "*.dmg" -type f | head -1 || true)
fi
done
# Also search dmg directory (Tauri may put DMGs there)
for path in \
"src-tauri/target/universal-apple-darwin/release/bundle/dmg" \
"src-tauri/target/aarch64-apple-darwin/release/bundle/dmg" \
"src-tauri/target/x86_64-apple-darwin/release/bundle/dmg" \
"src-tauri/target/release/bundle/dmg"; do
if [ -d "$path" ] && [ -z "$DMG_PATH" ]; then
DMG_PATH=$(find "$path" -maxdepth 1 -name "*.dmg" -type f | head -1 || true)
fi
done
if [ -z "$TAR_GZ" ]; then
echo "No macOS .tar.gz updater artifact found" >&2
exit 1
fi
# 重命名 tar.gz 为统一格式
# Staple notarization ticket (hard-fail — must succeed before release)
if [ -n "$APP_PATH" ]; then
xcrun stapler staple "$APP_PATH"
echo "✅ .app stapled"
fi
if [ -n "$DMG_PATH" ]; then
xcrun stapler staple "$DMG_PATH"
echo "✅ .dmg stapled"
fi
# 1) Collect .tar.gz (updater artifact)
NEW_TAR_GZ="CC-Switch-${VERSION}-macOS.tar.gz"
cp "$TAR_GZ" "release-assets/$NEW_TAR_GZ"
[ -f "$TAR_GZ.sig" ] && cp "$TAR_GZ.sig" "release-assets/$NEW_TAR_GZ.sig" || echo ".sig for macOS not found yet"
echo "macOS updater artifact copied: $NEW_TAR_GZ"
# 2) Collect .app as zip (absolute paths, no cd)
if [ -n "$APP_PATH" ]; then
APP_DIR=$(dirname "$APP_PATH"); APP_NAME=$(basename "$APP_PATH")
NEW_ZIP="CC-Switch-${VERSION}-macOS.zip"
cd "$APP_DIR"
ditto -c -k --sequesterRsrc --keepParent "$APP_NAME" "$NEW_ZIP"
mv "$NEW_ZIP" "$GITHUB_WORKSPACE/release-assets/"
ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "release-assets/$NEW_ZIP"
echo "macOS zip ready: $NEW_ZIP"
else
echo "No .app found to zip (optional)" >&2
fi
# 3) Collect .dmg
if [ -n "$DMG_PATH" ]; then
NEW_DMG="CC-Switch-${VERSION}-macOS.dmg"
cp "$DMG_PATH" "release-assets/$NEW_DMG"
echo "macOS DMG ready: $NEW_DMG"
else
echo "No .dmg found (optional)" >&2
fi
- name: Verify macOS code signing and notarization
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
APP_PATH=""; DMG_PATH=""
for path in \
"src-tauri/target/universal-apple-darwin/release/bundle/macos" \
"src-tauri/target/aarch64-apple-darwin/release/bundle/macos" \
"src-tauri/target/x86_64-apple-darwin/release/bundle/macos" \
"src-tauri/target/release/bundle/macos"; do
if [ -d "$path" ]; then
[ -z "$APP_PATH" ] && APP_PATH=$(find "$path" -maxdepth 1 -name "*.app" -type d | head -1 || true)
fi
done
for path in \
"src-tauri/target/universal-apple-darwin/release/bundle/dmg" \
"src-tauri/target/aarch64-apple-darwin/release/bundle/dmg" \
"src-tauri/target/x86_64-apple-darwin/release/bundle/dmg" \
"src-tauri/target/release/bundle/dmg" \
"src-tauri/target/universal-apple-darwin/release/bundle/macos" \
"src-tauri/target/release/bundle/macos"; do
if [ -d "$path" ] && [ -z "$DMG_PATH" ]; then
DMG_PATH=$(find "$path" -maxdepth 1 -name "*.dmg" -type f | head -1 || true)
fi
done
# Verify .app
if [ -z "$APP_PATH" ]; then
echo "❌ No .app found for verification" >&2
exit 1
fi
echo "=== Verifying .app: $APP_PATH ==="
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
echo "✅ codesign verification passed"
spctl -a -t exec -vv "$APP_PATH"
echo "✅ spctl assessment passed"
xcrun stapler validate "$APP_PATH"
echo "✅ .app stapler validation passed"
# Verify .dmg
if [ -n "$DMG_PATH" ]; then
echo "=== Verifying .dmg: $DMG_PATH ==="
codesign --verify --verbose=2 "$DMG_PATH"
echo "✅ .dmg codesign verification passed"
spctl -a -t open --context context:primary-signature -vv "$DMG_PATH"
echo "✅ .dmg spctl assessment passed"
xcrun stapler validate "$DMG_PATH"
echo "✅ .dmg stapler validation passed"
else
echo "❌ No .dmg found for verification — release would ship without verified DMG" >&2
exit 1
fi
- name: Prepare Windows Assets
if: runner.os == 'Windows'
shell: pwsh
@@ -312,13 +448,15 @@ jobs:
### 下载
- **macOS**: `CC-Switch-${{ github.ref_name }}-macOS.zip`(解压即用)或 `CC-Switch-${{ github.ref_name }}-macOS.tar.gz`Homebrew
- **macOS**: `CC-Switch-${{ github.ref_name }}-macOS.dmg`(推荐)或 `CC-Switch-${{ github.ref_name }}-macOS.zip`(解压即用
- **Windows**: `CC-Switch-${{ github.ref_name }}-Windows.msi`(安装版)或 `CC-Switch-${{ github.ref_name }}-Windows-Portable.zip`(绿色版)
- **Linux (x86_64)**: `CC-Switch-${{ github.ref_name }}-Linux-x86_64.AppImage` / `.deb` / `.rpm`
- **Linux (ARM64)**: `CC-Switch-${{ github.ref_name }}-Linux-arm64.AppImage` / `.deb` / `.rpm`
> `.tar.gz` 为 Tauri updater 自动更新专用,无需手动下载。
---
提示:macOS 如遇"已损坏"提示,可在终端执行:`xattr -cr "/Applications/CC Switch.app"`
macOS 版本已通过 Apple 代码签名和公证,可直接安装使用。
files: release-assets/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -330,6 +468,17 @@ jobs:
echo "Listing bundles in src-tauri/target..."
find src-tauri/target -maxdepth 4 -type f -name "*.*" 2>/dev/null || true
- name: Clean up Apple signing keychain
if: runner.os == 'macOS' && always()
shell: bash
run: |
if [ -n "${ORIGINAL_DEFAULT_KEYCHAIN:-}" ]; then
security default-keychain -s "$ORIGINAL_DEFAULT_KEYCHAIN" || true
fi
if [ -f "$RUNNER_TEMP/build.keychain-db" ]; then
security delete-keychain "$RUNNER_TEMP/build.keychain-db" || true
fi
assemble-latest-json:
name: Assemble latest.json
runs-on: ubuntu-22.04
+37
View File
@@ -5,6 +5,43 @@ All notable changes to CC Switch will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
Post-v3.12.3 work introduces GitHub Copilot reverse proxy support as a major new feature, adds intelligent reasoning_effort mapping for OpenAI o-series models, optimizes Skills cache invalidation, and delivers critical fixes for WebDAV password safety, tool message parsing, and dark mode.
**Stats**: 13 commits | 74 files changed | +6,080 insertions | -1,197 deletions
### Added
- **GitHub Copilot Reverse Proxy**: Full GitHub Copilot integration as a Claude Code provider via OAuth Device Code flow; includes multi-account management, automatic token refresh, Anthropic ↔ OpenAI format conversion, real-time model list fetching, and usage statistics (#930)
- **Copilot Auth Center**: New Auth Center panel in Settings for managing GitHub accounts globally, with per-provider account binding via `meta.authBinding`
- **Reasoning Effort Mapping**: Two-tier `resolve_reasoning_effort()` for OpenAI o-series and GPT-5+ models — explicit `output_config.effort` takes priority, falling back to thinking `budget_tokens` thresholds (<4 000→low, 4 00016 000→medium, ≥16 000→high); covers both Chat Completions and Responses API paths with 17 unit tests
- **OpenCode SQLite Backend**: Added SQLite session storage support for OpenCode alongside existing JSON backend; dual-backend scan with SQLite priority on ID conflicts, atomic session deletion, and path validation
- **macOS Code Signing & Notarization**: CI now imports an Apple Developer ID certificate, signs the universal binary, submits for Apple notarization, and staples the ticket to both `.app` and `.dmg`; a hard-fail verification step (`codesign --verify` + `spctl -a` + `stapler validate`) gates the release for both artifacts
### Changed
- **Tool Search Mechanism Migration**: Replaced the binary-patching bypass (~590 lines in `toolsearch_patch.rs`) with Claude 2.1.76+ native `ENABLE_TOOL_SEARCH` env var toggle; moved the control from Settings page to the provider Common Config editor as a simple checkbox (#930)
- **Skills Cache Strategy**: Replaced `invalidateQueries` with direct `setQueryData` updates for skill install/uninstall/import operations; added `staleTime: Infinity` with `keepPreviousData` to eliminate loading flicker (#1573)
- **Claude 4.6 Context Window**: Updated Claude Opus 4.6 and Sonnet 4.6 context window from 200K to 1M across OpenClaw and OpenCode presets (GA release)
- **MiniMax Model Upgrade**: Updated MiniMax presets from M2.5 to M2.7 across Claude, OpenClaw, and OpenCode configurations with updated partner descriptions in all three locales
- **Xiaomi MiMo Model Upgrade**: Updated MiMo presets from mimo-v2-flash to mimo-v2-pro across all supported applications
- **AddProviderDialog Simplification**: Removed redundant OAuth tab, reducing dialog from 3 tabs to 2 (app-specific + universal)
### Fixed
- **WebDAV Password Silent Clear**: Fixed WebDAV password being silently wiped when ProviderList or UsageScriptModal saved settings by stripping `webdavSync` from frontend payloads and adding backend backfill logic in `merge_settings_for_save()` to preserve existing passwords
- **Tool Message Parsing**: Fixed tool_use/tool_result message classification across Claude (tool_result content blocks), Codex (function_call/function_call_output payloads), and Gemini (array content + toolCalls extraction) session providers (#1401)
- **Dark Mode Selector**: Changed Tailwind `darkMode` from `["selector", "class"]` to `["selector", ".dark"]` to ensure correct dark mode activation (#1596)
- **Copilot Request Fingerprint**: Unified Copilot request fingerprint headers across all API call sites to prevent User-Agent leakage and stream check mismatches
### Docs
- **Pricing Model ID Normalization**: Added documentation section explaining model ID normalization rules (prefix stripping, suffix trimming, `@``-` replacement) in EN/ZH/JA user manuals (#1591)
- **macOS Signed & Notarized**: Removed all `xattr` workaround instructions and "unidentified developer" warnings from README, README_ZH, installation guides (EN/ZH/JA), and FAQ pages (EN/ZH/JA); replaced with "signed and notarized by Apple" messaging
---
## [3.12.3] - 2026-03-15
Post-v3.12.2 work adds a Tool Search domain restriction bypass, skill backup/restore lifecycle, proxy compatibility for OpenAI o-series models and gzip compression, and robustness fixes for Skills import, provider forms, and terminal session restore.
+4 -4
View File
@@ -184,9 +184,9 @@ CC Switch provides a "Shared Config Snippet" feature to pass common data (beyond
</details>
<details>
<summary><strong>macOS shows "unidentified developer" warning — how do I fix it?</strong></summary>
<summary><strong>macOS installation</strong></summary>
The author doesn't have an Apple Developer account yet (registration in progress). Close the warning, then go to **System Settings → Privacy & Security → Open Anyway**. After that, the app will open normally.
CC Switch for macOS is code-signed and notarized by Apple. You can download and install it directly — no extra steps needed. We recommend using the `.dmg` installer.
</details>
@@ -268,9 +268,9 @@ brew upgrade --cask cc-switch
**Method 2: Manual Download**
Download `CC-Switch-v{version}-macOS.zip` from the [Releases](../../releases) page and extract to use.
Download `CC-Switch-v{version}-macOS.dmg` (recommended) or `.zip` from the [Releases](../../releases) page.
> **Note**: Since the author doesn't have an Apple Developer account, you may see an "unidentified developer" warning on first launch. Please close it first, then go to "System Settings" → "Privacy & Security" → click "Open Anyway", and you'll be able to open it normally afterwards.
> **Note**: CC Switch for macOS is code-signed and notarized by Apple. You can install and open it directly.
### Arch Linux Users
+4 -4
View File
@@ -185,9 +185,9 @@ CC Switch 使用“通用配置片段”功能,在不同的供应商之间传
</details>
<details>
<summary><strong>macOS 提示"未知开发者"警告 — 如何解决?</strong></summary>
<summary><strong>macOS 安装</strong></summary>
这是由于作者没有苹果开发者账号(正在注册中)。关闭警告后,前往**系统设置 → 隐私与安全性 → 仍要打开**。之后应用即可正常打开
CC Switch macOS 版本已通过 Apple 代码签名和公证,可直接下载安装,无需额外操作。推荐使用 `.dmg` 安装包
</details>
@@ -271,9 +271,9 @@ brew upgrade --cask cc-switch
**方式二:手动下载**
从 [Releases](../../releases) 页面下载 `CC-Switch-v{版本号}-macOS.zip` 解压使用
从 [Releases](../../releases) 页面下载 `CC-Switch-v{版本号}-macOS.dmg`(推荐)或 `.zip`
> **注意**由于作者没有苹果开发者账号,首次打开可能出现"未知开发者"警告,请先关闭,然后前往"系统设置" → "隐私与安全性" → 点击"仍要打开",之后便可以正常打开。
> **注意**CC Switch macOS 版本已通过 Apple 代码签名和公证,可直接安装打开。
### Arch Linux 用户
@@ -131,21 +131,9 @@ brew upgrade --cask cc-switch
2. Extract to get `CC Switch.app`
3. Drag it to the Applications folder
### First Launch Warning
### Signed and Notarized
Since the developer does not have an Apple Developer account, a "developer cannot be verified" warning may appear on first launch:
**Recommended solution**:
Open Terminal and run the following command:
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
**Alternative solution (via System Settings)**:
1. Close the warning dialog
2. Open "System Settings" > "Privacy & Security"
3. Find the CC Switch prompt and click "Open Anyway"
4. Reopen the app to use it normally
CC Switch for macOS is signed and notarized by Apple. You can install and open it directly — no extra steps needed.
## Linux
+2 -16
View File
@@ -2,23 +2,9 @@
## Installation Issues
### macOS Shows "Unidentified Developer"
### macOS Installation
**Problem**: First launch shows "Cannot open because it is from an unidentified developer"
**Solution 1**: Via System Settings
1. Close the warning dialog
2. Open "System Settings" > "Privacy & Security"
3. Find the CC Switch prompt
4. Click "Open Anyway"
5. Reopen the app
**Solution 2**: Via Terminal command (recommended)
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
The app can be opened normally after running this command.
CC Switch for macOS is code-signed and notarized by Apple. You can download and install it directly without any additional steps. If you encounter issues, try downloading the latest version from the [Releases page](https://github.com/farion1231/cc-switch/releases).
### Windows: App Doesn't Launch After Installation
@@ -131,21 +131,9 @@ brew upgrade --cask cc-switch
2. 展開して `CC Switch.app` を取得
3. 「アプリケーション」フォルダにドラッグ
### 初回起動時の警告
### 署名・公証済み
開発者が Apple 開発者アカウントを持っていないため、初回起動時に「不明な開発者」の警告が表示される場合があります
**推奨される解決方法**
ターミナルで以下のコマンドを実行してください:
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
**別の解決方法(システム設定から)**:
1. 警告ダイアログを閉じる
2. 「システム設定」→「プライバシーとセキュリティ」を開く
3. CC Switch に関する表示を見つけ、「このまま開く」をクリック
4. 再度アプリを開くと正常に使用可能
CC Switch の macOS 版は Apple のコード署名と公証を受けています。追加の操作なしで直接インストールして開くことができます
## Linux
+2 -16
View File
@@ -2,23 +2,9 @@
## インストールに関する問題
### macOS で「不明な開発者」と表示される
### macOS のインストール
**問題**:初回起動時に「開けません。身元不明の開発者のものです」と表示される
**解決方法 1**:システム設定から
1. 警告ダイアログを閉じる
2. 「システム設定」→「プライバシーとセキュリティ」を開く
3. CC Switch に関する表示を見つける
4. 「このまま開く」をクリック
5. 再度アプリを開く
**解決方法 2**:ターミナルコマンドから(推奨)
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
実行後、正常にアプリを開けるようになります。
CC Switch の macOS 版は Apple のコード署名と公証を受けています。追加の操作なしで直接ダウンロードしてインストールできます。問題が発生した場合は、[Releases ページ](https://github.com/farion1231/cc-switch/releases) から最新版をダウンロードしてください。
### Windows でインストール後に起動できない
@@ -145,21 +145,9 @@ brew upgrade --cask cc-switch
2. 解压得到 `CC Switch.app`
3. 拖动到「应用程序」文件夹
### 首次打开提示
### 已签名并公证
由于开发者没有 Apple 开发者账号,首次打开可能出现「未知开发者」警告:
**推荐解决方法**
打开终端执行以下命令:
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
**备选解决方法(通过系统设置)**:
1. 关闭警告弹窗
2. 打开「系统设置」→「隐私与安全性」
3. 找到 CC Switch 相关提示,点击「仍要打开」
4. 再次打开应用即可正常使用
CC Switch macOS 版本已通过 Apple 代码签名和公证,可直接安装打开,无需额外操作。
## Linux
+2 -16
View File
@@ -2,23 +2,9 @@
## 安装问题
### macOS 提示「未知开发者」
### macOS 安装
**问题**:首次打开时提示「无法打开,因为它来自身份不明的开发者」
**解决方法一**:通过系统设置
1. 关闭警告弹窗
2. 打开「系统设置」→「隐私与安全性」
3. 找到 CC Switch 相关提示
4. 点击「仍要打开」
5. 再次打开应用
**解决方法二**:通过终端命令(推荐)
```bash
sudo xattr -dr com.apple.quarantine /Applications/CC\ Switch.app/
```
执行后即可正常打开应用。
CC Switch macOS 版本已通过 Apple 代码签名和公证,可直接下载安装,无需额外操作。如遇问题,请尝试从 [Releases 页面](https://github.com/farion1231/cc-switch/releases) 下载最新版本。
### Windows 安装后无法启动