mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Merge pull request #1267 from perfectra1n/develop
Enable MacOS code signing and notarization in GitHub Actions
This commit is contained in:
		
							
								
								
									
										189
									
								
								.github/actions/build-electron/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										189
									
								
								.github/actions/build-electron/action.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,6 @@ | ||||
| name: "Build Electron App" | ||||
| description: "Builds and packages the Electron app for different platforms" | ||||
|  | ||||
| inputs: | ||||
|   os: | ||||
|     description: "One of the supported platforms: macos, linux, windows" | ||||
| @@ -8,13 +11,45 @@ inputs: | ||||
|   extension: | ||||
|     description: "Platform specific extensions to copy in the output: dmg, deb, rpm, exe, zip" | ||||
|     required: true | ||||
|  | ||||
| runs: | ||||
|   using: composite | ||||
|   steps: | ||||
|     - name: Set up Python for appdmg to be installed | ||||
|     # Certificate setup | ||||
|     - name: Import Apple certificates | ||||
|       if: inputs.os == 'macos' | ||||
|       uses: apple-actions/import-codesign-certs@v2 | ||||
|       with: | ||||
|         p12-file-base64: ${{ env.APPLE_APP_CERTIFICATE_BASE64 }} | ||||
|         p12-password: ${{ env.APPLE_APP_CERTIFICATE_PASSWORD }} | ||||
|         keychain: build | ||||
|         keychain-password: ${{ github.run_id }} | ||||
|  | ||||
|     - name: Install Installer certificate | ||||
|       if: inputs.os == 'macos' | ||||
|       uses: apple-actions/import-codesign-certs@v2 | ||||
|       with: | ||||
|         p12-file-base64: ${{ env.APPLE_INSTALLER_CERTIFICATE_BASE64 }} | ||||
|         p12-password: ${{ env.APPLE_INSTALLER_CERTIFICATE_PASSWORD }} | ||||
|         keychain: build | ||||
|         keychain-password: ${{ github.run_id }} | ||||
|         # We don't need to create a keychain here because we're using the build keychain that was created in the previous step | ||||
|         create-keychain: false | ||||
|  | ||||
|     - name: Verify certificates | ||||
|       if: inputs.os == 'macos' | ||||
|       shell: bash | ||||
|       run: | | ||||
|         echo "Available signing identities:" | ||||
|         security find-identity -v -p codesigning build.keychain | ||||
|  | ||||
|     - name: Set up Python and other macOS dependencies | ||||
|       if: ${{ inputs.os == 'macos' }} | ||||
|       shell: bash | ||||
|       run: brew install python-setuptools | ||||
|       run: | | ||||
|         brew install python-setuptools | ||||
|         brew install create-dmg | ||||
|  | ||||
|     - name: Install dependencies for RPM and Flatpak package building | ||||
|       if: ${{ inputs.os == 'linux' }} | ||||
|       shell: bash | ||||
| @@ -24,21 +59,155 @@ runs: | ||||
|         FLATPAK_ARCH=$(if [[ ${{ inputs.arch }} = 'arm64' ]]; then echo 'aarch64'; else echo 'x86_64'; fi) | ||||
|         FLATPAK_VERSION='24.08' | ||||
|         flatpak install --user --no-deps --arch $FLATPAK_ARCH --assumeyes runtime/org.freedesktop.Platform/$FLATPAK_ARCH/$FLATPAK_VERSION runtime/org.freedesktop.Sdk/$FLATPAK_ARCH/$FLATPAK_VERSION org.electronjs.Electron2.BaseApp/$FLATPAK_ARCH/$FLATPAK_VERSION | ||||
|  | ||||
|     # Build setup | ||||
|     - name: Install dependencies | ||||
|       shell: bash | ||||
|       run: npm ci | ||||
|  | ||||
|     - name: Update build info | ||||
|       shell: bash | ||||
|       run: npm run chore:update-build-info | ||||
|     - name: Run electron-forge | ||||
|  | ||||
|     # Critical debugging configuration | ||||
|     - name: Run electron-forge build with enhanced logging | ||||
|       shell: bash | ||||
|       run: npm run electron-forge:make -- --arch=${{ inputs.arch }} | ||||
|       env: | ||||
|         # Pass through required environment variables for signing and notarization | ||||
|         APPLE_TEAM_ID: ${{ env.APPLE_TEAM_ID }} | ||||
|         APPLE_ID: ${{ env.APPLE_ID }} | ||||
|         APPLE_ID_PASSWORD: ${{ env.APPLE_ID_PASSWORD }} | ||||
|       run: | | ||||
|         # Map OS names to Electron Forge platform names | ||||
|         if [ "${{ inputs.os }}" = "macos" ]; then | ||||
|           PLATFORM="darwin" | ||||
|         elif [ "${{ inputs.os }}" = "windows" ]; then | ||||
|           PLATFORM="win32" | ||||
|         else | ||||
|           PLATFORM="${{ inputs.os }}" | ||||
|         fi | ||||
|  | ||||
|         npm run electron-forge:make -- \ | ||||
|           --arch=${{ inputs.arch }} \ | ||||
|           --platform=$PLATFORM | ||||
|  | ||||
|     # Add DMG signing step | ||||
|     - name: Sign DMG | ||||
|       if: inputs.os == 'macos' | ||||
|       shell: bash | ||||
|       run: | | ||||
|         echo "Signing DMG file..." | ||||
|         dmg_file=$(find out -name "*.dmg" -print -quit) | ||||
|         if [ -n "$dmg_file" ]; then | ||||
|           echo "Found DMG: $dmg_file" | ||||
|           # Get the first valid signing identity from the keychain | ||||
|           SIGNING_IDENTITY=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application" | head -1 | sed -E 's/.*"([^"]+)".*/\1/') | ||||
|           if [ -z "$SIGNING_IDENTITY" ]; then | ||||
|             echo "Error: No valid Developer ID Application certificate found in keychain" | ||||
|             exit 1 | ||||
|           fi | ||||
|           echo "Using signing identity: $SIGNING_IDENTITY" | ||||
|           # Sign the DMG | ||||
|           codesign --force --sign "$SIGNING_IDENTITY" --options runtime --timestamp "$dmg_file" | ||||
|           # Notarize the DMG | ||||
|           xcrun notarytool submit "$dmg_file" --apple-id "$APPLE_ID" --password "$APPLE_ID_PASSWORD" --team-id "$APPLE_TEAM_ID" --wait | ||||
|           # Staple the notarization ticket | ||||
|           xcrun stapler staple "$dmg_file" | ||||
|         else | ||||
|           echo "No DMG found to sign" | ||||
|         fi | ||||
|  | ||||
|     - name: Verify code signing | ||||
|       if: inputs.os == 'macos' | ||||
|       shell: bash | ||||
|       run: | | ||||
|         echo "Verifying code signing for all artifacts..." | ||||
|  | ||||
|         # First check the .app bundle | ||||
|         echo "Looking for .app bundle..." | ||||
|         app_bundle=$(find out -name "*.app" -print -quit) | ||||
|         if [ -n "$app_bundle" ]; then | ||||
|           echo "Found app bundle: $app_bundle" | ||||
|           echo "Verifying app bundle signing..." | ||||
|           codesign --verify --deep --strict --verbose=2 "$app_bundle" | ||||
|           echo "Displaying app bundle signing info..." | ||||
|           codesign --display --verbose=2 "$app_bundle" | ||||
|  | ||||
|           echo "Checking entitlements..." | ||||
|           codesign --display --entitlements :- "$app_bundle" | ||||
|  | ||||
|           echo "Checking notarization status..." | ||||
|           xcrun stapler validate "$app_bundle" || echo "Warning: App bundle not notarized yet" | ||||
|         else | ||||
|           echo "No .app bundle found to verify" | ||||
|         fi | ||||
|  | ||||
|         # Then check DMG if it exists | ||||
|         echo "Looking for DMG..." | ||||
|         dmg_file=$(find out -name "*.dmg" -print -quit) | ||||
|         if [ -n "$dmg_file" ]; then | ||||
|           echo "Found DMG: $dmg_file" | ||||
|           echo "Verifying DMG signing..." | ||||
|           codesign --verify --deep --strict --verbose=2 "$dmg_file" | ||||
|           echo "Displaying DMG signing info..." | ||||
|           codesign --display --verbose=2 "$dmg_file" | ||||
|  | ||||
|           echo "Checking DMG notarization..." | ||||
|           xcrun stapler validate "$dmg_file" || echo "Warning: DMG not notarized yet" | ||||
|         else | ||||
|           echo "No DMG found to verify" | ||||
|         fi | ||||
|  | ||||
|         # Finally check ZIP if it exists | ||||
|         echo "Looking for ZIP..." | ||||
|         zip_file=$(find out -name "*.zip" -print -quit) | ||||
|         if [ -n "$zip_file" ]; then | ||||
|           echo "Found ZIP: $zip_file" | ||||
|           echo "Note: ZIP files are not code signed, but their contents should be" | ||||
|         fi | ||||
|  | ||||
|     - name: Prepare artifacts | ||||
|       shell: bash | ||||
|       run: | | ||||
|         mkdir -p upload; | ||||
|         for ext in ${{ inputs.extension }}; | ||||
|         do | ||||
|           file=$(find out/make -name "*.$ext" -print -quit); | ||||
|           cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}.$ext"; | ||||
|         done | ||||
|         mkdir -p upload | ||||
|  | ||||
|         if [ "${{ inputs.os }}" = "macos" ]; then | ||||
|           # For macOS, we need to look in specific directories based on the maker | ||||
|           echo "Collecting macOS artifacts..." | ||||
|  | ||||
|           # Look for DMG files recursively | ||||
|           echo "Looking for DMG files..." | ||||
|           dmg_file=$(find out -name "*.dmg" -print -quit) | ||||
|           if [ -n "$dmg_file" ]; then | ||||
|             echo "Found DMG: $dmg_file" | ||||
|             cp "$dmg_file" "upload/TriliumNextNotes-${{ github.ref_name }}-darwin-${{ inputs.arch }}.dmg" | ||||
|           else | ||||
|             echo "Warning: No DMG file found" | ||||
|           fi | ||||
|  | ||||
|           # Look for ZIP files recursively | ||||
|           echo "Looking for ZIP files..." | ||||
|           zip_file=$(find out -name "*.zip" -print -quit) | ||||
|           if [ -n "$zip_file" ]; then | ||||
|             echo "Found ZIP: $zip_file" | ||||
|             cp "$zip_file" "upload/TriliumNextNotes-${{ github.ref_name }}-darwin-${{ inputs.arch }}.zip" | ||||
|           else | ||||
|             echo "Warning: No ZIP file found" | ||||
|           fi | ||||
|         else | ||||
|           # For other platforms, use the existing logic but with better error handling | ||||
|           echo "Collecting artifacts for ${{ inputs.os }}..." | ||||
|           for ext in ${{ inputs.extension }}; do | ||||
|             echo "Looking for .$ext files..." | ||||
|             file=$(find out -name "*.$ext" -print -quit) | ||||
|             if [ -n "$file" ]; then | ||||
|               echo "Found $file for extension $ext" | ||||
|               cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}.$ext" | ||||
|             else | ||||
|               echo "Warning: No file found with extension .$ext" | ||||
|             fi | ||||
|           done | ||||
|         fi | ||||
|  | ||||
|         echo "Final contents of upload directory:" | ||||
|         ls -la upload/ | ||||
|   | ||||
							
								
								
									
										41
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										41
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							| @@ -33,6 +33,36 @@ jobs: | ||||
|     runs-on: ${{ matrix.os.image }} | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|  | ||||
|       # Set up certificates and keychain for macOS | ||||
|       - name: Install Apple Certificates | ||||
|         if: matrix.os.name == 'macos' | ||||
|         env: | ||||
|           APP_CERTIFICATE_BASE64: ${{ secrets.APPLE_APP_CERTIFICATE_BASE64 }} | ||||
|           APP_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_APP_CERTIFICATE_PASSWORD }} | ||||
|           INSTALLER_CERTIFICATE_BASE64: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_BASE64 }} | ||||
|           INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_PASSWORD }} | ||||
|           KEYCHAIN_PASSWORD: ${{ github.run_id }} | ||||
|         run: | | ||||
|           # Create keychain | ||||
|           security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain | ||||
|           security default-keychain -s build.keychain | ||||
|           security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain | ||||
|           security set-keychain-settings -t 3600 -u build.keychain | ||||
|  | ||||
|           # Import application certificate | ||||
|           echo "$APP_CERTIFICATE_BASE64" | base64 --decode > application.p12 | ||||
|           security import application.p12 -k build.keychain -P "$APP_CERTIFICATE_PASSWORD" -T /usr/bin/codesign | ||||
|           rm application.p12 | ||||
|  | ||||
|           # Import installer certificate | ||||
|           echo "$INSTALLER_CERTIFICATE_BASE64" | base64 --decode > installer.p12 | ||||
|           security import installer.p12 -k build.keychain -P "$INSTALLER_CERTIFICATE_PASSWORD" -T /usr/bin/codesign | ||||
|           rm installer.p12 | ||||
|  | ||||
|           # Update keychain settings | ||||
|           security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain | ||||
|  | ||||
|       - name: Set up node & dependencies | ||||
|         uses: actions/setup-node@v4 | ||||
|         with: | ||||
| @@ -43,6 +73,17 @@ jobs: | ||||
|           os: ${{ matrix.os.name }} | ||||
|           arch: ${{ matrix.arch }} | ||||
|           extension: ${{ matrix.os.extension }} | ||||
|         env: | ||||
|           APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | ||||
|           APPLE_ID: ${{ secrets.APPLE_ID }} | ||||
|           APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} | ||||
|  | ||||
|       # Clean up keychain after build | ||||
|       - name: Clean up keychain | ||||
|         if: matrix.os.name == 'macos' && always() | ||||
|         run: | | ||||
|           security delete-keychain build.keychain | ||||
|  | ||||
|       - name: Publish artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|   | ||||
							
								
								
									
										9
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -40,6 +40,15 @@ jobs: | ||||
|           os: ${{ matrix.os.name }} | ||||
|           arch: ${{ matrix.arch }} | ||||
|           extension: ${{ join(matrix.os.extension, ' ') }} | ||||
|         env: | ||||
|           APPLE_APP_CERTIFICATE_BASE64: ${{ secrets.APPLE_APP_CERTIFICATE_BASE64 }} | ||||
|           APPLE_APP_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_APP_CERTIFICATE_PASSWORD }} | ||||
|           APPLE_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_BASE64 }} | ||||
|           APPLE_INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_PASSWORD }} | ||||
|           APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | ||||
|           APPLE_ID: ${{ secrets.APPLE_ID }} | ||||
|           APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} | ||||
|  | ||||
|       - name: Publish release | ||||
|         uses: softprops/action-gh-release@v2 | ||||
|         with: | ||||
|   | ||||
							
								
								
									
										10
									
								
								entitlements.plist
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								entitlements.plist
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <?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.cs.allow-jit</key> | ||||
|     <true/> | ||||
|     <key>com.apple.security.files.user-selected.read-write</key> | ||||
|     <true/> | ||||
|   </dict> | ||||
| </plist> | ||||
| @@ -17,33 +17,39 @@ module.exports = { | ||||
|         overwrite: true, | ||||
|         asar: true, | ||||
|         icon: "./images/app-icons/icon", | ||||
|         osxSign: {}, | ||||
|         osxNotarize: { | ||||
|             appleId: process.env.APPLE_ID, | ||||
|             appleIdPassword: process.env.APPLE_ID_PASSWORD, | ||||
|             teamId: process.env.APPLE_TEAM_ID | ||||
|         }, | ||||
|         extraResource: [ | ||||
|             // Moved to root | ||||
|             ...extraResourcesForPlatform, | ||||
|             // All resources should stay in Resources directory for macOS | ||||
|             ...(process.platform === "darwin" ? [] : extraResourcesForPlatform), | ||||
|  | ||||
|             // Moved to resources (TriliumNext Notes.app/Contents/Resources on macOS) | ||||
|             // These always go in Resources | ||||
|             "translations/", | ||||
|             "node_modules/@highlightjs/cdn-assets/styles" | ||||
|         ], | ||||
|         afterComplete: [ | ||||
|             (buildPath, _electronVersion, platform, _arch, callback) => { | ||||
|                 for (const resource of extraResourcesForPlatform) { | ||||
|                     const baseName = path.basename(resource); | ||||
|                 // Only move resources on non-macOS platforms | ||||
|                 if (platform !== "darwin") { | ||||
|                     for (const resource of extraResourcesForPlatform) { | ||||
|                         const baseName = path.basename(resource); | ||||
|                         const sourcePath = path.join(buildPath, "resources", baseName); | ||||
|  | ||||
|                     // prettier-ignore | ||||
|                     const sourcePath = (platform === "darwin") | ||||
|                         ? path.join(buildPath, `${APP_NAME}.app`, "Contents", "Resources", baseName) | ||||
|                         : path.join(buildPath, "resources", baseName); | ||||
|                         // prettier-ignore | ||||
|                         const destPath = (baseName !== "256x256.png") | ||||
|                             ? path.join(buildPath, baseName) | ||||
|                             : path.join(buildPath, "icon.png"); | ||||
|  | ||||
|                     // prettier-ignore | ||||
|                     const destPath = (baseName !== "256x256.png") | ||||
|                         ? path.join(buildPath, baseName) | ||||
|                         : path.join(buildPath, "icon.png"); | ||||
|  | ||||
|                     // Copy files from resources folder to root | ||||
|                     fs.move(sourcePath, destPath) | ||||
|                         .then(() => callback()) | ||||
|                         .catch((err) => callback(err)); | ||||
|                         fs.move(sourcePath, destPath) | ||||
|                             .then(() => callback()) | ||||
|                             .catch((err) => callback(err)); | ||||
|                     } | ||||
|                 } else { | ||||
|                     callback(); | ||||
|                 } | ||||
|             } | ||||
|         ] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user