Skip to content

Commit 7908221

Browse files
MagicalTuxclaude
andcommitted
feat: add macOS code signing and notarization to CI
Add Apple Developer ID signing, notarization, and stapling for the .app bundle, DMG, and ctermd binary. All signing steps are gated on APPLE_CERTIFICATE_BASE64 secret so builds still work without it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1518bcf commit 7908221

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

.github/workflows/build.yml

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,83 @@ jobs:
660660
# Create PkgInfo
661661
echo -n "APPL????" > "$APP_DIR/Contents/PkgInfo"
662662
663+
- name: Import signing certificate
664+
if: env.APPLE_CERTIFICATE_BASE64 != ''
665+
env:
666+
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
667+
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
668+
run: |
669+
# Decode certificate
670+
echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > /tmp/certificate.p12
671+
672+
# Create a temporary keychain
673+
KEYCHAIN_PATH="/tmp/signing.keychain-db"
674+
KEYCHAIN_PASSWORD="$(openssl rand -hex 16)"
675+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
676+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
677+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
678+
679+
# Import certificate
680+
security import /tmp/certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" \
681+
-A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
682+
683+
# Allow codesign to access the keychain
684+
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
685+
686+
# Add temporary keychain to search list
687+
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
688+
689+
# Clean up certificate file
690+
rm /tmp/certificate.p12
691+
692+
- name: Sign app bundle
693+
if: env.APPLE_CERTIFICATE_BASE64 != ''
694+
env:
695+
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
696+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
697+
run: |
698+
IDENTITY="Developer ID Application: ($APPLE_TEAM_ID)"
699+
700+
# Sign the binary inside the bundle with hardened runtime
701+
codesign --force --options runtime \
702+
--entitlements packaging/macos/cterm.entitlements \
703+
--sign "$IDENTITY" \
704+
--timestamp \
705+
"release/cterm.app/Contents/MacOS/cterm"
706+
707+
# Sign the app bundle itself
708+
codesign --force --options runtime \
709+
--entitlements packaging/macos/cterm.entitlements \
710+
--sign "$IDENTITY" \
711+
--timestamp \
712+
"release/cterm.app"
713+
714+
# Verify
715+
codesign --verify --deep --strict --verbose=2 "release/cterm.app"
716+
717+
- name: Notarize app bundle
718+
if: env.APPLE_CERTIFICATE_BASE64 != ''
719+
env:
720+
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
721+
APPLE_ID: ${{ secrets.APPLE_ID }}
722+
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
723+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
724+
run: |
725+
# Create a zip for notarization submission
726+
ditto -c -k --keepParent "release/cterm.app" /tmp/cterm-notarize.zip
727+
728+
# Submit for notarization
729+
xcrun notarytool submit /tmp/cterm-notarize.zip \
730+
--apple-id "$APPLE_ID" \
731+
--password "$APPLE_ID_PASSWORD" \
732+
--team-id "$APPLE_TEAM_ID" \
733+
--wait --timeout 30m
734+
735+
# Staple the notarization ticket to the app
736+
xcrun stapler staple "release/cterm.app"
737+
738+
rm /tmp/cterm-notarize.zip
739+
663740
- name: Install create-dmg
664741
run: brew install create-dmg
665742

@@ -692,6 +769,50 @@ jobs:
692769
# Rename to standard name for artifact
693770
mv "release/cterm-macos-$VERSION.dmg" "release/cterm-macos-universal.dmg"
694771
772+
- name: Sign and notarize DMG
773+
if: env.APPLE_CERTIFICATE_BASE64 != ''
774+
env:
775+
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
776+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
777+
APPLE_ID: ${{ secrets.APPLE_ID }}
778+
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
779+
run: |
780+
IDENTITY="Developer ID Application: ($APPLE_TEAM_ID)"
781+
782+
# Sign the DMG
783+
codesign --force --sign "$IDENTITY" --timestamp "release/cterm-macos-universal.dmg"
784+
785+
# Notarize the DMG
786+
xcrun notarytool submit "release/cterm-macos-universal.dmg" \
787+
--apple-id "$APPLE_ID" \
788+
--password "$APPLE_ID_PASSWORD" \
789+
--team-id "$APPLE_TEAM_ID" \
790+
--wait --timeout 30m
791+
792+
# Staple the notarization ticket to the DMG
793+
xcrun stapler staple "release/cterm-macos-universal.dmg"
794+
795+
- name: Sign ctermd binary
796+
if: env.APPLE_CERTIFICATE_BASE64 != ''
797+
env:
798+
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
799+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
800+
run: |
801+
IDENTITY="Developer ID Application: ($APPLE_TEAM_ID)"
802+
codesign --force --options runtime \
803+
--sign "$IDENTITY" \
804+
--timestamp \
805+
"universal-binary/ctermd"
806+
codesign --verify --strict --verbose=2 "universal-binary/ctermd"
807+
808+
- name: Clean up keychain
809+
if: always()
810+
run: |
811+
KEYCHAIN_PATH="/tmp/signing.keychain-db"
812+
if [ -f "$KEYCHAIN_PATH" ]; then
813+
security delete-keychain "$KEYCHAIN_PATH" || true
814+
fi
815+
695816
- name: Create tar.gz for auto-update (app bundle)
696817
run: |
697818
# Create tar.gz of the full app bundle for auto-update

packaging/macos/cterm.entitlements

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict/>
5+
</plist>

0 commit comments

Comments
 (0)