Skip to content
Merged
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
122 changes: 122 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: Release

on:
push:
tags:
- "v*"

permissions:
contents: write

jobs:
release:
runs-on: macos-14
timeout-minutes: 60

steps:
- uses: actions/checkout@v4

- name: Resolve release metadata
id: meta
env:
MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
VERSION="${GITHUB_REF_NAME#v}"
if [ -z "$VERSION" ]; then
echo "Unable to derive version from tag: $GITHUB_REF_NAME" >&2
exit 1
fi

HAS_SIGNING="false"
HAS_NOTARIZATION="false"

if [ -n "$MACOS_CERTIFICATE_P12" ] && [ -n "$MACOS_CERTIFICATE_PASSWORD" ] && [ -n "$MACOS_SIGNING_IDENTITY" ]; then
HAS_SIGNING="true"
fi

if [ -n "$APPLE_ID" ] && [ -n "$APPLE_APP_SPECIFIC_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then
HAS_NOTARIZATION="true"
fi

echo "app_version=$VERSION" >> "$GITHUB_OUTPUT"
echo "archive_name=VoiceMemo-$VERSION-macos.zip" >> "$GITHUB_OUTPUT"
echo "has_signing=$HAS_SIGNING" >> "$GITHUB_OUTPUT"
echo "has_notarization=$HAS_NOTARIZATION" >> "$GITHUB_OUTPUT"

- name: Prepare signing keychain
if: steps.meta.outputs.has_signing == 'true'
env:
MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
run: |
CERT_PATH="$RUNNER_TEMP/signing_certificate.p12"
KEYCHAIN_PATH="$RUNNER_TEMP/voicememo-signing.keychain-db"
KEYCHAIN_PASSWORD="${MACOS_KEYCHAIN_PASSWORD:-$MACOS_CERTIFICATE_PASSWORD}"

printf '%s' "$MACOS_CERTIFICATE_P12" | base64 -D -o "$CERT_PATH"

security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/xcrun
security list-keychains -d user -s "$KEYCHAIN_PATH"
security default-keychain -d user -s "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

- name: Run tests
run: swift test -v

- name: Package release archive
env:
APP_VERSION: ${{ steps.meta.outputs.app_version }}
BUILD_NUMBER: ${{ github.run_number }}
ARCHIVE_NAME: ${{ steps.meta.outputs.archive_name }}
HAS_SIGNING: ${{ steps.meta.outputs.has_signing }}
HAS_NOTARIZATION: ${{ steps.meta.outputs.has_notarization }}
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
SIGN_IDENTITY="-"
NOTARIZE="false"

if [ "$HAS_SIGNING" = "true" ]; then
SIGN_IDENTITY="$MACOS_SIGNING_IDENTITY"
fi

if [ "$HAS_SIGNING" = "true" ] && [ "$HAS_NOTARIZATION" = "true" ]; then
NOTARIZE="true"
fi

APP_VERSION="$APP_VERSION" \
BUILD_NUMBER="$BUILD_NUMBER" \
ARCHIVE_NAME="$ARCHIVE_NAME" \
SIGN_IDENTITY="$SIGN_IDENTITY" \
NOTARIZE="$NOTARIZE" \
APPLE_ID="$APPLE_ID" \
APPLE_APP_SPECIFIC_PASSWORD="$APPLE_APP_SPECIFIC_PASSWORD" \
APPLE_TEAM_ID="$APPLE_TEAM_ID" \
bash ./package_app.sh

- name: Upload workflow artifact
uses: actions/upload-artifact@v4
with:
name: VoiceMemo-${{ steps.meta.outputs.app_version }}
path: |
dist/${{ steps.meta.outputs.archive_name }}
dist/${{ steps.meta.outputs.archive_name }}.sha256

- name: Publish GitHub release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
files: |
dist/${{ steps.meta.outputs.archive_name }}
dist/${{ steps.meta.outputs.archive_name }}.sha256
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Packages.resolved
.DS_Store
*.app/
*.dSYM/
dist/
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
Expand Down
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ flowchart LR

- `Sources/`: Core Swift implementation.
- `Package.swift`: Swift Package Manager configuration.
- `package_app.sh`: Automated build and ad-hoc signing script.
- `package_app.sh`: Build, sign, notarize (optional), and archive the macOS app bundle.
- `Info.plist`: Application configuration and permission strings.

## Getting Started
Expand All @@ -100,9 +100,15 @@ Due to macOS security requirements (ScreenCaptureKit needs specific entitlements
```bash
chmod +x package_app.sh
./package_app.sh
open VoiceMemo.app
open dist/VoiceMemo.app
```

Artifacts are written to `dist/` by default:

- `dist/VoiceMemo.app`
- `dist/VoiceMemo-<version>-macos.zip`
- `dist/VoiceMemo-<version>-macos.zip.sha256`

### 2. Permissions

When you first start recording, macOS will request the following permissions:
Expand Down Expand Up @@ -171,6 +177,26 @@ xed VoiceMemo.xcodeproj

Ensure you configure **Signing & Capabilities** with your Development Team to run the app with full permissions.

## Release

Tag-driven releases are handled by [`.github/workflows/release.yml`](.github/workflows/release.yml). Pushing a tag like `v1.2.3` will:

- run `swift test`
- build a versioned macOS app archive
- create a GitHub Release and upload the zip plus SHA-256 checksum

If the following GitHub Actions secrets are configured, the workflow signs with Developer ID and notarizes the archive. Otherwise it falls back to ad-hoc signing for internal/testing distribution:

- `MACOS_CERTIFICATE_P12`
- `MACOS_CERTIFICATE_PASSWORD`
- `MACOS_SIGNING_IDENTITY`
- `MACOS_KEYCHAIN_PASSWORD` (optional, defaults to the certificate password)
- `APPLE_ID`
- `APPLE_APP_SPECIFIC_PASSWORD`
- `APPLE_TEAM_ID`

For signing and notarization details, see [Permissions and Signing](doc/05-permissions-and-signing.md).

## Roadmap

### Completed
Expand Down
30 changes: 28 additions & 2 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ flowchart LR

- `Sources/`:核心 Swift 代码实现。
- `Package.swift`:Swift Package Manager 项目配置。
- `package_app.sh`:自动化编译及 Ad-hoc 签名脚本
- `package_app.sh`:用于构建、签名、可选公证并归档 macOS 应用包
- `Info.plist`:应用配置及权限描述。

## 快速开始
Expand All @@ -100,9 +100,15 @@ flowchart LR
```bash
chmod +x package_app.sh
./package_app.sh
open VoiceMemo.app
open dist/VoiceMemo.app
```

默认产物会输出到 `dist/`:

- `dist/VoiceMemo.app`
- `dist/VoiceMemo-<version>-macos.zip`
- `dist/VoiceMemo-<version>-macos.zip.sha256`

### 2. 权限说明

首次开始录制时,macOS 会请求以下权限:
Expand Down Expand Up @@ -170,6 +176,26 @@ xed VoiceMemo.xcodeproj

请务必在 **Signing & Capabilities** 中配置您的开发团队(Team),以便应用能以完整权限运行。

## 发布

基于 tag 的自动发布由 [`.github/workflows/release.yml`](.github/workflows/release.yml) 负责。推送 `v1.2.3` 这类 tag 时,会自动:

- 执行 `swift test`
- 构建带版本号的 macOS 应用归档
- 创建 GitHub Release 并上传 zip 与 SHA-256 校验文件

如果配置了以下 GitHub Actions secrets,workflow 会使用 Developer ID 签名并执行 notarization;如果没有配置,则回退到 ad-hoc 签名,适合内部或测试分发:

- `MACOS_CERTIFICATE_P12`
- `MACOS_CERTIFICATE_PASSWORD`
- `MACOS_SIGNING_IDENTITY`
- `MACOS_KEYCHAIN_PASSWORD`(可选,默认使用证书密码)
- `APPLE_ID`
- `APPLE_APP_SPECIFIC_PASSWORD`
- `APPLE_TEAM_ID`

签名和公证的细节请参阅 [权限与签名说明](doc/05-permissions-and-signing.zh-CN.md)。

## 开发路线图

### 已完成
Expand Down
2 changes: 2 additions & 0 deletions Sources/VoiceMemo/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>13.0</string>
<key>NSHighResolutionCapable</key>
Expand Down
1 change: 1 addition & 0 deletions Tests/VoiceMemoTests/OSSUploadTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ final class OSSUploadTests: XCTestCase {
}

let settings = SettingsStore()
settings.useKeychain = false
settings.ossRegion = ossRegion
settings.ossEndpoint = ossEndpoint
settings.ossBucket = ossBucket
Expand Down
2 changes: 2 additions & 0 deletions Tests/VoiceMemoTests/TingwuCreateTaskTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ final class TingwuCreateTaskTests: XCTestCase {
}

let settings = SettingsStore()
settings.useKeychain = false
settings.tingwuAppKey = appKey
settings.saveTingwuAccessKeyId(accessKeyId)
settings.saveTingwuAccessKeySecret(accessKeySecret)
Expand All @@ -35,6 +36,7 @@ final class TingwuCreateTaskTests: XCTestCase {
// specifically checking that Summarization and MeetingAssistance parameters are present.

let settings = SettingsStore()
settings.useKeychain = false
settings.tingwuAppKey = "TEST_APP_KEY"
settings.saveTingwuAccessKeyId("TEST_AK_ID")
settings.saveTingwuAccessKeySecret("TEST_AK_SECRET")
Expand Down
2 changes: 2 additions & 0 deletions Tests/VoiceMemoTests/VolcengineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ final class VolcengineTests: XCTestCase {

func testCreateTaskRequestConstruction() throws {
let settings = SettingsStore()
settings.useKeychain = false
settings.volcAppId = "TEST_APP_ID"
settings.volcResourceId = "volc.auc.common"
settings.enableRoleSplit = false // Ensure it works even if setting is false
Expand Down Expand Up @@ -62,6 +63,7 @@ final class VolcengineTests: XCTestCase {

func testQueryRequestConstruction() throws {
let settings = SettingsStore()
settings.useKeychain = false
settings.volcAppId = "TEST_APP_ID"
settings.volcResourceId = "volc.auc.common"
settings.saveVolcAccessToken("TEST_TOKEN")
Expand Down
39 changes: 37 additions & 2 deletions doc/05-permissions-and-signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,45 @@ For stable behavior during development:

Packaging script note:

- `package_app.sh` uses ad-hoc signing (`codesign --sign -`).
- `package_app.sh` always builds a versioned `.app` plus a `.zip` archive in `dist/`.
- The script uses ad-hoc signing by default (`codesign --sign -`) and can switch to Developer ID signing when `SIGN_IDENTITY` is provided.
- If `VoiceMemo.entitlements` exists, the script signs with that entitlements file to match the Xcode target more closely.
- Ad-hoc signatures are not stable identities for repeated permission grants; repeated prompts are expected when running differently signed builds.

## GitHub Tag Releases

Workflow file: `.github/workflows/release.yml`

When you push a tag such as `v1.2.3`, the release workflow:

- runs `swift test`
- builds `dist/VoiceMemo.app`
- injects `CFBundleShortVersionString=1.2.3`
- injects `CFBundleVersion=<GitHub run number>`
- creates `dist/VoiceMemo-1.2.3-macos.zip`
- uploads the zip and `.sha256` file to GitHub Releases

### Release Secrets

For ad-hoc archive publishing, no extra secrets are required.

For Developer ID signing:

- `MACOS_CERTIFICATE_P12`
- `MACOS_CERTIFICATE_PASSWORD`
- `MACOS_SIGNING_IDENTITY`
- `MACOS_KEYCHAIN_PASSWORD` (optional)

For notarization:

- `APPLE_ID`
- `APPLE_APP_SPECIFIC_PASSWORD`
- `APPLE_TEAM_ID`

If the signing or notarization secrets are missing, the workflow still publishes an ad-hoc signed archive. That is useful for internal distribution, but external users may still hit Gatekeeper/quarantine warnings.

## Practical Recommendation

- Debug via Xcode with automatic signing and a fixed team.
- Use the packaging script only for quick local distribution/testing; expect permission prompts unless you switch to a stable signing identity.
- Use GitHub tag releases with Developer ID signing + notarization for public binary distribution.
- Use ad-hoc packaging only for quick local distribution/testing; expect permission prompts unless you switch to a stable signing identity.
39 changes: 37 additions & 2 deletions doc/05-permissions-and-signing.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,45 @@ macOS 隐私权限授权与“应用身份 + 签名”绑定。

打包脚本说明:

- `package_app.sh` 使用 ad-hoc 签名(`codesign --sign -`)
- `package_app.sh` 会在 `dist/` 下输出带版本号的 `.app` 和 `.zip` 包
- 脚本默认使用 ad-hoc 签名(`codesign --sign -`),如果提供 `SIGN_IDENTITY` 则会切换为 Developer ID 签名
- 如果仓库中存在 `VoiceMemo.entitlements`,脚本会在签名时显式带上它,尽量与 Xcode target 的行为保持一致
- ad-hoc 签名不具备稳定身份,换一种方式运行/签名后重复弹窗是预期现象

## GitHub Tag 发布

工作流文件:`.github/workflows/release.yml`

当你推送 `v1.2.3` 这样的 tag 时,发布 workflow 会自动:

- 执行 `swift test`
- 构建 `dist/VoiceMemo.app`
- 注入 `CFBundleShortVersionString=1.2.3`
- 注入 `CFBundleVersion=<GitHub run number>`
- 生成 `dist/VoiceMemo-1.2.3-macos.zip`
- 将 zip 和 `.sha256` 文件上传到 GitHub Release

### 发布所需 Secrets

如果只是发布 ad-hoc 归档,不需要额外 secrets。

如果要做 Developer ID 签名,需要:

- `MACOS_CERTIFICATE_P12`
- `MACOS_CERTIFICATE_PASSWORD`
- `MACOS_SIGNING_IDENTITY`
- `MACOS_KEYCHAIN_PASSWORD`(可选)

如果要做 notarization,还需要:

- `APPLE_ID`
- `APPLE_APP_SPECIFIC_PASSWORD`
- `APPLE_TEAM_ID`

如果签名或公证所需 secrets 缺失,workflow 仍然会发布 ad-hoc 签名的归档。这适合内部测试,但对外分发时用户仍可能遇到 Gatekeeper 或 quarantine 提示。

## 实践建议

- 调试:优先使用 Xcode + 自动签名 + 固定 Team。
- 打包脚本:适合快速本地运行/分发测试;若要减少弹窗,需要改为稳定签名策略。
- 对外二进制发布:优先使用 GitHub tag release + Developer ID 签名 + notarization。
- ad-hoc 打包:适合快速本地运行/分发测试;若要减少弹窗,需要改为稳定签名策略。
Loading
Loading