From 04b8f1d5fab0d83ae1e61a25b4c92c95af86e37e Mon Sep 17 00:00:00 2001 From: deepend Date: Wed, 18 Feb 2026 10:55:53 -0700 Subject: [PATCH] Added a new GitHub Actions workflow at .github/workflows/macos-build.yml with branch triggers aligned to existing CI (push/pull_request on master) and a two-job structure for unsigned CI build plus optional release signing/notarization. Implemented the build-only unsigned macOS phase on macos-latest: installs Meson/Ninja/GTK tooling via Homebrew, configures/builds with Meson, installs for bundling, generates the .app zip via the existing macOS bundle script, and uploads the unsigned artifact with retention (14 days). Implemented the release-grade gated phase: job is gated to push on master and only runs when required Apple signing/notarization secrets are present; it downloads the unsigned artifact, imports Developer ID cert, codesigns, notarizes with Apple API key credentials, staples the ticket, and uploads a signed artifact with retention (30 days). --- .github/workflows/macos-build.yml | 156 ++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 .github/workflows/macos-build.yml diff --git a/.github/workflows/macos-build.yml b/.github/workflows/macos-build.yml new file mode 100644 index 00000000..949d55ed --- /dev/null +++ b/.github/workflows/macos-build.yml @@ -0,0 +1,156 @@ +name: macOS Build + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + macos_build_unsigned: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Install build dependencies + run: | + set -eux + brew update + brew install \ + meson ninja pkg-config gettext perl \ + gtk+3 gdk-pixbuf pango adwaita-icon-theme \ + hicolor-icon-theme glib dbus \ + enchant-applespell gtk-mac-bundler + + - name: Configure + run: | + set -eux + PREFIX="$(brew --prefix)" + rm -rf build + meson setup build \ + --prefix="$PREFIX" \ + -Dgtk3=true \ + -Dtext-frontend=true \ + -Dwith-perl=perl \ + -Dwith-python=python3 \ + -Dauto_features=enabled + + - name: Build + run: | + set -eux + meson compile -C build + + - name: Install for bundling + run: | + set -eux + sudo meson install -C build + + - name: Package unsigned .app + run: | + set -eux + VERSION="$(git describe --tags --always)" + PREFIX="$(brew --prefix)" + ENCHANT_PREFIX="$(brew --prefix enchant-applespell)" + + sed "s/@VERSION@/${VERSION}/g" osx/Info.plist.in > osx/Info.plist + + perl -0pi -e 's|.*?|$ENV{PREFIX}|s' osx/zoitechat.bundle + perl -0pi -e 's|.*?|$ENV{ENCHANT_PREFIX}|s' osx/zoitechat.bundle + + (cd osx && ./makebundle.sh) + mv osx/ZoiteChat-*.app.zip ./ + + - name: Upload unsigned macOS app artifact + uses: actions/upload-artifact@v4 + with: + name: zoitechat-macos-unsigned + path: ZoiteChat-*.app.zip + if-no-files-found: error + retention-days: 14 + + macos_release_signed: + needs: macos_build_unsigned + runs-on: macos-latest + if: >- + github.event_name == 'push' && + github.ref == 'refs/heads/master' && + secrets.APPLE_DEVELOPER_ID_APPLICATION != '' && + secrets.APPLE_DEVELOPER_ID_CERT_P12 != '' && + secrets.APPLE_DEVELOPER_ID_CERT_P12_PASSWORD != '' && + secrets.APPLE_NOTARY_API_KEY != '' && + secrets.APPLE_NOTARY_API_KEY_ID != '' && + secrets.APPLE_NOTARY_ISSUER_ID != '' + + steps: + - name: Download unsigned app artifact + uses: actions/download-artifact@v4 + with: + name: zoitechat-macos-unsigned + path: dist + + - name: Import Developer ID certificate + env: + CERT_P12_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERT_P12 }} + CERT_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERT_P12_PASSWORD }} + run: | + set -eux + echo "$CERT_P12_BASE64" | base64 --decode > certificate.p12 + + security create-keychain -p "" build.keychain + security set-keychain-settings -lut 21600 build.keychain + security unlock-keychain -p "" build.keychain + security import certificate.p12 -k build.keychain -P "$CERT_PASSWORD" -A -T /usr/bin/codesign + security list-keychains -d user -s build.keychain $(security list-keychains -d user | tr -d '"') + security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain + + - name: Codesign app bundle + env: + CODESIGN_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION }} + run: | + set -eux + unzip -q dist/ZoiteChat-*.app.zip -d dist + APP_PATH="$(find dist -maxdepth 1 -name 'ZoiteChat.app' -type d | head -n 1)" + + codesign --force --deep --options runtime --timestamp \ + --sign "$CODESIGN_IDENTITY" "$APP_PATH" + + codesign --verify --deep --strict --verbose=2 "$APP_PATH" + spctl --assess --type execute --verbose "$APP_PATH" + + - name: Notarize and staple + env: + NOTARY_API_KEY_BASE64: ${{ secrets.APPLE_NOTARY_API_KEY }} + NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_API_KEY_ID }} + NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }} + run: | + set -eux + APP_PATH="$(find dist -maxdepth 1 -name 'ZoiteChat.app' -type d | head -n 1)" + NOTARY_ZIP="dist/ZoiteChat-notarize.zip" + SIGNED_ZIP="dist/ZoiteChat-signed.app.zip" + + echo "$NOTARY_API_KEY_BASE64" | base64 --decode > AuthKey_${NOTARY_KEY_ID}.p8 + ditto -c -k --keepParent "$APP_PATH" "$NOTARY_ZIP" + + xcrun notarytool submit "$NOTARY_ZIP" \ + --key "AuthKey_${NOTARY_KEY_ID}.p8" \ + --key-id "$NOTARY_KEY_ID" \ + --issuer "$NOTARY_ISSUER_ID" \ + --wait + + xcrun stapler staple "$APP_PATH" + xcrun stapler validate "$APP_PATH" + + ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "$SIGNED_ZIP" + + - name: Upload signed macOS app artifact + uses: actions/upload-artifact@v4 + with: + name: zoitechat-macos-signed + path: dist/ZoiteChat-signed.app.zip + if-no-files-found: error + retention-days: 30