Skip to content

Commit ef2e762

Browse files
committed
Sign releases with free Apple Development identity
Restore Launch at Login for public builds by importing an Apple Development .p12 in CI and signing the Release app with the Personal Team identity. Keep Sparkle EdDSA as the update trust anchor and continue distributing without notarization. Validate the signing authority and Team ID, clean up the temporary keychain, ignore private-key exports, document the first-launch trust model, and prepare Elapse Recorder 1.13.2 (build 33) as the signed repair release.
1 parent 37c1b90 commit ef2e762

6 files changed

Lines changed: 105 additions & 30 deletions

File tree

.github/workflows/release.yml

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ concurrency:
1313
cancel-in-progress: false
1414

1515
# Required repository configuration before pushing a v* tag:
16-
# Variable:
16+
# Variables:
17+
# APPLE_TEAM_ID: free Personal Team identifier used by the Apple Development certificate.
1718
# SPARKLE_PUBLIC_ED_KEY: output from Sparkle's generate_keys tool.
18-
# Secret:
19+
# Secrets:
20+
# APPLE_DEVELOPMENT_CERTIFICATE: base64-encoded Apple Development .p12 export.
21+
# APPLE_DEVELOPMENT_CERTIFICATE_PASSWORD: password chosen when exporting the .p12.
1922
# SPARKLE_PRIVATE_ED_KEY: contents of the file exported by generate_keys -x.
2023
# The public and private Sparkle keys must be generated as one pair.
21-
# Releases are intentionally Apple-unsigned and are authenticated by Sparkle EdDSA.
24+
# Releases are Apple Development-signed (not notarized) and Sparkle EdDSA-authenticated.
2225

2326
jobs:
2427
release:
@@ -27,7 +30,10 @@ jobs:
2730
runs-on: macos-26
2831
env:
2932
DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer
33+
APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }}
3034
SPARKLE_PUBLIC_ED_KEY: ${{ vars.SPARKLE_PUBLIC_ED_KEY }}
35+
APPLE_DEVELOPMENT_CERTIFICATE: ${{ secrets.APPLE_DEVELOPMENT_CERTIFICATE }}
36+
APPLE_DEVELOPMENT_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPMENT_CERTIFICATE_PASSWORD }}
3137
SPARKLE_PRIVATE_ED_KEY: ${{ secrets.SPARKLE_PRIVATE_ED_KEY }}
3238
GH_TOKEN: ${{ github.token }}
3339

