Skip to content

Commit c89804a

Browse files
committed
fix: re-sign macos desktop bundle before release
1 parent 573784f commit c89804a

File tree

5 files changed

+212
-1
lines changed

5 files changed

+212
-1
lines changed

.github/workflows/desktop-release.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jobs:
2222
bash scripts/test/desktop_updater_config_test.sh
2323
bash scripts/test/generate_updater_manifest_test.sh
2424
bash scripts/test/desktop_release_workflow_publish_test.sh
25+
bash scripts/test/package_local_release_test.sh
2526
2627
package-macos:
2728
needs: validate-release-scripts
@@ -54,12 +55,27 @@ jobs:
5455
cp "$(which tesseract)" "$RUNTIME_DIR/macos/bin/tesseract"
5556
cp "$(which gs)" "$RUNTIME_DIR/macos/bin/gs"
5657
echo "SCREEN_PDF_RUNTIME_SOURCE_DIR=$RUNTIME_DIR" >> "$GITHUB_ENV"
58+
- name: Prepare notarization key
59+
env:
60+
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
61+
run: |
62+
if [[ -z "${APPLE_API_KEY:-}" ]]; then
63+
echo "APPLE_API_KEY not set, skip notarization key preparation"
64+
exit 0
65+
fi
66+
key_path="$RUNNER_TEMP/AuthKey.p8"
67+
printf '%s' "$APPLE_API_KEY" >"$key_path"
68+
echo "APPLE_API_KEY_PATH=$key_path" >> "$GITHUB_ENV"
5769
- name: Package desktop app
5870
run: bash scripts/desktop/package_local_release.sh
5971
env:
6072
RELEASE_PLATFORM: macos
6173
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
6274
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
75+
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
76+
APPLE_API_KEY_PATH: ${{ env.APPLE_API_KEY_PATH }}
77+
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
78+
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
6379
- uses: actions/upload-artifact@v4
6480
with:
6581
name: screen-pdf-macos

scripts/desktop/notarize_macos.sh

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
5+
BUNDLE_ROOT="${RELEASE_BUNDLE_DIR:-$ROOT_DIR/program/desktop/src-tauri/target/release/bundle}"
6+
APP_PATH="$(find "$BUNDLE_ROOT/macos" -maxdepth 1 -name "*.app" -type d -print0 | xargs -0 ls -td 2>/dev/null | head -n1 || true)"
7+
DMG_PATH="$(find "$BUNDLE_ROOT/dmg" -maxdepth 1 -name "*.dmg" -type f -print0 | xargs -0 ls -t 2>/dev/null | head -n1 || true)"
8+
REQUIRE_NOTARIZATION="${REQUIRE_MACOS_NOTARIZATION:-0}"
9+
10+
require_env() {
11+
local name="$1"
12+
if [[ -z "${!name:-}" ]]; then
13+
echo "missing required environment variable: $name" >&2
14+
exit 1
15+
fi
16+
}
17+
18+
if [[ -z "$APP_PATH" ]]; then
19+
echo "No macOS app bundle found under $BUNDLE_ROOT, skip notarization"
20+
exit 0
21+
fi
22+
23+
if [[ "$REQUIRE_NOTARIZATION" == "1" ]]; then
24+
require_env APPLE_SIGNING_IDENTITY
25+
require_env APPLE_API_KEY_PATH
26+
require_env APPLE_API_KEY_ID
27+
require_env APPLE_API_ISSUER
28+
fi
29+
30+
if [[ -n "${APPLE_SIGNING_IDENTITY:-}" ]]; then
31+
echo "Code signing app with Developer ID identity"
32+
codesign --force --deep --options runtime --timestamp \
33+
--sign "$APPLE_SIGNING_IDENTITY" \
34+
"$APP_PATH"
35+
else
36+
echo "APPLE_SIGNING_IDENTITY not set, falling back to ad-hoc codesign"
37+
codesign --force --deep --sign - "$APP_PATH"
38+
fi
39+
40+
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
41+
42+
if [[ -z "${APPLE_API_KEY_PATH:-}" || -z "${APPLE_API_KEY_ID:-}" || -z "${APPLE_API_ISSUER:-}" ]]; then
43+
echo "Apple notarization credentials are incomplete, skip notarization"
44+
exit 0
45+
fi
46+
47+
if [[ -n "$DMG_PATH" ]]; then
48+
NOTARY_INPUT="$DMG_PATH"
49+
else
50+
NOTARY_INPUT="$ROOT_DIR/program/desktop/src-tauri/target/notary/ScreenPDF.zip"
51+
mkdir -p "$(dirname "$NOTARY_INPUT")"
52+
ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "$NOTARY_INPUT"
53+
fi
54+
55+
xcrun notarytool submit "$NOTARY_INPUT" \
56+
--key "$APPLE_API_KEY_PATH" \
57+
--key-id "$APPLE_API_KEY_ID" \
58+
--issuer "$APPLE_API_ISSUER" \
59+
--wait
60+
61+
xcrun stapler staple "$APP_PATH"
62+
if [[ -n "$DMG_PATH" ]]; then
63+
xcrun stapler staple "$DMG_PATH"
64+
fi
65+
66+
spctl -a -vv -t exec "$APP_PATH"

