Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions .github/workflows/prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -727,16 +727,23 @@ 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:-<unset>}'"
# 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

VERSION=${GITHUB_REF#refs/tags/}
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 }}" \
Expand All @@ -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}"
Expand Down
25 changes: 16 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1028,17 +1028,24 @@ 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:-<unset>}'"
# 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

VERSION=${GITHUB_REF#refs/tags/}
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}" \
Expand All @@ -1057,20 +1064,20 @@ 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
# becomes a release asset — a broken seal only shows up as a crash on
# 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}"

Expand Down
14 changes: 14 additions & 0 deletions scripts/create-dmg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Loading