add signing for macos #18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| # Changesets needs to push the version commit/tag and open the version PR; | |
| # the binary job needs to upload an asset to the release. | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| # Don't run two releases at once, but let an in-progress one finish. | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| jobs: | |
| # 1. Changesets: open/update the "Version Packages" PR, or — once that PR is | |
| # merged — tag the new version and create the GitHub Release. | |
| release: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| published: ${{ steps.changesets.outputs.published }} | |
| publishedPackages: ${{ steps.changesets.outputs.publishedPackages }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Install release tooling | |
| # Only Changesets is needed here; skip zero-native's postinstall (the | |
| # native CLI download) — the binary jobs do a full install for that. | |
| run: npm install --ignore-scripts | |
| - name: Create version PR or tag a release | |
| id: changesets | |
| uses: changesets/action@v1 | |
| with: | |
| version: npm run version | |
| publish: npm run release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # 2. Build the macOS app and attach it to the release Changesets just created. | |
| binary-macos: | |
| needs: release | |
| if: needs.release.outputs.published == 'true' | |
| runs-on: macos-latest | |
| # Signing + notarization secrets. When unset, the signing steps below are | |
| # skipped and the build ships unsigned (first launch needs right-click → | |
| # Open). Set all of them to ship a signed, notarized .dmg with no Gatekeeper | |
| # prompt. See "Code signing" in README.md for how to obtain each. | |
| env: | |
| MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} | |
| MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} | |
| MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }} | |
| APPLE_NOTARY_KEY: ${{ secrets.APPLE_NOTARY_KEY }} | |
| APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }} | |
| APPLE_NOTARY_ISSUER: ${{ secrets.APPLE_NOTARY_ISSUER }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - uses: mlugg/setup-zig@v2 | |
| with: | |
| version: 0.16.0 | |
| - name: Install dependencies (zero-native framework + CLI) | |
| run: | | |
| npm install | |
| echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH" | |
| - name: Resolve the released version | |
| id: ver | |
| run: | | |
| VER=$(echo '${{ needs.release.outputs.publishedPackages }}' | jq -r '.[0].version') | |
| echo "version=$VER" >> "$GITHUB_OUTPUT" | |
| echo "tag=keyparty@$VER" >> "$GITHUB_OUTPUT" | |
| - name: Build & package the macOS app | |
| run: | | |
| zig build package \ | |
| -Doptimize=ReleaseFast \ | |
| -Dpackage-target=macos | |
| - name: Import Developer ID certificate | |
| if: ${{ env.MACOS_CERTIFICATE != '' }} | |
| run: | | |
| KEYCHAIN="$RUNNER_TEMP/signing.keychain-db" | |
| KEYCHAIN_PWD="$(openssl rand -base64 24)" | |
| security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN" | |
| security set-keychain-settings -lut 3600 "$KEYCHAIN" | |
| security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN" | |
| echo "$MACOS_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/cert.p12" | |
| security import "$RUNNER_TEMP/cert.p12" -k "$KEYCHAIN" \ | |
| -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: \ | |
| -s -k "$KEYCHAIN_PWD" "$KEYCHAIN" >/dev/null | |
| # Put the temp keychain on the search list so codesign finds the identity. | |
| security list-keychains -d user -s "$KEYCHAIN" \ | |
| $(security list-keychains -d user | sed s/\"//g) | |
| rm -f "$RUNNER_TEMP/cert.p12" | |
| - name: Sign the app (Developer ID + Hardened Runtime) | |
| if: ${{ env.MACOS_CERTIFICATE != '' }} | |
| run: | | |
| APP="$(ls -d zig-out/package/*.app | head -n1)" | |
| codesign --force --deep --options runtime --timestamp \ | |
| --entitlements assets/keyparty.entitlements \ | |
| --sign "$MACOS_SIGN_IDENTITY" "$APP" | |
| codesign --verify --strict --verbose=2 "$APP" | |
| - name: Build a .dmg (drag-to-Applications) | |
| run: | | |
| APP="$(ls -d zig-out/package/*.app | head -n1)" | |
| # Stage the bundle under its display name with an Applications shortcut, | |
| # so the mounted disk image is a familiar drag-to-install window. | |
| STAGE="dmg-stage" | |
| rm -rf "$STAGE" && mkdir -p "$STAGE" | |
| cp -R "$APP" "$STAGE/Key Party.app" | |
| ln -s /Applications "$STAGE/Applications" | |
| hdiutil create -volname "Key Party" -srcfolder "$STAGE" -ov -format UDZO "keyparty-macos.dmg" | |
| - name: Sign & notarize the .dmg | |
| if: ${{ env.MACOS_CERTIFICATE != '' }} | |
| run: | | |
| codesign --force --timestamp --sign "$MACOS_SIGN_IDENTITY" "keyparty-macos.dmg" | |
| if [ -n "$APPLE_NOTARY_KEY" ]; then | |
| echo "$APPLE_NOTARY_KEY" | base64 --decode > "$RUNNER_TEMP/notary.p8" | |
| # --wait blocks until Apple finishes; staple embeds the ticket so the | |
| # .dmg launches offline with no Gatekeeper prompt. | |
| xcrun notarytool submit "keyparty-macos.dmg" \ | |
| --key "$RUNNER_TEMP/notary.p8" \ | |
| --key-id "$APPLE_NOTARY_KEY_ID" \ | |
| --issuer "$APPLE_NOTARY_ISSUER" \ | |
| --wait | |
| xcrun stapler staple "keyparty-macos.dmg" | |
| rm -f "$RUNNER_TEMP/notary.p8" | |
| else | |
| echo "::warning::APPLE_NOTARY_KEY unset — .dmg is signed but NOT notarized; Gatekeeper will still warn." | |
| fi | |
| - name: Attach the .dmg to the release | |
| # Version-less name -> https://github.com/<owner>/<repo>/releases/latest/download/keyparty-macos.dmg | |
| # is a stable link the web build can point at. | |
| run: gh release upload "${{ steps.ver.outputs.tag }}" "keyparty-macos.dmg" --clobber | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # 3. Build the Windows app (full-screen kiosk WebView2 host) and attach it. | |
| binary-windows: | |
| needs: release | |
| if: needs.release.outputs.published == 'true' | |
| runs-on: windows-latest | |
| env: | |
| # Where the WebView2 SDK headers land (NuGet, version stripped). | |
| NUGET_DIR: packages/Microsoft.Web.WebView2 | |
| # Code signing is scaffolded but inert. Set this secret AND drop a real | |
| # signing command into the "Sign the Windows exe" step to enable it (see | |
| # "Code signing" in README.md). Until then the exe ships unsigned. | |
| WINDOWS_SIGN_CERT: ${{ secrets.WINDOWS_SIGN_CERT }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - uses: mlugg/setup-zig@v2 | |
| with: | |
| version: 0.16.0 | |
| # Sets the MSVC environment (INCLUDE, WindowsSdkDir, WindowsSdkVersion, …) | |
| # so Zig's msvc target finds the toolchain and we can locate the winrt | |
| # headers (wrl.h) the WebView2 host needs. | |
| - uses: ilammy/msvc-dev-cmd@v1 | |
| - name: Install dependencies (zero-native framework + CLI) | |
| shell: bash | |
| run: | | |
| npm install | |
| echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH" | |
| - name: Fetch the WebView2 SDK headers + loader | |
| shell: bash | |
| run: nuget install Microsoft.Web.WebView2 -OutputDirectory packages -ExcludeVersion | |
| - name: Resolve the released version | |
| id: ver | |
| shell: bash | |
| run: | | |
| VER=$(echo '${{ needs.release.outputs.publishedPackages }}' | jq -r '.[0].version') | |
| echo "version=$VER" >> "$GITHUB_OUTPUT" | |
| echo "tag=keyparty@$VER" >> "$GITHUB_OUTPUT" | |
| - name: Build the single-file Windows exe | |
| shell: bash | |
| run: | | |
| WV2_INC="$GITHUB_WORKSPACE/$NUGET_DIR/build/native/include" | |
| WV2_LIB="$GITHUB_WORKSPACE/$NUGET_DIR/build/native/x64" | |
| echo "WebView2 include: $WV2_INC" | |
| echo "WebView2 lib: $WV2_LIB" | |
| # Fail loudly if the SDK pieces are missing: without WebView2.h the host | |
| # silently compiles to a blank-window stub; without the static loader lib | |
| # it can't link a self-contained exe. | |
| test -f "$WV2_INC/WebView2.h" || { echo "ERROR: WebView2.h not found at $WV2_INC"; ls -R "$GITHUB_WORKSPACE/$NUGET_DIR" 2>/dev/null | head -40; exit 1; } | |
| test -f "$WV2_LIB/WebView2LoaderStatic.lib" || { echo "ERROR: WebView2LoaderStatic.lib not found at $WV2_LIB"; exit 1; } | |
| # The frontend is embedded into the exe (served from memory) and the | |
| # WebView2 loader is static-linked, so the release artifact is a single | |
| # self-contained keyparty.exe. cl.exe finds the Windows SDK + winrt via | |
| # %INCLUDE%. | |
| zig build \ | |
| -Dtarget=x86_64-windows-msvc \ | |
| -Doptimize=ReleaseFast \ | |
| -Dwebview2-include="$WV2_INC" \ | |
| -Dwebview2-lib-dir="$WV2_LIB" | |
| test -f zig-out/bin/keyparty.exe || { echo "ERROR: no exe produced"; ls -R zig-out; exit 1; } | |
| # Version-less name -> .../releases/latest/download/keyparty-windows.exe is stable. | |
| cp zig-out/bin/keyparty.exe "keyparty-windows.exe" | |
| - name: Sign the Windows exe (scaffold — skipped until a cert is configured) | |
| # Inert today (no WINDOWS_SIGN_CERT secret). Public-trust Windows certs now | |
| # require hardware/cloud key storage, so plug your provider in here: | |
| # • Microsoft Trusted Signing: azure/trusted-signing-action@v0 | |
| # • DigiCert KeyLocker / SSL.com eSigner: the provider's signtool dlib | |
| # • signtool: signtool sign /fd SHA256 /tr <timestamp-url> /td SHA256 keyparty-windows.exe | |
| # Replace the echo with the real command; the signed exe then flows through | |
| # to the existing upload step unchanged. | |
| if: ${{ env.WINDOWS_SIGN_CERT != '' }} | |
| shell: bash | |
| run: | | |
| echo "TODO: sign keyparty-windows.exe (see step comment for provider options)." | |
| - name: Attach the binary to the release | |
| shell: bash | |
| run: gh release upload "${{ steps.ver.outputs.tag }}" "keyparty-windows.exe" --clobber | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |