Implement macOS code-signing (not tested, let's see if CI works, very unlikely).

This commit is contained in:
Martín Lucas Golini
2025-10-08 21:39:27 -03:00
parent 921ad5c4c4
commit 97d6665d3b
4 changed files with 190 additions and 1 deletions

View File

@@ -271,6 +271,11 @@ jobs:
env:
CC: clang
CXX: clang++
MACOS_CERTIFICATE_P12_B64: ${{ secrets.MACOS_CERTIFICATE_P12_B64 }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
MACOS_APPLE_ID: ${{ secrets.MACOS_APPLE_ID }}
MACOS_NOTARIZATION_PASSWORD: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
MACOS_TEAM_ID: ${{ secrets.MACOS_TEAM_ID }}
steps:
- name: Checkout Code
uses: actions/checkout@v4
@@ -293,9 +298,20 @@ jobs:
run: |
bash projects/scripts/patch_commit_number.sh
bash projects/macos/ecode/build.app.sh --version ${{ env.INSTALL_REF }}
- name: Sign Application
if: env.MACOS_CERTIFICATE_P12_B64 != ''
working-directory: projects/macos/ecode
run: |
bash ./sign.sh ecode.app
- name: Create DMG Image
run: |
bash projects/macos/ecode/create.dmg.sh --version ${{ env.INSTALL_REF }}
- name: Notarize DMG
if: env.MACOS_CERTIFICATE_P12_B64 != ''
working-directory: projects/macos/ecode
run: |
DMG_NAME="ecode-macos-${{ env.INSTALL_REF }}-arm64.dmg"
bash ./sign.sh "$DMG_NAME"
- name: Upload Files
uses: softprops/action-gh-release@v2.2.2
with:
@@ -315,6 +331,11 @@ jobs:
env:
CC: clang
CXX: clang++
MACOS_CERTIFICATE_P12_B64: ${{ secrets.MACOS_CERTIFICATE_P12_B64 }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
MACOS_APPLE_ID: ${{ secrets.MACOS_APPLE_ID }}
MACOS_NOTARIZATION_PASSWORD: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
MACOS_TEAM_ID: ${{ secrets.MACOS_TEAM_ID }}
steps:
- name: Checkout Code
uses: actions/checkout@v4
@@ -341,9 +362,20 @@ jobs:
run: |
bash projects/scripts/patch_commit_number.sh
bash projects/macos/ecode/cross.build.app.sh --version ${{ env.INSTALL_REF }}
- name: Sign Application
if: env.MACOS_CERTIFICATE_P12_B64 != ''
working-directory: projects/macos/ecode
run: |
bash ./sign.sh ecode.app
- name: Create DMG Image
run: |
bash projects/macos/ecode/cross.create.dmg.sh --version ${{ env.INSTALL_REF }}
- name: Notarize DMG
if: env.MACOS_CERTIFICATE_P12_B64 != ''
working-directory: projects/macos/ecode
run: |
DMG_NAME="ecode-macos-${{ env.INSTALL_REF }}-x86_64.dmg"
bash ./sign.sh "$DMG_NAME"
- name: Upload Files
uses: softprops/action-gh-release@v2.2.2
with:

View File

@@ -508,7 +508,7 @@ Generate the project:
And build it:
`make -C make/macosx config=release_x86_64` (or `config=debug_x86_64` for a debug build)
`make -C make/macosx config=release_x86_64` (or `config=debug_x86_64` for a debug build, or `release_arm64`/`debug_arm64` if building from arm64)
##### Using premake4

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,143 @@
#!/bin/bash
set -eo pipefail
# Change to the script's directory to ensure relative paths are stable.
CANONPATH=$(readlink -f "$0")
DIRPATH="$(dirname "$CANONPATH")"
cd "$DIRPATH" || exit
# This script handles code signing and notarization for the macOS app.
# It's designed to be called from the CI environment.
#
# It expects the following environment variables to be set for real signing:
# - MACOS_CERTIFICATE_P12_B64: The base64 encoded .p12 certificate.
# - MACOS_CERTIFICATE_PASSWORD: The password for the .p12 certificate.
# - MACOS_APPLE_ID: Your Apple ID email used for notarization.
# - MACOS_NOTARIZATION_PASSWORD: An app-specific password for your Apple ID.
# - MACOS_TEAM_ID: Your Apple Developer Team ID.
#
# If these variables are not set, it will fall back to ad-hoc (self) signing for .app bundles
# and skip notarization for .dmg files.
# The first argument is the path to the artifact, relative to this script's location.
ARTIFACT_PATH="$1"
# The entitlements file is in the same directory as this script.
ENTITLEMENTS_PATH="entitlements.plist"
if [[ -z "$ARTIFACT_PATH" ]]; then
echo "Usage: $0 <path_to_app_or_dmg>"
exit 1
fi
if [[ ! -e "$ARTIFACT_PATH" ]]; then
echo "Error: Artifact not found at '$ARTIFACT_PATH'"
exit 1
fi
# --- AD-HOC SIGNING (if no credentials) ---
if [[ -z "$MACOS_CERTIFICATE_P12_B64" ]]; then
if [[ "$ARTIFACT_PATH" == *.app ]]; then
echo "No signing certificate found. Performing ad-hoc signing..."
# Find and sign all binaries within the app bundle
find "$ARTIFACT_PATH/Contents/MacOS/" -type f -exec codesign --force --sign - {} \;
codesign --force --sign - "$ARTIFACT_PATH"
echo "Ad-hoc signing complete."
fi
# For .dmg files, we just skip if no credentials
exit 0
fi
echo "Credentials found. Proceeding with official signing and notarization..."
# --- KEYCHAIN AND CERTIFICATE SETUP ---
KEYCHAIN_NAME="build.keychain"
KEYCHAIN_PASSWORD="a-very-secure-password"
CERTIFICATE_P12_PATH="certificate.p12"
# Decode the certificate
echo "$MACOS_CERTIFICATE_P12_B64" | base64 --decode > "$CERTIFICATE_P12_PATH"
# Create a temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
security default-keychain -s "$KEYCHAIN_NAME"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
security set-keychain-settings -t 3600 -u "$KEYCHAIN_NAME"
# Import the certificate into the keychain
security import "$CERTIFICATE_P12_PATH" -k "$KEYCHAIN_NAME" -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
# Allow codesign to access the certificate
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" > /dev/null
# Find the signing identity
SIGNING_IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_NAME" | grep "Developer ID Application" | head -n 1 | awk -F '"' '{print $2}')
if [[ -z "$SIGNING_IDENTITY" ]]; then
echo "Error: Signing identity not found in keychain."
exit 1
fi
echo "Using signing identity: $SIGNING_IDENTITY"
# --- MAIN LOGIC ---
# Function to sign the .app bundle
sign_app() {
echo "Signing application bundle at: $ARTIFACT_PATH"
# Sign all dylibs, frameworks and executables from the inside out
find "$ARTIFACT_PATH" -depth -name "*.dylib" -o -name "*.framework" -o -path "$ARTIFACT_PATH/Contents/MacOS/*" -type f | while read -r comp; do
echo "Signing component: $comp"
codesign --force --verify --verbose --sign "$SIGNING_IDENTITY" --options runtime --timestamp "$comp"
done
echo "Signing main application bundle with entitlements..."
codesign --force --verify --verbose --sign "$SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS_PATH" --options runtime --timestamp "$ARTIFACT_PATH"
echo "App signing complete."
}
# Function to notarize and staple the .dmg
notarize_dmg() {
echo "Notarizing DMG at: $ARTIFACT_PATH"
# Submit for notarization and wait for it to complete
NOTARY_OUTPUT=$(xcrun notarytool submit "$ARTIFACT_PATH" \
--apple-id "$MACOS_APPLE_ID" \
--password "$MACOS_NOTARIZATION_PASSWORD" \
--team-id "$MACOS_TEAM_ID" \
--wait 2>&1)
echo "$NOTARY_OUTPUT"
# Check for success
if ! echo "$NOTARY_OUTPUT" | grep -q "Status: Accepted"; then
echo "Error: Notarization failed."
# Attempt to get logs for debugging
REQUEST_UUID=$(echo "$NOTARY_OUTPUT" | grep "id:" | awk '{print $2}')
if [[ -n "$REQUEST_UUID" ]]; then
echo "Fetching notarization logs for UUID: $REQUEST_UUID"
xcrun notarytool log "$REQUEST_UUID" --apple-id "$MACOS_APPLE_ID" --password "$MACOS_NOTARIZATION_PASSWORD" --team-id "$MACOS_TEAM_ID"
fi
exit 1
fi
echo "Notarization successful. Stapling ticket to DMG..."
xcrun stapler staple "$ARTIFACT_PATH"
echo "Stapling complete."
}
# --- CLEANUP ---
cleanup() {
echo "Cleaning up..."
security delete-keychain "$KEYCHAIN_NAME" || true
rm -f "$CERTIFICATE_P12_PATH"
}
trap cleanup EXIT
# --- EXECUTION ---
if [[ "$ARTIFACT_PATH" == *.app ]]; then
sign_app
elif [[ "$ARTIFACT_PATH" == *.dmg ]]; then
notarize_dmg
else
echo "Unsupported artifact type: $ARTIFACT_PATH"
exit 1
fi