mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-01 03:12:37 +08:00
[build] add macOS signing and notarization to GitHub Actions
This commit is contained in:
@@ -127,9 +127,9 @@ fi
|
||||
# The package cache remains intact for debugging and other consumers.
|
||||
strip -S -x "$FRAMEWORKS_DIR/$SLINT_NAME"
|
||||
|
||||
# install_name_tool and strip invalidate the linker's ad-hoc signature. Sign nested code
|
||||
# first, then seal the complete app bundle so local packages remain launchable.
|
||||
codesign --force --sign - "$FRAMEWORKS_DIR/$SLINT_NAME"
|
||||
codesign --force --deep --sign - "$APP_BUNDLE"
|
||||
# install_name_tool and strip invalidate existing signatures. Sign only after
|
||||
# all bundle changes, using Developer ID when configured or ad-hoc for local use.
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
bash "$SCRIPT_DIR/sign_app.sh" "$APP_BUNDLE"
|
||||
|
||||
echo "Bundled Slint runtime: $SLINT_LIBRARY"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?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.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
<key>com.apple.security.automation.apple-events</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 1 || -z "${CROSSDESK_MACOS_NOTARY_PROFILE:-}" ]]; then
|
||||
echo "Usage: CROSSDESK_MACOS_NOTARY_PROFILE=<keychain-profile> $0 <signed.pkg>" >&2
|
||||
echo "Create the profile with: xcrun notarytool store-credentials <keychain-profile>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
PACKAGE_FILE="$1"
|
||||
if [[ ! -f "$PACKAGE_FILE" || "$PACKAGE_FILE" != *.pkg ]]; then
|
||||
echo "Expected a signed .pkg file: $PACKAGE_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pkgutil --check-signature "$PACKAGE_FILE"
|
||||
NOTARY_ARGS=(--keychain-profile "$CROSSDESK_MACOS_NOTARY_PROFILE")
|
||||
if [[ -n "${CROSSDESK_MACOS_KEYCHAIN:-}" ]]; then
|
||||
NOTARY_ARGS+=(--keychain "$CROSSDESK_MACOS_KEYCHAIN")
|
||||
fi
|
||||
UPLOAD_ARGS=()
|
||||
case "${CROSSDESK_MACOS_NOTARY_S3_ACCELERATION:-true}" in
|
||||
true) UPLOAD_ARGS+=(--s3-acceleration) ;;
|
||||
false) UPLOAD_ARGS+=(--no-s3-acceleration) ;;
|
||||
*)
|
||||
echo "CROSSDESK_MACOS_NOTARY_S3_ACCELERATION must be true or false." >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
# Retain the submission response and log beside the installer for diagnostics
|
||||
# and for resuming a submission if Apple's service takes longer than expected.
|
||||
SUBMIT_FILE="${PACKAGE_FILE}.notary-submit.json"
|
||||
RESULT_FILE="${PACKAGE_FILE}.notary.json"
|
||||
LOG_FILE="${PACKAGE_FILE}.notary-log.json"
|
||||
SUBMIT_STATUS=0
|
||||
xcrun notarytool submit "$PACKAGE_FILE" "${NOTARY_ARGS[@]}" "${UPLOAD_ARGS[@]}" \
|
||||
--no-wait --output-format json > "$SUBMIT_FILE" || SUBMIT_STATUS=$?
|
||||
cat "$SUBMIT_FILE"
|
||||
SUBMISSION_ID="$(plutil -extract id raw -o - "$SUBMIT_FILE" 2>/dev/null || true)"
|
||||
if [[ "$SUBMIT_STATUS" -ne 0 || -z "$SUBMISSION_ID" ]]; then
|
||||
echo "Notarization upload failed; response: $SUBMIT_FILE" >&2
|
||||
echo "Check notarytool history before retrying; Apple may have allocated a submission ID." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Waiting for notarization: $SUBMISSION_ID"
|
||||
WAIT_STATUS=0
|
||||
xcrun notarytool wait "$SUBMISSION_ID" "${NOTARY_ARGS[@]}" \
|
||||
--timeout "${CROSSDESK_MACOS_NOTARY_TIMEOUT:-30m}" \
|
||||
--output-format json > "$RESULT_FILE" || WAIT_STATUS=$?
|
||||
cat "$RESULT_FILE"
|
||||
STATUS="$(plutil -extract status raw -o - "$RESULT_FILE" 2>/dev/null || true)"
|
||||
xcrun notarytool log "$SUBMISSION_ID" "${NOTARY_ARGS[@]}" "$LOG_FILE" || true
|
||||
if [[ "$WAIT_STATUS" -ne 0 || "$STATUS" != "Accepted" ]]; then
|
||||
echo "Notarization did not complete successfully (status: ${STATUS:-unknown})." >&2
|
||||
echo "Submission: ${SUBMISSION_ID:-unavailable}; response: $RESULT_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
xcrun stapler staple "$PACKAGE_FILE"
|
||||
xcrun stapler validate "$PACKAGE_FILE"
|
||||
spctl --assess --type install --verbose=2 "$PACKAGE_FILE"
|
||||
echo "Notarized and stapled: $PACKAGE_FILE"
|
||||
@@ -13,6 +13,31 @@ IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="${PROJECT_ROOT}/apps/desktop/resources/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="14.0"
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "Usage: $0 <version>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# For distribution, configure both full Developer ID certificate names.
|
||||
# Setting CROSSDESK_MACOS_NOTARY_PROFILE also notarizes and staples the pkg.
|
||||
PRODUCT_SIGN_ARGS=()
|
||||
if [[ -n "${CROSSDESK_MACOS_INSTALLER_IDENTITY:-}" || -n "${CROSSDESK_MACOS_NOTARY_PROFILE:-}" ||
|
||||
"${CROSSDESK_MACOS_SIGN_IDENTITY:--}" != "-" ]]; then
|
||||
if [[ "${CROSSDESK_MACOS_SIGN_IDENTITY:-}" != "Developer ID Application: "* ||
|
||||
"${CROSSDESK_MACOS_INSTALLER_IDENTITY:-}" != "Developer ID Installer: "* ]]; then
|
||||
echo "Signed packaging requires CROSSDESK_MACOS_SIGN_IDENTITY and CROSSDESK_MACOS_INSTALLER_IDENTITY." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${CROSSDESK_MACOS_SIGN_IDENTITY##* (}" != "${CROSSDESK_MACOS_INSTALLER_IDENTITY##* (}" ]]; then
|
||||
echo "Application and installer certificates must belong to the same team." >&2
|
||||
exit 1
|
||||
fi
|
||||
PRODUCT_SIGN_ARGS+=(--sign "$CROSSDESK_MACOS_INSTALLER_IDENTITY" --timestamp)
|
||||
if [[ -n "${CROSSDESK_MACOS_KEYCHAIN:-}" ]]; then
|
||||
PRODUCT_SIGN_ARGS+=(--keychain "$CROSSDESK_MACOS_KEYCHAIN")
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
normalize_app_version() {
|
||||
@@ -175,6 +200,7 @@ cat > build_pkg_temp/Distribution <<EOF
|
||||
EOF
|
||||
|
||||
productbuild \
|
||||
${PRODUCT_SIGN_ARGS[@]+"${PRODUCT_SIGN_ARGS[@]}"} \
|
||||
--distribution build_pkg_temp/Distribution \
|
||||
--package-path build_pkg_temp \
|
||||
--resources build_pkg_resources \
|
||||
@@ -182,6 +208,15 @@ productbuild \
|
||||
|
||||
echo "PKG package created: ${PKG_NAME}"
|
||||
|
||||
if [[ -n "${CROSSDESK_MACOS_INSTALLER_IDENTITY:-}" ]]; then
|
||||
pkgutil --check-signature "${PKG_NAME}"
|
||||
fi
|
||||
if [[ -n "${CROSSDESK_MACOS_NOTARY_PROFILE:-}" ]]; then
|
||||
bash "${SCRIPT_DIR}/notarize_pkg.sh" "${PKG_NAME}"
|
||||
elif [[ -n "${CROSSDESK_MACOS_INSTALLER_IDENTITY:-}" ]]; then
|
||||
echo "Package is signed but not notarized. Set CROSSDESK_MACOS_NOTARY_PROFILE before public distribution."
|
||||
fi
|
||||
|
||||
# Set custom icon for PKG file
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
echo "Setting custom icon for PKG file..."
|
||||
@@ -215,8 +250,7 @@ APPLESCRIPT
|
||||
fi
|
||||
echo "Set icon finished"
|
||||
|
||||
rm -rf build_pkg_temp build_pkg_scripts build_pkg_resources ${APP_BUNDLE}
|
||||
rm -rf build_pkg_temp build_pkg_scripts build_pkg_resources "${APP_BUNDLE}"
|
||||
|
||||
echo "PKG package created successfully."
|
||||
echo "package ${APP_BUNDLE}"
|
||||
echo "installer ${PKG_NAME}"
|
||||
|
||||
@@ -13,6 +13,31 @@ IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="${PROJECT_ROOT}/apps/desktop/resources/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="14.0"
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "Usage: $0 <version>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# For distribution, configure both full Developer ID certificate names.
|
||||
# Setting CROSSDESK_MACOS_NOTARY_PROFILE also notarizes and staples the pkg.
|
||||
PRODUCT_SIGN_ARGS=()
|
||||
if [[ -n "${CROSSDESK_MACOS_INSTALLER_IDENTITY:-}" || -n "${CROSSDESK_MACOS_NOTARY_PROFILE:-}" ||
|
||||
"${CROSSDESK_MACOS_SIGN_IDENTITY:--}" != "-" ]]; then
|
||||
if [[ "${CROSSDESK_MACOS_SIGN_IDENTITY:-}" != "Developer ID Application: "* ||
|
||||
"${CROSSDESK_MACOS_INSTALLER_IDENTITY:-}" != "Developer ID Installer: "* ]]; then
|
||||
echo "Signed packaging requires CROSSDESK_MACOS_SIGN_IDENTITY and CROSSDESK_MACOS_INSTALLER_IDENTITY." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${CROSSDESK_MACOS_SIGN_IDENTITY##* (}" != "${CROSSDESK_MACOS_INSTALLER_IDENTITY##* (}" ]]; then
|
||||
echo "Application and installer certificates must belong to the same team." >&2
|
||||
exit 1
|
||||
fi
|
||||
PRODUCT_SIGN_ARGS+=(--sign "$CROSSDESK_MACOS_INSTALLER_IDENTITY" --timestamp)
|
||||
if [[ -n "${CROSSDESK_MACOS_KEYCHAIN:-}" ]]; then
|
||||
PRODUCT_SIGN_ARGS+=(--keychain "$CROSSDESK_MACOS_KEYCHAIN")
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
normalize_app_version() {
|
||||
@@ -175,6 +200,7 @@ cat > build_pkg_temp/Distribution <<EOF
|
||||
EOF
|
||||
|
||||
productbuild \
|
||||
${PRODUCT_SIGN_ARGS[@]+"${PRODUCT_SIGN_ARGS[@]}"} \
|
||||
--distribution build_pkg_temp/Distribution \
|
||||
--package-path build_pkg_temp \
|
||||
--resources build_pkg_resources \
|
||||
@@ -182,6 +208,15 @@ productbuild \
|
||||
|
||||
echo "PKG package created: ${PKG_NAME}"
|
||||
|
||||
if [[ -n "${CROSSDESK_MACOS_INSTALLER_IDENTITY:-}" ]]; then
|
||||
pkgutil --check-signature "${PKG_NAME}"
|
||||
fi
|
||||
if [[ -n "${CROSSDESK_MACOS_NOTARY_PROFILE:-}" ]]; then
|
||||
bash "${SCRIPT_DIR}/notarize_pkg.sh" "${PKG_NAME}"
|
||||
elif [[ -n "${CROSSDESK_MACOS_INSTALLER_IDENTITY:-}" ]]; then
|
||||
echo "Package is signed but not notarized. Set CROSSDESK_MACOS_NOTARY_PROFILE before public distribution."
|
||||
fi
|
||||
|
||||
# Set custom icon for PKG file
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
echo "Setting custom icon for PKG file..."
|
||||
@@ -215,8 +250,7 @@ APPLESCRIPT
|
||||
fi
|
||||
echo "Set icon finished"
|
||||
|
||||
rm -rf build_pkg_temp build_pkg_scripts build_pkg_resources ${APP_BUNDLE}
|
||||
rm -rf build_pkg_temp build_pkg_scripts build_pkg_resources "${APP_BUNDLE}"
|
||||
|
||||
echo "PKG package created successfully."
|
||||
echo "package ${APP_BUNDLE}"
|
||||
echo "installer ${PKG_NAME}"
|
||||
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "Usage: $0 <app-bundle>" >&2
|
||||
echo "Set CROSSDESK_MACOS_SIGN_IDENTITY to a full Developer ID Application name." >&2
|
||||
echo "Without an identity, the bundle receives an ad-hoc signature for local use." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
APP_BUNDLE="$1"
|
||||
SIGN_IDENTITY="${CROSSDESK_MACOS_SIGN_IDENTITY:--}"
|
||||
SIGN_ARGS=(--force --sign "$SIGN_IDENTITY")
|
||||
APP_ARGS=()
|
||||
|
||||
case "$SIGN_IDENTITY" in
|
||||
-) ;;
|
||||
"Developer ID Application: "*)
|
||||
SIGN_ARGS+=(--options runtime --timestamp)
|
||||
APP_ARGS+=(--entitlements "$SCRIPT_DIR/entitlements.plist")
|
||||
;;
|
||||
*)
|
||||
echo "CROSSDESK_MACOS_SIGN_IDENTITY must be a full Developer ID Application name or '-'." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n "${CROSSDESK_MACOS_KEYCHAIN:-}" ]]; then
|
||||
SIGN_ARGS+=(--keychain "$CROSSDESK_MACOS_KEYCHAIN")
|
||||
fi
|
||||
|
||||
for executable in CrossDesk crossdesk_updater; do
|
||||
if [[ ! -x "$APP_BUNDLE/Contents/MacOS/$executable" ]]; then
|
||||
echo "Missing app executable: $APP_BUNDLE/Contents/MacOS/$executable" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! -d "$APP_BUNDLE/Contents/Frameworks" ]]; then
|
||||
echo "Missing bundled frameworks: $APP_BUNDLE/Contents/Frameworks" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sign nested code explicitly, from the inside out. The updater also runs
|
||||
# outside the bundle, and must retain its own valid Developer ID signature.
|
||||
while IFS= read -r -d '' library; do
|
||||
codesign "${SIGN_ARGS[@]}" "$library"
|
||||
done < <(find "$APP_BUNDLE/Contents/Frameworks" -type f -name '*.dylib' -print0)
|
||||
codesign "${SIGN_ARGS[@]}" "$APP_BUNDLE/Contents/MacOS/crossdesk_updater"
|
||||
codesign "${SIGN_ARGS[@]}" ${APP_ARGS[@]+"${APP_ARGS[@]}"} "$APP_BUNDLE"
|
||||
codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE"
|
||||
|
||||
echo "App signed with: $SIGN_IDENTITY"
|
||||
@@ -127,4 +127,43 @@ fi
|
||||
|
||||
codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE"
|
||||
|
||||
# A distribution build must not silently fall back to an ad-hoc/development
|
||||
# identity, omit a secure timestamp, or leave its updater/runtime unsigned.
|
||||
APP_SIGNATURE="$(codesign --display --verbose=4 "$APP_BUNDLE" 2>&1)"
|
||||
if [[ "${CROSSDESK_MACOS_SIGN_IDENTITY:--}" != "-" ||
|
||||
"$APP_SIGNATURE" == *"Authority=Developer ID Application: "* ]]; then
|
||||
EXPECTED_TEAM="$(printf '%s\n' "$APP_SIGNATURE" | sed -n 's/^TeamIdentifier=//p')"
|
||||
verify_distribution_signature() {
|
||||
local target="$1"
|
||||
local signature=""
|
||||
codesign --verify --strict --verbose=2 \
|
||||
-R='anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists' \
|
||||
"$target"
|
||||
signature="$(codesign --display --verbose=4 "$target" 2>&1)"
|
||||
if ! printf '%s\n' "$signature" | grep -q '^CodeDirectory .*flags=.*runtime'; then
|
||||
echo "Hardened Runtime is missing: $target" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! printf '%s\n' "$signature" | grep -q '^Timestamp='; then
|
||||
echo "Secure signing timestamp is missing: $target" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! printf '%s\n' "$signature" | grep -Fxq "TeamIdentifier=$EXPECTED_TEAM"; then
|
||||
echo "Signing team mismatch: $target" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ "${CROSSDESK_MACOS_SIGN_IDENTITY:--}" != "-" ]] &&
|
||||
! printf '%s\n' "$signature" | grep -Fxq "Authority=$CROSSDESK_MACOS_SIGN_IDENTITY"; then
|
||||
echo "Unexpected signing identity: $target" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_distribution_signature "$APP_BUNDLE"
|
||||
verify_distribution_signature "$UPDATER"
|
||||
while IFS= read -r -d '' library; do
|
||||
verify_distribution_signature "$library"
|
||||
done < <(find "$FRAMEWORKS_DIR" -type f -name '*.dylib' -print0)
|
||||
fi
|
||||
|
||||
echo "Verified $APP_BUNDLE ($EXPECTED_ARCH, macOS $BINARY_MIN_VERSION+)"
|
||||
|
||||
Reference in New Issue
Block a user