@@ -51,7 +57,7 @@ jobs:
5157
run: |
5258
set -euo pipefail
5359
missing=()
54-
for name in SPARKLE_PUBLIC_ED_KEY SPARKLE_PRIVATE_ED_KEY; do
60+
for name in APPLE_TEAM_ID SPARKLE_PUBLIC_ED_KEY APPLE_DEVELOPMENT_CERTIFICATE APPLE_DEVELOPMENT_CERTIFICATE_PASSWORD SPARKLE_PRIVATE_ED_KEY; do
5561
if [[ -z "${!name:-}" ]]; then
5662
missing+=("$name")
5763
fi
@@ -69,6 +75,41 @@ jobs:
6975
echo "SPARKLE_PUBLIC_ED_KEY must decode to 32 bytes, found $decoded_length." >&2
7076
exit 1
7177
}
78+
[[ "$APPLE_TEAM_ID" =~ ^[A-Z0-9]{10}$ ]] || {
79+
echo "APPLE_TEAM_ID must be a 10-character team identifier." >&2
80+
exit 1
81+
}
82+
83+
- name: Import Apple Development certificate
84+
shell: bash
85+
run: |
86+
set -euo pipefail
87+
keychain="$RUNNER_TEMP/release-signing.keychain-db"
88+
certificate="$RUNNER_TEMP/apple-development.p12"
89+
keychain_password="$(openssl rand -hex 24)"
90+
printf '%s' "$APPLE_DEVELOPMENT_CERTIFICATE" | /usr/bin/base64 -D > "$certificate"
91+
test -s "$certificate"
92+
security create-keychain -p "$keychain_password" "$keychain"
93+
security set-keychain-settings -lut 21600 "$keychain"
94+
security unlock-keychain -p "$keychain_password" "$keychain"
95+
security import "$certificate" \
96+
-k "$keychain" \
97+
-P "$APPLE_DEVELOPMENT_CERTIFICATE_PASSWORD" \
98+
-T /usr/bin/codesign \
99+
-T /usr/bin/security
100+
security set-key-partition-list \
101+
-S apple-tool:,apple: \
102+
-s \
103+
-k "$keychain_password" \
104+
"$keychain"
105+
security list-keychains -d user -s "$keychain"
106+
identity_output="$(security find-identity -v -p codesigning "$keychain")"
107+
printf '%s\n' "$identity_output"
108+
grep -q '"Apple Development:' <<< "$identity_output" || {
109+
echo "Imported .p12 does not contain a usable Apple Development identity." >&2
110+
exit 1
111+
}
112+
echo "RELEASE_KEYCHAIN=$keychain" >> "$GITHUB_ENV"
72113
73114
- name: Verify tag matches app version
74115
shell: bash
@@ -88,7 +129,7 @@ jobs:
88129
echo "APP_VERSION=$version" >> "$GITHUB_ENV"
89130
echo "APP_BUILD=$build" >> "$GITHUB_ENV"
90131
91-
- name: Build Apple-unsigned application
132+
- name: Build Apple Development-signed application
92133
shell: bash
93134
run: |
94135
set -euo pipefail
@@ -106,24 +147,29 @@ jobs:
106147
-skipMacroValidation \
107148
-skipPackagePluginValidation \
108149
-onlyUsePackageVersionsFromResolvedFile \
109-
CODE_SIGNING_ALLOWED=NO \
110-
CODE_SIGNING_REQUIRED=NO \
111-
CODE_SIGN_INJECT_BASE_ENTITLEMENTS=NO \
112-
ENABLE_HARDENED_RUNTIME=NO \
150+
CODE_SIGN_STYLE=Manual \
151+
CODE_SIGN_IDENTITY='Apple Development' \
152+
DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \
153+
OTHER_CODE_SIGN_FLAGS="--keychain $RELEASE_KEYCHAIN" \
113154
SPARKLE_PUBLIC_ED_KEY="$SPARKLE_PUBLIC_ED_KEY"
114155
115156
app="$PWD/build/DerivedData/Build/Products/Release/Elapse Recorder.app"
116157
[[ -d "$app" ]] || {
117-
echo "Unsigned app was not produced at $app" >&2
158+
echo "Signed app was not produced at $app" >&2
118159
exit 1
119160
}
120-
# Mach-O binaries may receive an automatic linker ad-hoc signature even when Xcode
121-
# code signing is disabled. Remove it so Sparkle uses EdDSA as the sole trust anchor.
122-
codesign --remove-signature "$app"
123-
if codesign -dv "$app" >/dev/null 2>&1; then
124-
echo "Expected an Apple-unsigned host app, but a code signature is present." >&2
161+
codesign --verify --deep --strict --verbose=2 "$app"
162+
signature_info="$(codesign -dv --verbose=4 "$app" 2>&1)"
163+
printf '%s\n' "$signature_info"
164+
grep -q '^Authority=Apple Development:' <<< "$signature_info" || {
165+
echo "Built app is not signed with an Apple Development identity." >&2
125166
exit 1
126-
fi
167+
}
168+
embedded_team="$(awk -F= '/^TeamIdentifier=/ { print $2 }' <<< "$signature_info")"
169+
[[ "$embedded_team" == "$APPLE_TEAM_ID" ]] || {
170+
echo "Built app team $embedded_team does not match APPLE_TEAM_ID $APPLE_TEAM_ID." >&2
171+
exit 1
172+
}
127173
embedded_key="$(/usr/libexec/PlistBuddy -c 'Print SUPublicEDKey' "$app/Contents/Info.plist")"
128174
[[ "$embedded_key" == "$SPARKLE_PUBLIC_ED_KEY" ]] || {
129175
echo "Built app does not contain the configured Sparkle public key." >&2
@@ -135,15 +181,15 @@ jobs:
135181
echo "Built app version $embedded_version ($embedded_build) does not match $APP_VERSION ($APP_BUILD)." >&2
136182
exit 1
137183
}
138-
echo "UNSIGNED_APP=$app" >> "$GITHUB_ENV"
184+
echo "SIGNED_APP=$app" >> "$GITHUB_ENV"
139185
140186
- name: Package Sparkle update
141187
shell: bash
142188
run: |
143189
set -euo pipefail
144190
mkdir -p build/updates
145191
archive="build/updates/ElapseRecorder-${APP_VERSION}.zip"
146-
ditto -c -k --sequesterRsrc --keepParent "$UNSIGNED_APP" "$archive"
192+
ditto -c -k --sequesterRsrc --keepParent "$SIGNED_APP" "$archive"
147193
echo "UPDATE_ARCHIVE=$archive" >> "$GITHUB_ENV"
148194
149195
- name: Create draft GitHub release
@@ -202,3 +248,11 @@ jobs:
202248
set -euo pipefail
203249
gh release upload "$GITHUB_REF_NAME" build/updates/appcast.xml --clobber
204250
gh release edit "$GITHUB_REF_NAME" --draft=false
251+
252+
- name: Remove release keychain
253+
if: always()
254+
shell: bash
255+
run: |
256+
if [[ -n "${RELEASE_KEYCHAIN:-}" ]]; then
257+
security delete-keychain "$RELEASE_KEYCHAIN" || true
258+
fi

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ build/
1818
.swiftpm/
1919
.build/
2020
.github/copilot-instructions.md
21+
22+
# Signing key exports
23+
*.p12
24+
*.sparkle.private-key

