diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7f78da8a..9373bd56d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -350,6 +350,7 @@ jobs: -derivedDataPath apps/macos/.derivedData-ci \ ARCHS=arm64 \ ONLY_ACTIVE_ARCH=YES \ + CODE_SIGNING_ALLOWED=NO \ build ios-app: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86136b813..8e84e0132 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -855,6 +855,33 @@ jobs: if: ${{ env.RELEASE_DRY_RUN != 'true' }} uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1 + - name: Import Apple certificate + if: ${{ env.RELEASE_DRY_RUN != 'true' }} + env: + APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + run: | + CERTIFICATE_PATH="$RUNNER_TEMP/certificate.p12" + KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db" + KEYCHAIN_PASSWORD="$(openssl rand -hex 16)" + + echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH" + + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + + security import "$CERTIFICATE_PATH" \ + -P "$APPLE_CERTIFICATE_PASSWORD" \ + -A -t cert -f pkcs12 \ + -k "$KEYCHAIN_PATH" + + security set-key-partition-list \ + -S apple-tool:,apple: \ + -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + + security list-keychain -d user -s "$KEYCHAIN_PATH" + - name: Determine package version id: version run: | @@ -864,7 +891,6 @@ jobs: VERSION="0.0.0-dev" fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" - - name: Build Swift bridge artifacts and generate Xcode project env: MOLTIS_VERSION: ${{ steps.version.outputs.version }} @@ -875,6 +901,7 @@ jobs: - name: Build macOS app env: DERIVED_DATA_DIR: apps/macos/.derivedData-ci + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} run: | xcodebuild \ -project apps/macos/Moltis.xcodeproj \ @@ -882,6 +909,10 @@ jobs: -configuration Release \ -destination "platform=macOS" \ -derivedDataPath "$DERIVED_DATA_DIR" \ + CODE_SIGN_STYLE=Manual \ + CODE_SIGN_IDENTITY="Developer ID Application" \ + DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \ + OTHER_CODE_SIGN_FLAGS="--options runtime" \ build - name: Package macOS app @@ -895,6 +926,28 @@ jobs: fi ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "moltis-${VERSION}-macos.app.zip" + - name: Notarize macOS app + if: ${{ env.RELEASE_DRY_RUN != 'true' }} + env: + VERSION: ${{ steps.version.outputs.version }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: | + xcrun notarytool submit "moltis-${VERSION}-macos.app.zip" \ + --apple-id "$APPLE_ID" \ + --password "$APPLE_ID_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" \ + --wait + + # Staple the notarization ticket to the app bundle + APP_PATH="apps/macos/.derivedData-ci/Build/Products/Release/Moltis.app" + xcrun stapler staple "$APP_PATH" + + # Re-package with the stapled ticket + rm "moltis-${VERSION}-macos.app.zip" + ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "moltis-${VERSION}-macos.app.zip" + - name: Sign with Sigstore and generate checksums if: ${{ env.RELEASE_DRY_RUN != 'true' }} uses: ./.github/actions/sign-artifacts @@ -913,6 +966,14 @@ jobs: *.app.zip.sig *.app.zip.crt + - name: Clean up keychain + if: always() + run: | + KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db" + if [ -f "$KEYCHAIN_PATH" ]; then + security delete-keychain "$KEYCHAIN_PATH" + fi + build-windows-exe: needs: [clippy, test, e2e] runs-on: windows-latest diff --git a/CLAUDE.md b/CLAUDE.md index 0bd36c00c..f217f2af4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -242,7 +242,7 @@ Exact commands (must match `local-validate.sh`): - Fmt: `cargo +nightly-2025-11-30 fmt --all -- --check` - Clippy: `cargo +nightly-2025-11-30 clippy -Z unstable-options --workspace --all-features --all-targets --timings -- -D warnings` - macOS without `nvcc`: clippy without `--all-features` -- macOS app (Darwin hosts): `./scripts/build-swift-bridge.sh && ./scripts/generate-swift-project.sh && ./scripts/lint-swift.sh && xcodebuild -project apps/macos/Moltis.xcodeproj -scheme Moltis -configuration Release -destination "platform=macOS" -derivedDataPath apps/macos/.derivedData-local-validate build` +- macOS app (Darwin hosts): `./scripts/build-swift-bridge.sh && ./scripts/generate-swift-project.sh && ./scripts/lint-swift.sh && xcodebuild -project apps/macos/Moltis.xcodeproj -scheme Moltis -configuration Release -destination "platform=macOS" -derivedDataPath apps/macos/.derivedData-local-validate CODE_SIGNING_ALLOWED=NO build` - iOS app (Darwin hosts): `cargo run -p moltis-schema-export -- apps/ios/GraphQL/Schema/schema.graphqls && ./scripts/generate-ios-graphql.sh && ./scripts/generate-ios-project.sh && xcodebuild -project apps/ios/Moltis.xcodeproj -scheme Moltis -configuration Debug -destination "generic/platform=iOS" CODE_SIGNING_ALLOWED=NO build` ### PR Descriptions diff --git a/apps/macos/Moltis.entitlements b/apps/macos/Moltis.entitlements new file mode 100644 index 000000000..0c67376eb --- /dev/null +++ b/apps/macos/Moltis.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/macos/project.yml b/apps/macos/project.yml index b08421f62..46db2a28b 100644 --- a/apps/macos/project.yml +++ b/apps/macos/project.yml @@ -28,6 +28,8 @@ targets: sources: - path: Sources - path: Assets.xcassets + entitlements: + path: Moltis.entitlements settings: base: PRODUCT_BUNDLE_IDENTIFIER: org.moltis.app @@ -35,6 +37,9 @@ targets: SWIFT_EMIT_LOC_STRINGS: NO ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon SWIFT_OBJC_BRIDGING_HEADER: Sources/Bridging-Header.h + ENABLE_HARDENED_RUNTIME: YES + CODE_SIGN_STYLE: Manual + CODE_SIGN_IDENTITY: "Developer ID Application" OTHER_LDFLAGS: - "-lmoltis_bridge" - "-lsqlite3" diff --git a/scripts/local-validate.sh b/scripts/local-validate.sh index 5c51ede0a..67f31d88f 100755 --- a/scripts/local-validate.sh +++ b/scripts/local-validate.sh @@ -179,7 +179,7 @@ lint_cmd="${LOCAL_VALIDATE_LINT_CMD:-cargo +${nightly_toolchain} clippy -Z unsta test_cmd="${LOCAL_VALIDATE_TEST_CMD:-cargo +${nightly_toolchain} nextest run --all-features --profile ci}" e2e_cmd="${LOCAL_VALIDATE_E2E_CMD:-cd crates/web/ui && if [ ! -d node_modules ]; then npm ci; fi && npm run e2e:install && npm run e2e}" coverage_cmd="${LOCAL_VALIDATE_COVERAGE_CMD:-cargo +${nightly_toolchain} llvm-cov --workspace --all-features --html}" -macos_app_cmd="${LOCAL_VALIDATE_MACOS_APP_CMD:-./scripts/build-swift-bridge.sh && ./scripts/generate-swift-project.sh && ./scripts/lint-swift.sh && xcodebuild -project apps/macos/Moltis.xcodeproj -scheme Moltis -configuration Release -destination \"platform=macOS\" -derivedDataPath apps/macos/.derivedData-local-validate build}" +macos_app_cmd="${LOCAL_VALIDATE_MACOS_APP_CMD:-./scripts/build-swift-bridge.sh && ./scripts/generate-swift-project.sh && ./scripts/lint-swift.sh && xcodebuild -project apps/macos/Moltis.xcodeproj -scheme Moltis -configuration Release -destination \"platform=macOS\" -derivedDataPath apps/macos/.derivedData-local-validate CODE_SIGNING_ALLOWED=NO build}" ios_app_cmd="${LOCAL_VALIDATE_IOS_APP_CMD:-cargo run -p moltis-schema-export -- apps/ios/GraphQL/Schema/schema.graphqls && ./scripts/generate-ios-graphql.sh && ./scripts/generate-ios-project.sh && xcodebuild -project apps/ios/Moltis.xcodeproj -scheme Moltis -configuration Debug -destination \"generic/platform=iOS\" CODE_SIGNING_ALLOWED=NO build}" build_cmd="${LOCAL_VALIDATE_BUILD_CMD:-cargo +${nightly_toolchain} build --workspace --all-features --all-targets}"