Skip to content

Commit 5f70fe0

Browse files
committed
Add signed macOS release packaging
1 parent 76ba1f5 commit 5f70fe0

10 files changed

Lines changed: 527 additions & 13 deletions

File tree

.github/workflows/release.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,31 @@ jobs:
4848
shell: bash
4949
run: swift test
5050

51+
- name: Import Apple signing assets
52+
id: apple-signing
53+
env:
54+
APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64 }}
55+
APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD }}
56+
APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64 }}
57+
APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE_PASSWORD }}
58+
APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }}
59+
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
60+
APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
61+
shell: bash
62+
run: scripts/import-apple-signing-assets.sh
63+
5164
- name: Build release artifacts
65+
env:
66+
MACOS_APP_BUNDLE_ID: com.jianliang00.computer-use-cli
67+
MACOS_CODE_SIGN_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_IDENTITY }}
68+
MACOS_INSTALLER_SIGN_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_INSTALLER_IDENTITY }}
69+
MACOS_CODE_SIGN_KEYCHAIN: ${{ steps.apple-signing.outputs.keychain-path }}
70+
MACOS_SIGNING_ENABLED: "1"
71+
MACOS_NOTARIZATION_ENABLED: "1"
72+
CREATE_INSTALLER_PKGS: "1"
73+
NOTARYTOOL_KEY_PATH: ${{ steps.apple-signing.outputs.notary-key-path }}
74+
NOTARYTOOL_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
75+
NOTARYTOOL_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
5276
shell: bash
5377
run: scripts/package-release-artifacts.sh "${{ steps.release.outputs.version }}" "$RUNNER_TEMP/release-artifacts"
5478