Docs/简体中文.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ Elapse Recorder是一款原生macOS菜单栏工具——它可以在你工作时
4747
2. 解压`Elapse Recorder.app`并将其移动到`/Applications`
4848
3. 首次启动时,右键单击应用并选择**打开。**如果macOS仍然阻止启动,请在系统设置→隐私与安全性中允许Elapse Recorder运行,然后再次打开。
4949

50+
> [!NOTE]
51+
>
52+
> 官方版本使用免费Apple Development证书签名,并通过Sparkle EdDSA验证,但未经过公证。macOS仍可能要求你通过Finder或隐私与安全性批准首次启动。
53+
5054
## 构建
5155

5256
```sh

ElapseRecorder.xcodeproj/project.pbxproj

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@
455455
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
456456
CODE_SIGN_STYLE = Automatic;
457457
COMBINE_HIDPI_IMAGES = YES;
458-
CURRENT_PROJECT_VERSION = 32;
458+
CURRENT_PROJECT_VERSION = 33;
459459
DEVELOPMENT_TEAM = 7XRF3J9RS4;
460460
ENABLE_APP_SANDBOX = NO;
461461
ENABLE_HARDENED_RUNTIME = YES;
@@ -482,7 +482,7 @@
482482
"$(inherited)",
483483
"@executable_path/../Frameworks",
484484
);
485-
MARKETING_VERSION = 1.13.1;
485+
MARKETING_VERSION = 1.13.2;
486486
PRODUCT_BUNDLE_IDENTIFIER = d.iota.ElapseRecorder;
487487
PRODUCT_MODULE_NAME = ElapseRecorder;
488488
PRODUCT_NAME = "Elapse Recorder";
@@ -504,7 +504,7 @@
504504
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
505505
CODE_SIGN_STYLE = Automatic;
506506
COMBINE_HIDPI_IMAGES = YES;
507-
CURRENT_PROJECT_VERSION = 32;
507+
CURRENT_PROJECT_VERSION = 33;
508508
DEVELOPMENT_TEAM = 7XRF3J9RS4;
509509
ENABLE_APP_SANDBOX = NO;
510510
ENABLE_HARDENED_RUNTIME = YES;
@@ -531,7 +531,7 @@
531531
"$(inherited)",
532532
"@executable_path/../Frameworks",
533533
);
534-
MARKETING_VERSION = 1.13.1;
534+
MARKETING_VERSION = 1.13.2;
535535
PRODUCT_BUNDLE_IDENTIFIER = d.iota.ElapseRecorder;
536536
PRODUCT_MODULE_NAME = ElapseRecorder;
537537
PRODUCT_NAME = "Elapse Recorder";
@@ -551,11 +551,11 @@
551551
buildSettings = {
552552
BUNDLE_LOADER = "$(TEST_HOST)";
553553
CODE_SIGN_STYLE = Automatic;
554-
CURRENT_PROJECT_VERSION = 32;
554+
CURRENT_PROJECT_VERSION = 33;
555555
DEVELOPMENT_TEAM = 7XRF3J9RS4;
556556
GENERATE_INFOPLIST_FILE = YES;
557557
MACOSX_DEPLOYMENT_TARGET = 15.7;
558-
MARKETING_VERSION = 1.13.1;
558+
MARKETING_VERSION = 1.13.2;
559559
PRODUCT_BUNDLE_IDENTIFIER = d.iota.ElapseRecorderTests;
560560
PRODUCT_NAME = "$(TARGET_NAME)";
561561
STRING_CATALOG_GENERATE_SYMBOLS = NO;
@@ -572,11 +572,11 @@
572572
buildSettings = {
573573
BUNDLE_LOADER = "$(TEST_HOST)";
574574
CODE_SIGN_STYLE = Automatic;
575-
CURRENT_PROJECT_VERSION = 32;
575+
CURRENT_PROJECT_VERSION = 33;
576576
DEVELOPMENT_TEAM = 7XRF3J9RS4;
577577
GENERATE_INFOPLIST_FILE = YES;
578578
MACOSX_DEPLOYMENT_TARGET = 15.7;
579-
MARKETING_VERSION = 1.13.1;
579+
MARKETING_VERSION = 1.13.2;
580580
PRODUCT_BUNDLE_IDENTIFIER = d.iota.ElapseRecorderTests;
581581
PRODUCT_NAME = "$(TARGET_NAME)";
582582
STRING_CATALOG_GENERATE_SYMBOLS = NO;
@@ -592,10 +592,10 @@
592592
isa = XCBuildConfiguration;
593593
buildSettings = {
594594
CODE_SIGN_STYLE = Automatic;
595-
CURRENT_PROJECT_VERSION = 32;
595+
CURRENT_PROJECT_VERSION = 33;
596596
DEVELOPMENT_TEAM = 7XRF3J9RS4;
597597
GENERATE_INFOPLIST_FILE = YES;
598-
MARKETING_VERSION = 1.13.1;
598+
MARKETING_VERSION = 1.13.2;
599599
PRODUCT_BUNDLE_IDENTIFIER = d.iota.ElapseRecorderUITests;
600600
PRODUCT_NAME = "$(TARGET_NAME)";
601601
STRING_CATALOG_GENERATE_SYMBOLS = NO;
@@ -611,10 +611,10 @@
611611
isa = XCBuildConfiguration;
612612
buildSettings = {
613613
CODE_SIGN_STYLE = Automatic;
614-
CURRENT_PROJECT_VERSION = 32;
614+
CURRENT_PROJECT_VERSION = 33;
615615
DEVELOPMENT_TEAM = 7XRF3J9RS4;
616616
GENERATE_INFOPLIST_FILE = YES;
617-
MARKETING_VERSION = 1.13.1;
617+
MARKETING_VERSION = 1.13.2;
618618
PRODUCT_BUNDLE_IDENTIFIER = d.iota.ElapseRecorderUITests;
619619
PRODUCT_NAME = "$(TARGET_NAME)";
620620
STRING_CATALOG_GENERATE_SYMBOLS = NO;

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ After choosing the applications and monitoring rules you care about, no further
4747
2. Extract `Elapse Recorder.app` and move it to `/Applications`.
4848
3. On first launch, right-click the app and choose **Open.** If macOS still blocks it, approve Elapse Recorder under System Settings → Privacy & Security and open it again.
4949

50+
> [!NOTE]
51+
>
52+
> Official releases are signed with a free Apple Development certificate and authenticated by Sparkle EdDSA, but they are not notarized. macOS may still require first-launch approval through Finder or Privacy & Security.
53+
5054
## Building
5155

5256
```sh

ReleaseNotes/1.13.2.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Elapse Recorder 1.13.2
2+
3+
This maintenance release restores **Launch at Login** for publicly distributed builds.
4+
5+
## Fixes
6+
7+
- Sign the release with a free Apple Development identity so macOS Background Task Management can register Elapse Recorder as a login item.
8+
- Keep Sparkle EdDSA verification as the update trust anchor.
9+
- Preserve the manual first-launch approval flow for the non-notarized app.

0 commit comments

Comments
 (0)