From d3849acdd3a1018d96ce266ff0b6e6bc26297fa8 Mon Sep 17 00:00:00 2001 From: reaper Date: Fri, 14 Aug 2026 20:40:26 -0500 Subject: [PATCH] ci: add universal macOS release workflow --- .github/workflows/macos-release.yml | 95 +++++++++++++++++++++++++++++ .gitignore | 3 +- build/entitlements.mac.plist | 12 ++++ docs/ALPHA24_COMPLETION_REPORT.md | 3 +- docs/MACOS_RELEASE.md | 41 +++++++++++++ electron-builder.yml | 5 +- scripts/create-mac-icon.sh | 17 ++++++ 7 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/macos-release.yml create mode 100644 build/entitlements.mac.plist create mode 100644 docs/MACOS_RELEASE.md create mode 100644 scripts/create-mac-icon.sh diff --git a/.github/workflows/macos-release.yml b/.github/workflows/macos-release.yml new file mode 100644 index 0000000..6c97322 --- /dev/null +++ b/.github/workflows/macos-release.yml @@ -0,0 +1,95 @@ +name: macOS release + +on: + workflow_dispatch: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + build-macos: + runs-on: macos-14 + timeout-minutes: 45 + env: + HAS_APPLE_SIGNING: ${{ secrets.MAC_CERTIFICATE_P12 != '' }} + HAS_APPLE_NOTARIZATION: ${{ secrets.APPLE_API_KEY_P8 != '' }} + CSC_LINK: ${{ secrets.MAC_CERTIFICATE_P12 }} + CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} + + steps: + - name: Check out source + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Generate macOS icon + run: bash scripts/create-mac-icon.sh + + - name: Verify source + run: | + npm run typecheck + npm run test:run + npm run build + + - name: Prepare Apple API key + if: env.HAS_APPLE_NOTARIZATION == 'true' + env: + APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }} + run: | + printf '%s' "$APPLE_API_KEY_P8" > "$RUNNER_TEMP/AuthKey.p8" + chmod 600 "$RUNNER_TEMP/AuthKey.p8" + + - name: Build unsigned universal DMG and ZIP + if: env.HAS_APPLE_SIGNING != 'true' + run: npx electron-builder --mac dmg zip --universal --publish never + + - name: Build signed and notarized universal DMG and ZIP + if: env.HAS_APPLE_SIGNING == 'true' && env.HAS_APPLE_NOTARIZATION == 'true' + env: + APPLE_API_KEY: ${{ runner.temp }}/AuthKey.p8 + run: npx electron-builder --mac dmg zip --universal --publish never -c.mac.notarize=true + + - name: Reject incomplete signing configuration + if: env.HAS_APPLE_SIGNING == 'true' && env.HAS_APPLE_NOTARIZATION != 'true' + run: | + echo 'A signing certificate was supplied without Apple notarization credentials.' >&2 + exit 1 + + - name: Verify embedded PAC catalog + run: | + test -f dist/module-catalog/wordpress-1.2.0.pac + test -f "dist/mac-universal/Aurora Dockside.app/Contents/Resources/module-catalog/wordpress-1.2.0.pac" + cmp dist/module-catalog/wordpress-1.2.0.pac "dist/mac-universal/Aurora Dockside.app/Contents/Resources/module-catalog/wordpress-1.2.0.pac" + unzip -t "dist/mac-universal/Aurora Dockside.app/Contents/Resources/module-catalog/wordpress-1.2.0.pac" + + - name: Create checksums + run: | + find dist -maxdepth 1 -type f \( -name '*.dmg' -o -name '*-mac.zip' -o -name '*.yml' \) -print0 | sort -z | xargs -0 shasum -a 256 > dist/SHA256SUMS-macos.txt + shasum -a 256 dist/module-catalog/*.pac >> dist/SHA256SUMS-macos.txt + cat dist/SHA256SUMS-macos.txt + + - name: Upload macOS release bundle + uses: actions/upload-artifact@v4 + with: + name: aurora-dockside-macos-universal + if-no-files-found: error + retention-days: 14 + path: | + dist/*.dmg + dist/*-mac.zip + dist/*.yml + dist/SHA256SUMS-macos.txt + dist/module-catalog/*.pac + diff --git a/.gitignore b/.gitignore index f8747f5..2a17958 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,8 @@ node_modules/ # Build output out/ dist/ -build/ +build/* +!build/entitlements.mac.plist release/ # Environment / secrets diff --git a/build/entitlements.mac.plist b/build/entitlements.mac.plist new file mode 100644 index 0000000..f2eb2ec --- /dev/null +++ b/build/entitlements.mac.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + + diff --git a/docs/ALPHA24_COMPLETION_REPORT.md b/docs/ALPHA24_COMPLETION_REPORT.md index e07551c..5e8ee89 100644 --- a/docs/ALPHA24_COMPLETION_REPORT.md +++ b/docs/ALPHA24_COMPLETION_REPORT.md @@ -14,6 +14,7 @@ - Native `.pac` files are ZIP-compressed Aurora packages with `manifest.json` at archive root. - A `.pac` placed beside the AppImage or in its `modules` folder is discovered automatically and appears in the Modules screen. - Packaged applications also carry a generated `.pac` catalog in their application resources. The macOS DMG layout exposes the same catalog as an **Aurora Modules** folder while the copied `.app` retains its own embedded catalog after the DMG is ejected. +- The `macOS release` GitHub Actions workflow builds universal DMG and ZIP artifacts, verifies the embedded catalog, emits checksums, and optionally signs/notarizes when Apple credentials are configured. - Linux packages use Aurora's 512×512 application icon and a synchronized `aurora-dockside` desktop filename, executable name, icon name, and `StartupWMClass`. - `.pac` inspection rejects encrypted entries, symbolic links, path traversal, absolute/drive paths, excessive entry counts, and expanded archives larger than 256 MiB before extraction. - Installation uses staging plus rollback-safe replacement, so a failed update preserves the currently installed module. @@ -87,4 +88,4 @@ d2165af4b75ab888c6d35a750a449205123866231ba5e98e1cbe9bcaa8738f46 aurora-module- - Clean-registry install/remove behavior was exercised through the real registry implementation in automated temporary-directory tests. The live GUI smoke used the already installed local WordPress package and project `24`. - AppImage systems without working FUSE can use `--appimage-extract` and launch `squashfs-root/AppRun --no-sandbox`. -- The embedded catalog and DMG layout are configured and the identical Linux application-resource layout was verified. A final signed/notarized `.app` and DMG must be built and inspected on macOS or a macOS CI runner. +- The embedded catalog and DMG layout are configured and the identical Linux application-resource layout was verified. The macOS CI workflow must still complete once to validate the final Apple-generated DMG and `.app` artifacts. diff --git a/docs/MACOS_RELEASE.md b/docs/MACOS_RELEASE.md new file mode 100644 index 0000000..d787423 --- /dev/null +++ b/docs/MACOS_RELEASE.md @@ -0,0 +1,41 @@ +# macOS release build + +The `macOS release` GitHub Actions workflow builds a universal Intel and Apple Silicon release containing: + +- `Aurora Dockside.app` +- DMG installer +- ZIP updater artifact +- Embedded WordPress `.pac` catalog +- Separate WordPress `.pac` +- SHA-256 checksum manifest + +Run it manually from **Actions → macOS release → Run workflow**, or push a version tag such as `v2.0.0-alpha.24`. + +## Unsigned test build + +No secrets are required. The workflow produces an unsigned DMG suitable for internal verification. macOS Gatekeeper will warn users because it is not signed or notarized. + +## Signed and notarized release + +Configure these GitHub Actions repository secrets: + +| Secret | Purpose | +| --- | --- | +| `MAC_CERTIFICATE_P12` | Base64-encoded Developer ID Application `.p12` certificate | +| `MAC_CERTIFICATE_PASSWORD` | Password protecting the `.p12` certificate | +| `APPLE_API_KEY_P8` | App Store Connect API private key contents | +| `APPLE_API_KEY_ID` | App Store Connect API key ID | +| `APPLE_API_ISSUER` | App Store Connect API issuer ID | + +The workflow refuses a partially configured release where a signing certificate is present but notarization credentials are missing. + +## DMG contents + +The mounted DMG contains the application, an Applications shortcut, and an **Aurora Modules** folder. The application also retains the module catalog internally at: + +```text +Aurora Dockside.app/Contents/Resources/module-catalog/ +└── wordpress-1.2.0.pac +``` + +The workflow verifies that the internal `.pac` is byte-for-byte identical to the separately uploaded package before publishing its artifacts. diff --git a/electron-builder.yml b/electron-builder.yml index b3fecc9..cf6fa88 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -25,10 +25,13 @@ nsis: uninstallDisplayName: ${productName} createDesktopShortcut: always mac: + category: public.app-category.developer-tools + icon: build/icon.icns + entitlements: build/entitlements.mac.plist entitlementsInherit: build/entitlements.mac.plist notarize: false dmg: - artifactName: ${name}-${version}.${ext} + artifactName: ${name}-${version}-${arch}.${ext} contents: - x: 130 y: 220 diff --git a/scripts/create-mac-icon.sh b/scripts/create-mac-icon.sh new file mode 100644 index 0000000..52e4d59 --- /dev/null +++ b/scripts/create-mac-icon.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +project_root="$(cd "$(dirname "$0")/.." && pwd)" +source_icon="$project_root/resources/icon.png" +iconset="$project_root/build/icon.iconset" +output="$project_root/build/icon.icns" + +mkdir -p "$iconset" +for size in 16 32 128 256 512; do + sips -z "$size" "$size" "$source_icon" --out "$iconset/icon_${size}x${size}.png" >/dev/null + retina=$((size * 2)) + sips -z "$retina" "$retina" "$source_icon" --out "$iconset/icon_${size}x${size}@2x.png" >/dev/null +done +iconutil -c icns "$iconset" -o "$output" +echo "Created $output" +