diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 16ceaea2..f3a3f422 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -727,8 +727,15 @@ jobs: exit 0 fi - if [ -z "${SWIFT_APP_PATH:-}" ] || [ ! -d "${SWIFT_APP_PATH}" ]; then - echo "❌ SWIFT_APP_PATH is not a bundle: '${SWIFT_APP_PATH:-}'" + # The enclosure must ship the SAME bundle users run: the DMG staging + # copy that create-dmg.sh signed (core + certs + Developer ID deep + # signature) and exported. SWIFT_APP_PATH is the raw swift-build + # output — no core, not notarization-signed — and must never be + # zipped as an update (it failed notarization and would install a + # coreless app). + APP_SRC="signed-app-${{ matrix.goarch }}/mcpproxy.app" + if [ ! -d "${APP_SRC}" ]; then + echo "❌ signed bundle export missing: ${APP_SRC} (create-dmg.sh exports it)" exit 1 fi @@ -736,7 +743,7 @@ jobs: ENCLOSURE="mcpproxy-${VERSION#v}-darwin-${{ matrix.goarch }}.app.zip" NOTARIZE_ZIP="sparkle-notarize-${{ matrix.goarch }}.zip" - ditto -c -k --sequesterRsrc --keepParent "${SWIFT_APP_PATH}" "${NOTARIZE_ZIP}" + ditto -c -k --sequesterRsrc --keepParent "${APP_SRC}" "${NOTARIZE_ZIP}" SUBMISSION_OUTPUT=$(xcrun notarytool submit "${NOTARIZE_ZIP}" \ --apple-id "${{ secrets.APPLE_ID_USERNAME }}" \ @@ -751,16 +758,16 @@ jobs: exit 1 fi - xcrun stapler staple "${SWIFT_APP_PATH}" - xcrun stapler validate "${SWIFT_APP_PATH}" + xcrun stapler staple "${APP_SRC}" + xcrun stapler validate "${APP_SRC}" - ditto -c -k --sequesterRsrc --keepParent "${SWIFT_APP_PATH}" "sparkle-enclosure/${ENCLOSURE}" + ditto -c -k --sequesterRsrc --keepParent "${APP_SRC}" "sparkle-enclosure/${ENCLOSURE}" rm -f "${NOTARIZE_ZIP}" VERIFY_DIR=$(mktemp -d) ditto -x -k "sparkle-enclosure/${ENCLOSURE}" "${VERIFY_DIR}" - codesign --verify --deep --strict --verbose=2 "${VERIFY_DIR}/MCPProxy.app" - xcrun stapler validate "${VERIFY_DIR}/MCPProxy.app" + codesign --verify --deep --strict --verbose=2 "${VERIFY_DIR}/mcpproxy.app" + xcrun stapler validate "${VERIFY_DIR}/mcpproxy.app" rm -rf "${VERIFY_DIR}" echo "✅ Sparkle enclosure ready: ${ENCLOSURE}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5cc8fed5..18b70e21 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1028,8 +1028,15 @@ jobs: exit 0 fi - if [ -z "${SWIFT_APP_PATH:-}" ] || [ ! -d "${SWIFT_APP_PATH}" ]; then - echo "❌ SWIFT_APP_PATH is not a bundle: '${SWIFT_APP_PATH:-}'" + # The enclosure must ship the SAME bundle users run: the DMG staging + # copy that create-dmg.sh signed (core + certs + Developer ID deep + # signature) and exported. SWIFT_APP_PATH is the raw swift-build + # output — no core, not notarization-signed — and must never be + # zipped as an update (it failed notarization and would install a + # coreless app). + APP_SRC="signed-app-${{ matrix.goarch }}/mcpproxy.app" + if [ ! -d "${APP_SRC}" ]; then + echo "❌ signed bundle export missing: ${APP_SRC} (create-dmg.sh exports it)" exit 1 fi @@ -1037,8 +1044,8 @@ jobs: ENCLOSURE="mcpproxy-${VERSION#v}-darwin-${{ matrix.goarch }}.app.zip" NOTARIZE_ZIP="sparkle-notarize-${{ matrix.goarch }}.zip" - echo "=== Archiving ${SWIFT_APP_PATH} for notarization ===" - ditto -c -k --sequesterRsrc --keepParent "${SWIFT_APP_PATH}" "${NOTARIZE_ZIP}" + echo "=== Archiving ${APP_SRC} for notarization ===" + ditto -c -k --sequesterRsrc --keepParent "${APP_SRC}" "${NOTARIZE_ZIP}" echo "=== Submitting the app bundle for notarization ===" SUBMISSION_OUTPUT=$(xcrun notarytool submit "${NOTARIZE_ZIP}" \ @@ -1057,11 +1064,11 @@ jobs: # Staple the BUNDLE, then re-archive it: the ticket must travel inside # the .app the user ends up running. - xcrun stapler staple "${SWIFT_APP_PATH}" - xcrun stapler validate "${SWIFT_APP_PATH}" + xcrun stapler staple "${APP_SRC}" + xcrun stapler validate "${APP_SRC}" echo "=== Creating the stapled enclosure ===" - ditto -c -k --sequesterRsrc --keepParent "${SWIFT_APP_PATH}" "sparkle-enclosure/${ENCLOSURE}" + ditto -c -k --sequesterRsrc --keepParent "${APP_SRC}" "sparkle-enclosure/${ENCLOSURE}" rm -f "${NOTARIZE_ZIP}" # Prove the archive round-trips with its signature intact before it @@ -1069,8 +1076,8 @@ jobs: # the user's machine otherwise. VERIFY_DIR=$(mktemp -d) ditto -x -k "sparkle-enclosure/${ENCLOSURE}" "${VERIFY_DIR}" - codesign --verify --deep --strict --verbose=2 "${VERIFY_DIR}/MCPProxy.app" - xcrun stapler validate "${VERIFY_DIR}/MCPProxy.app" + codesign --verify --deep --strict --verbose=2 "${VERIFY_DIR}/mcpproxy.app" + xcrun stapler validate "${VERIFY_DIR}/mcpproxy.app" spctl -a -t exec -vv "${VERIFY_DIR}/MCPProxy.app" rm -rf "${VERIFY_DIR}" diff --git a/scripts/create-dmg.sh b/scripts/create-dmg.sh index 76180425..86c98b87 100755 --- a/scripts/create-dmg.sh +++ b/scripts/create-dmg.sh @@ -247,6 +247,20 @@ else codesign --force --deep --sign - --identifier "$BUNDLE_ID" "$TEMP_DIR/$APP_BUNDLE" fi +# Export the fully staged, fully signed bundle for the Sparkle enclosure +# (Spec 092). SWIFT_APP_PATH is only the raw swift-build output: it has no +# core binary, no certs, and not this final Developer ID signature — zipping +# it produced an enclosure that failed notarization AND would have installed +# an app without a core. The DMG staging copy here is the ONLY bundle in the +# pipeline that is byte-for-byte what users run, so it is what updates must +# ship. Symlink-free ditto copy preserves the signature. +SIGNED_APP_EXPORT="signed-app-${ARCH}" +rm -rf "${SIGNED_APP_EXPORT}" +mkdir -p "${SIGNED_APP_EXPORT}" +ditto "$TEMP_DIR/$APP_BUNDLE" "${SIGNED_APP_EXPORT}/${APP_BUNDLE}" +codesign --verify --deep --strict "${SIGNED_APP_EXPORT}/${APP_BUNDLE}" +echo "✅ Signed bundle exported for Sparkle enclosure: ${SIGNED_APP_EXPORT}/${APP_BUNDLE}" + # Create Applications symlink ln -s /Applications "$TEMP_DIR/Applications"