@@ -66,3 +90,8 @@ jobs:
6690
else
6791
gh release create "$tag" "$artifacts_dir"/* --title "$tag" --generate-notes --repo "$GH_REPO"
6892
fi
93+
94+
- name: Clean up Apple signing keychain
95+
if: ${{ always() && steps.apple-signing.outputs.keychain-path != '' }}
96+
shell: bash
97+
run: security delete-keychain "${{ steps.apple-signing.outputs.keychain-path }}" || true

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ forwarding computer-use commands to a session agent running inside the guest.
4343
- `computer-use action action --machine <name> --snapshot-id <id> --element-id <id> --name <AXAction>`
4444
- `computer-use-agent` starts a guest-side HTTP server on port `7777`.
4545
- `scripts/package-computer-use-agent-app.sh` builds `ComputerUseAgent.app`
46-
with bundle id `io.github.jianliang00.computer-use.agent`.
46+
with bundle id `com.jianliang00.computer-use-cli`.
4747
- `scripts/prepare-computer-use-image-context.sh` prepares a macOS image build
4848
context with the app bundle, bootstrap agent, launchd plists, installer
4949
scripts, and Dockerfile.
@@ -146,3 +146,18 @@ Useful runtime commands:
146146
Use the `runtime container --` wrapper for raw SDK operations such as image
147147
build, package, load, and list. It uses the same project-owned SDK root as
148148
`machine` commands and does not require `container` to be installed on `PATH`.
149+
150+
## Release Signing
151+
152+
The GitHub Actions release workflow signs the macOS executables and app bundle,
153+
notarizes them with Apple's notary service, staples the notary ticket where
154+
macOS supports it, and publishes directly installable `.pkg` artifacts:
155+
156+
- `computer-use-<version>-macos-arm64.pkg` installs the host CLI into
157+
`/usr/local/bin/computer-use`.
158+
- `computer-use-guest-kit-<version>-macos-arm64.pkg` installs
159+
`ComputerUseAgent.app`, `bootstrap-agent`, and the launchd plists into a
160+
macOS guest.
161+
162+
See `docs/release-signing.md` for the Apple Developer certificates, App Store
163+
Connect API key, and GitHub Actions secrets required by the workflow.

Sources/ComputerUseAgentApp/SessionAgentConfiguration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public struct SessionAgentConfiguration: Codable, Equatable, Sendable {
2323
}
2424

2525
public static let guestDefault = SessionAgentConfiguration(
26-
bundleIdentifier: "io.github.jianliang00.computer-use.agent",
26+
bundleIdentifier: "com.jianliang00.computer-use-cli",
2727
bundlePath: "/Applications/ComputerUseAgent.app",
2828
launchAgentLabel: "io.github.jianliang00.computer-use.agent",
2929
host: "127.0.0.1",

Tests/ComputerUseAgentCoreTests/ComputerUseAgentCoreTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Testing
66
func sessionAgentDefaultsMatchTechnicalPlan() {
77
let configuration = SessionAgentConfiguration.guestDefault
88

9-
#expect(configuration.bundleIdentifier == "io.github.jianliang00.computer-use.agent")
9+
#expect(configuration.bundleIdentifier == "com.jianliang00.computer-use-cli")
1010
#expect(configuration.bundlePath == "/Applications/ComputerUseAgent.app")
1111
#expect(configuration.launchAgentLabel == "io.github.jianliang00.computer-use.agent")
1212
#expect(configuration.host == "127.0.0.1")

docs/computer-use-cli-technical-plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ host 侧 machine metadata 固定存放在:
239239
固定身份如下:
240240

241241
- session agent bundle id
242-
- `io.github.jianliang00.computer-use.agent`
242+
- `com.jianliang00.computer-use-cli`
243243
- bootstrap label
244244
- `io.github.jianliang00.computer-use.bootstrap`
245245
- session label

docs/computer-use-cli-todo.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
- `set-value``action` 已通过 snapshot cache 接入 AX 元素执行
4040
- `bootstrap-agent` 可刷新并持久化 bootstrap status JSON
4141
- 已提供 bootstrap LaunchDaemon 与 session LaunchAgent plist 模板
42-
- 已提供 `ComputerUseAgent.app` 打包脚本,并验证 bundle id 为 `io.github.jianliang00.computer-use.agent`
42+
- 已提供 `ComputerUseAgent.app` 打包脚本,并验证 bundle id 为 `com.jianliang00.computer-use-cli`
4343
- `scripts/smoke-local-agent-e2e.sh` 已通过本机端到端 smoke:
4444
- TextEdit:element click、type、Return、set-value、AXRaise action、AX tree 读回 marker
4545
- Finder:坐标 click、scroll、drag
@@ -185,7 +185,7 @@
185185
- 基础日志能力
186186

187187
固定要求:
188-
- bundle id 为 `io.github.jianliang00.computer-use.agent`
188+
- bundle id 为 `com.jianliang00.computer-use-cli`
189189
- guest 内监听 `127.0.0.1:7777`
190190

191191
完成标准:

docs/release-signing.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Release Signing
2+
3+
Release builds use Apple Developer ID signing and notarization for macOS
4+
distribution outside the Mac App Store.
5+
6+
## Published Artifacts
7+
8+
The release workflow keeps the existing tarballs and also publishes directly
9+
installable packages:
10+
11+
- `computer-use-<version>-macos-arm64.pkg`
12+
- Installs `/usr/local/bin/computer-use`.
13+
- `computer-use-guest-kit-<version>-macos-arm64.pkg`
14+
- Installs `/Applications/ComputerUseAgent.app`.
15+
- Installs `/usr/local/libexec/computer-use/bootstrap-agent`.
16+
- Installs the LaunchDaemon and LaunchAgent plists.
17+
- Runs a `postinstall` script that fixes ownership, validates plists, and
18+
loads launchd jobs when possible.
19+
20+
The app bundle id is:
21+
22+
- `com.jianliang00.computer-use-cli`
23+
24+
The launchd labels remain:
25+
26+
- `io.github.jianliang00.computer-use.bootstrap`
27+
- `io.github.jianliang00.computer-use.agent`
28+
29+
## Apple Developer Setup
30+
31+
Create these assets in the Apple developer account used for this project:
32+
33+
1. Register the bundle id `com.jianliang00.computer-use-cli`.
34+
2. Create a `Developer ID Application` certificate and export it as a `.p12`.
35+
3. Create a `Developer ID Installer` certificate and export it as a `.p12`.
36+
4. Create an App Store Connect API key for notarization and download the `.p8`
37+
private key.
38+
39+
The `.p12` exports must include the private key. Keep the certificate passwords
40+
and `.p8` private key out of the repository.
41+
42+
## GitHub Actions Secrets
43+
44+
Configure these repository secrets:
45+
46+
- `APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64`
47+
- `APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD`
48+
- `APPLE_DEVELOPER_ID_APPLICATION_IDENTITY`
49+
- `APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64`
50+
- `APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE_PASSWORD`
51+
- `APPLE_DEVELOPER_ID_INSTALLER_IDENTITY`
52+
- `APP_STORE_CONNECT_API_KEY_BASE64`
53+
- `APP_STORE_CONNECT_API_KEY_ID`
54+
- `APP_STORE_CONNECT_API_ISSUER_ID`
55+
56+
The identity secrets should be the certificate common names, for example:
57+
58+
- `Developer ID Application: Example Name (TEAMID)`
59+
- `Developer ID Installer: Example Name (TEAMID)`
60+
61+
Base64 encode local files before pasting them into GitHub secrets:
62+
63+
```bash
64+
base64 < DeveloperIDApplication.p12 | tr -d '\n' | pbcopy
65+
base64 < DeveloperIDInstaller.p12 | tr -d '\n' | pbcopy
66+
base64 < AuthKey_XXXXXXXXXX.p8 | tr -d '\n' | pbcopy
67+
```
68+
69+
## Workflow Behavior
70+
71+
`.github/workflows/release.yml` imports the certificates into a temporary
72+
keychain, builds release artifacts with signing enabled, notarizes the app and
73+
packages, staples the app and packages, publishes the GitHub release, then
74+
deletes the temporary keychain.
75+
76+
The packaging script signs these Mach-O artifacts before packaging:
77+
78+
- `computer-use`
79+
- `bootstrap-agent`
80+
- `ComputerUseAgent.app`
81+
82+
The `.pkg` artifacts are signed with the Developer ID Installer certificate,
83+
submitted to Apple's notary service, stapled, and validated with `spctl`.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
RUNNER_TEMP="${RUNNER_TEMP:-/tmp}"
5+
KEYCHAIN_PATH="${SIGNING_KEYCHAIN_PATH:-"$RUNNER_TEMP/computer-use-signing.keychain-db"}"
6+
KEYCHAIN_PASSWORD="${SIGNING_KEYCHAIN_PASSWORD:-"$(uuidgen)"}"
7+
APP_CERT_PATH="$RUNNER_TEMP/developer-id-application.p12"
8+
INSTALLER_CERT_PATH="$RUNNER_TEMP/developer-id-installer.p12"
9+
NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey_${APP_STORE_CONNECT_API_KEY_ID:-missing}.p8"
10+
11+
require_value() {
12+
local name="$1"
13+
local value="$2"
14+
if [[ -z "$value" ]]; then
15+
echo "missing required value: $name" >&2
16+
exit 64
17+
fi
18+
}
19+
20+
decode_base64_secret() {
21+
local name="$1"
22+
local value="$2"
23+
local output_path="$3"
24+
25+
require_value "$name" "$value"
26+
printf '%s' "$value" | /usr/bin/base64 -D > "$output_path"
27+
chmod 0600 "$output_path"
28+
}
29+
30+
require_value APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD \
31+
"${APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD:-}"
32+
require_value APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE_PASSWORD \
33+
"${APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE_PASSWORD:-}"
34+
require_value APP_STORE_CONNECT_API_KEY_ID "${APP_STORE_CONNECT_API_KEY_ID:-}"
35+
require_value APP_STORE_CONNECT_API_ISSUER_ID "${APP_STORE_CONNECT_API_ISSUER_ID:-}"
36+
37+
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
38+
echo "::add-mask::$KEYCHAIN_PASSWORD"
39+
fi
40+
41+
decode_base64_secret \
42+
APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64 \
43+
"${APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64:-}" \
44+
"$APP_CERT_PATH"
45+
decode_base64_secret \
46+
APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64 \
47+
"${APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64:-}" \
48+
"$INSTALLER_CERT_PATH"
49+
decode_base64_secret \
50+
APP_STORE_CONNECT_API_KEY_BASE64 \
51+
"${APP_STORE_CONNECT_API_KEY_BASE64:-}" \
52+
"$NOTARY_KEY_PATH"
53+
54+
security delete-keychain "$KEYCHAIN_PATH" >/dev/null 2>&1 || true
55+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
56+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
57+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
58+
59+
existing_keychains=()
60+
while IFS= read -r keychain; do
61+
keychain="${keychain#"${keychain%%[![:space:]]*}"}"
62+
keychain="${keychain#\"}"
63+
keychain="${keychain%\"}"
64+
if [[ -n "$keychain" && "$keychain" != "$KEYCHAIN_PATH" ]]; then
65+
existing_keychains+=("$keychain")
66+
fi
67+
done < <(security list-keychains -d user)
68+
security list-keychains -d user -s "$KEYCHAIN_PATH" "${existing_keychains[@]}"
69+
70+
security import "$APP_CERT_PATH" \
71+
-k "$KEYCHAIN_PATH" \
72+
-P "$APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD" \
73+
-A \
74+
-t cert \
75+
-f pkcs12 \
76+
-T /usr/bin/codesign \
77+
-T /usr/bin/security
78+
79+
security import "$INSTALLER_CERT_PATH" \
80+
-k "$KEYCHAIN_PATH" \
81+
-P "$APPLE_DEVELOPER_ID_INSTALLER_CERTIFICATE_PASSWORD" \
82+
-A \
83+
-t cert \
84+
-f pkcs12 \
85+
-T /usr/bin/pkgbuild \
86+
-T /usr/bin/productsign \
87+
-T /usr/bin/security
88+
89+
security set-key-partition-list \
90+
-S apple-tool:,apple: \
91+
-s \
92+
-k "$KEYCHAIN_PASSWORD" \
93+
"$KEYCHAIN_PATH" >/dev/null
94+
95+
if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
96+
{
97+
echo "keychain-path=$KEYCHAIN_PATH"
98+
echo "notary-key-path=$NOTARY_KEY_PATH"
99+
} >> "$GITHUB_OUTPUT"
100+
fi
101+
102+
echo "Imported Apple signing assets into $KEYCHAIN_PATH"

scripts/package-computer-use-agent-app.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
55
CONFIGURATION="${CONFIGURATION:-release}"
66
APP_DIR="${1:-"$ROOT_DIR/.build/ComputerUseAgent.app"}"
77
EXECUTABLE_NAME="computer-use-agent"
8-
BUNDLE_ID="io.github.jianliang00.computer-use.agent"
8+
BUNDLE_ID="${MACOS_APP_BUNDLE_ID:-com.jianliang00.computer-use-cli}"
99
APP_VERSION="${APP_VERSION:-0.1.0}"
1010
APP_BUILD="${APP_BUILD:-1}"
1111

0 commit comments

Comments
 (0)