scripts/desktop/package_local_release.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,19 @@ esac
3535
bash "$ROOT_DIR/scripts/release/sync_release_metadata.sh"
3636
pnpm --dir "$ROOT_DIR/program/desktop" tauri build
3737

38+
if [[ "$PLATFORM" == "macos" ]]; then
39+
bash "$ROOT_DIR/scripts/desktop/notarize_macos.sh"
40+
fi
41+
3842
RELEASE_VERSION="$VERSION" RELEASE_PLATFORM="$PLATFORM" RELEASE_ASSET_DIR="$ASSET_DIR" \
3943
bash "$ROOT_DIR/scripts/desktop/collect_release_assets.sh"
4044

4145
if [[ "$PLATFORM" == "macos" ]]; then
42-
BUNDLE_PATH="$ROOT_DIR/program/desktop/src-tauri/target/release/bundle/macos/ScreenPDF.app"
46+
BUNDLE_PATH="$(find "$ROOT_DIR/program/desktop/src-tauri/target/release/bundle/macos" -maxdepth 1 -name '*.app' -type d -print0 | xargs -0 ls -td 2>/dev/null | head -n1 || true)"
47+
if [[ -z "$BUNDLE_PATH" ]]; then
48+
echo "macOS app bundle not found after tauri build" >&2
49+
exit 1
50+
fi
4351
VERIFY_ARGS=(
4452
--bundle "$BUNDLE_PATH"
4553
--platform "$PLATFORM"

scripts/test/desktop_release_workflow_publish_test.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ assert_contains 'choco install tesseract -y'
2929
assert_contains 'ghostscriptInstallerUrl = "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10070/gs10070w64.exe"'
3030
assert_contains 'Get-Command 7z.exe'
3131
assert_contains 'gswin64c.exe not found in extracted Ghostscript installer'
32+
assert_contains 'Prepare notarization key'
33+
assert_contains 'APPLE_SIGNING_IDENTITY'
34+
assert_contains 'APPLE_API_KEY'
35+
assert_contains 'APPLE_API_KEY_ID'
36+
assert_contains 'APPLE_API_ISSUER'
37+
assert_contains 'APPLE_API_KEY_PATH'
38+
assert_contains 'APPLE_API_KEY not set, skip notarization key preparation'
3239
assert_contains 'name: Collect publish files'
3340
assert_contains '**/*.dmg'
3441
assert_contains '**/*.msi'
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
5+
SOURCE_SCRIPT_PATH="$ROOT_DIR/scripts/desktop/package_local_release.sh"
6+
HELPER_PATH="$ROOT_DIR/scripts/desktop/release_version_helpers.sh"
7+
8+
fail() {
9+
echo "FAIL: $*" >&2
10+
exit 1
11+
}
12+
13+
assert_file() {
14+
local path="$1"
15+
[[ -f "$path" ]] || fail "expected file: $path"
16+
}
17+
18+
assert_contains() {
19+
local file="$1"
20+
local pattern="$2"
21+
local content
22+
content="$(cat "$file" 2>/dev/null || true)"
23+
if [[ "$content" != *"$pattern"* ]]; then
24+
echo "=== $file ===" >&2
25+
cat "$file" >&2 || true
26+
fail "expected $file to contain $pattern"
27+
fi
28+
}
29+
30+
tmp_dir="$(mktemp -d)"
31+
trap 'rm -rf "$tmp_dir"' EXIT
32+
33+
repo_dir="$tmp_dir/repo"
34+
mkdir -p "$repo_dir/scripts/release" "$repo_dir/scripts/desktop" "$repo_dir/bin" "$repo_dir/program/desktop"
35+
cp "$SOURCE_SCRIPT_PATH" "$repo_dir/scripts/desktop/package_local_release.sh"
36+
cp "$HELPER_PATH" "$repo_dir/scripts/desktop/release_version_helpers.sh"
37+
mkdir -p "$repo_dir/program/desktop/src-tauri/target/release/bundle/macos/ScreenPDF.app"
38+
39+
cat >"$repo_dir/scripts/release/sync_release_metadata.sh" <<'EOF'
40+
#!/usr/bin/env bash
41+
set -euo pipefail
42+
printf 'sync:%s\n' "$*" >>"$CALL_LOG"
43+
EOF
44+
chmod +x "$repo_dir/scripts/release/sync_release_metadata.sh"
45+
46+
cat >"$repo_dir/scripts/desktop/collect_release_assets.sh" <<'EOF'
47+
#!/usr/bin/env bash
48+
set -euo pipefail
49+
printf 'collect:%s:%s\n' "${RELEASE_VERSION:-missing}" "${RELEASE_PLATFORM:-missing}" >>"$CALL_LOG"
50+
mkdir -p "${RELEASE_ASSET_DIR:-$PWD/release-assets}"
51+
printf 'artifact\n' >"${RELEASE_ASSET_DIR:-$PWD/release-assets}/artifact.txt"
52+
EOF
53+
chmod +x "$repo_dir/scripts/desktop/collect_release_assets.sh"
54+
55+
cat >"$repo_dir/scripts/desktop/prepare_runtime_macos.sh" <<'EOF'
56+
#!/usr/bin/env bash
57+
set -euo pipefail
58+
printf 'prepare-runtime:macos\n' >>"$CALL_LOG"
59+
EOF
60+
chmod +x "$repo_dir/scripts/desktop/prepare_runtime_macos.sh"
61+
62+
cat >"$repo_dir/scripts/desktop/notarize_macos.sh" <<'EOF'
63+
#!/usr/bin/env bash
64+
set -euo pipefail
65+
printf 'notarize:macos\n' >>"$CALL_LOG"
66+
EOF
67+
chmod +x "$repo_dir/scripts/desktop/notarize_macos.sh"
68+
69+
cat >"$repo_dir/bin/pnpm" <<'EOF'
70+
#!/usr/bin/env bash
71+
set -euo pipefail
72+
printf 'pnpm:%s\n' "$*" >>"$CALL_LOG"
73+
EOF
74+
chmod +x "$repo_dir/bin/pnpm"
75+
76+
cat >"$repo_dir/bin/python" <<'EOF'
77+
#!/usr/bin/env bash
78+
set -euo pipefail
79+
printf 'python:%s\n' "$*" >>"$CALL_LOG"
80+
if [[ "${1:-}" == *"release_metadata.py" && "${2:-}" == "version" ]]; then
81+
printf '2.3.4\n'
82+
elif [[ "${1:-}" == *"release_metadata.py" && "${2:-}" == "platform" ]]; then
83+
printf '%s\n' "${@: -1}"
84+
fi
85+
EOF
86+
chmod +x "$repo_dir/bin/python"
87+
88+
(
89+
cd "$repo_dir"
90+
git init >/dev/null
91+
git config user.name "Codex"
92+
git config user.email "codex@example.com"
93+
printf 'seed\n' >README.md
94+
git add README.md
95+
git commit -m "init" >/dev/null
96+
git tag v2.3.4
97+
printf 'next\n' >>README.md
98+
git add README.md
99+
git commit -m "next" >/dev/null
100+
101+
CALL_LOG="$tmp_dir/calls.log" \
102+
PATH="$repo_dir/bin:$PATH" \
103+
RELEASE_PLATFORM="macos" \
104+
RELEASE_ASSET_DIR="$tmp_dir/release-assets" \
105+
bash "$repo_dir/scripts/desktop/package_local_release.sh" >/dev/null
106+
)
107+
108+
assert_file "$tmp_dir/release-assets/artifact.txt"
109+
assert_contains "$tmp_dir/calls.log" "prepare-runtime:macos"
110+
assert_contains "$tmp_dir/calls.log" "sync:"
111+
assert_contains "$tmp_dir/calls.log" "pnpm:--dir"
112+
assert_contains "$tmp_dir/calls.log" "notarize:macos"
113+
114+
echo "PASS: package_local_release_test"

0 commit comments

Comments
 (0)