Release v0.7.1 #54
Workflow file for this run
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: | |
| tags: | |
| - 'v*' | |
| permissions: | |
| contents: write | |
| # Prevent concurrent releases | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| build-macos: | |
| # Both targets build on macos-latest (arm64); x86_64 cross-compiles via Rust. | |
| # macos-13 (Intel) runners are retired by GitHub. | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: [aarch64-apple-darwin, x86_64-apple-darwin] | |
| runs-on: macos-latest | |
| permissions: | |
| contents: write | |
| timeout-minutes: 120 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: 'npm' | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Rust cache | |
| uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: src-tauri | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Rebuild better-sqlite3 for x64 (cross-arch) | |
| # The CI runner is ARM64 (macos-latest). For the x86_64 release build we | |
| # need the x64 native addon. npm_config_arch=x64 makes prebuild-install | |
| # download the x64 prebuilt binary instead of the ARM64 one. | |
| if: matrix.target == 'x86_64-apple-darwin' | |
| run: npm_config_arch=x64 npm rebuild better-sqlite3 | |
| - name: Build agent sidecar | |
| run: | | |
| cd agent && npm run build | |
| mkdir -p ../src-tauri/binaries | |
| cp dist/agent.js "../src-tauri/binaries/agent-$TARGET" | |
| chmod +x "../src-tauri/binaries/agent-$TARGET" | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| - name: Bundle native modules for Tauri resources | |
| # Ensures src-tauri/bundled-node-modules exists for the Tauri resource glob. | |
| # The agent build script tries to do this but may not resolve the workspace | |
| # root node_modules correctly in CI, so we do it explicitly here. | |
| run: | | |
| mkdir -p src-tauri/bundled-node-modules | |
| for pkg in better-sqlite3 bindings file-uri-to-path; do | |
| if [ -d "node_modules/$pkg" ]; then | |
| cp -r "node_modules/$pkg" "src-tauri/bundled-node-modules/$pkg" | |
| echo "Bundled $pkg" | |
| else | |
| echo "WARNING: $pkg not found in node_modules" | |
| fi | |
| done | |
| - name: Bundle Node.js runtime | |
| # Ship the official Node.js binary inside the app so the agent sidecar | |
| # always runs with the same Node.js version used to compile better-sqlite3. | |
| # Without this, users' system Node.js may have a different ABI, causing | |
| # NODE_MODULE_VERSION mismatch crashes. | |
| run: | | |
| NODE_VER="$(node -v)" | |
| if [[ "$TARGET" == "aarch64-apple-darwin" ]]; then | |
| ARCH="arm64" | |
| else | |
| ARCH="x64" | |
| fi | |
| echo "Downloading Node.js $NODE_VER for darwin-$ARCH" | |
| curl -sL "https://nodejs.org/dist/${NODE_VER}/node-${NODE_VER}-darwin-${ARCH}.tar.gz" \ | |
| | tar -xzf - --strip-components=2 -C /tmp "node-${NODE_VER}-darwin-${ARCH}/bin/node" | |
| mkdir -p src-tauri/bundled-node/bin | |
| mv /tmp/node "src-tauri/bundled-node/bin/node" | |
| chmod +x "src-tauri/bundled-node/bin/node" | |
| echo "Bundled Node.js $NODE_VER ($(du -h src-tauri/bundled-node/bin/node | cut -f1))" | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| - name: Import Apple certificate | |
| env: | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| run: | | |
| echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 | |
| security create-keychain -p "ci-keychain" build.keychain | |
| security default-keychain -s build.keychain | |
| security unlock-keychain -p "ci-keychain" build.keychain | |
| security import certificate.p12 \ | |
| -k build.keychain \ | |
| -P "$APPLE_CERTIFICATE_PASSWORD" \ | |
| -T /usr/bin/codesign | |
| security set-key-partition-list \ | |
| -S apple-tool:,apple:,codesign: \ | |
| -s -k "ci-keychain" build.keychain | |
| rm certificate.p12 | |
| - name: Sign native binaries and bundled node | |
| # Apple notarization requires all executables/dylibs in the bundle to be | |
| # codesigned with the hardened runtime. Sign both better-sqlite3's .node | |
| # addon and the bundled Node.js binary. | |
| env: | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| run: | | |
| find src-tauri/bundled-node-modules -name "*.node" | while read f; do | |
| echo "Signing $f" | |
| codesign --force --sign "$APPLE_SIGNING_IDENTITY" \ | |
| --timestamp --options runtime "$f" | |
| done | |
| if [ -f src-tauri/bundled-node/bin/node ]; then | |
| echo "Signing bundled Node.js binary (with JIT + automation entitlements)" | |
| codesign --force --sign "$APPLE_SIGNING_IDENTITY" \ | |
| --timestamp --options runtime \ | |
| --entitlements src-tauri/node-entitlements.plist \ | |
| "src-tauri/bundled-node/bin/node" | |
| fi | |
| AGENT_BIN=$(find src-tauri/binaries -name "agent-*" -type f 2>/dev/null | head -1) | |
| if [ -n "$AGENT_BIN" ]; then | |
| echo "Signing agent sidecar with automation entitlements" | |
| codesign --force --sign "$APPLE_SIGNING_IDENTITY" \ | |
| --timestamp --options runtime \ | |
| --entitlements src-tauri/node-entitlements.plist \ | |
| "$AGENT_BIN" | |
| fi | |
| - name: Build Tauri app | |
| uses: tauri-apps/tauri-action@v0 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }} | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| with: | |
| tagName: ${{ github.ref_name }} | |
| releaseName: 'OpenHelm ${{ github.ref_name }}' | |
| releaseBody: 'See the assets below to download and install.' | |
| releaseDraft: false | |
| prerelease: false | |
| args: --target ${{ matrix.target }} | |
| - name: Upload updater signature | |
| # tauri-action builds the .sig file but skips attaching it to the release. | |
| # Sign the already-uploaded .app.tar.gz ourselves and upload the sig. | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ github.ref_name }} | |
| TARGET: ${{ matrix.target }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| run: | | |
| VER="${TAG#v}" | |
| [[ "$TARGET" == "aarch64-apple-darwin" ]] && ARCH="aarch64" || ARCH="x64" | |
| TAR="src-tauri/target/${TARGET}/release/bundle/macos/OpenHelm.app.tar.gz" | |
| SIG="${TAR}.sig" | |
| # Sig should already exist from tauri build; re-sign if not found | |
| if [ ! -f "$SIG" ]; then | |
| echo "Sig not found at expected path, re-signing..." | |
| npx @tauri-apps/cli signer sign --private-key "$TAURI_SIGNING_PRIVATE_KEY" "$TAR" | |
| fi | |
| if [ -f "$SIG" ]; then | |
| # Copy to a versioned name so GitHub uses it as the download filename | |
| DEST="/tmp/OpenHelm_${VER}_${ARCH}.app.tar.gz.sig" | |
| cp "$SIG" "$DEST" | |
| gh release upload "$TAG" "$DEST" --clobber -R maxbeech/OpenHelm | |
| echo "Uploaded sig for $ARCH" | |
| else | |
| echo "ERROR: sig file still not found at $SIG" && exit 1 | |
| fi | |
| update-manifest: | |
| needs: [build-macos] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: main | |
| - name: Generate and commit latest.json | |
| # VERSION and TAG are passed via env to avoid injection via untrusted refs. | |
| # All git context (ref_name) flows through env vars, not directly into run: scripts. | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ github.ref_name }} | |
| run: | | |
| VER="${TAG#v}" | |
| gh release download "$TAG" \ | |
| --pattern "*.app.tar.gz.sig" \ | |
| --dir /tmp/sigs | |
| ARM_SIG=$(cat "/tmp/sigs/OpenHelm_${VER}_aarch64.app.tar.gz.sig") | |
| X64_SIG=$(cat "/tmp/sigs/OpenHelm_${VER}_x64.app.tar.gz.sig") | |
| python3 -c " | |
| import json, sys | |
| manifest = { | |
| 'version': sys.argv[1], | |
| 'notes': 'See https://github.com/maxbeech/OpenHelm/releases/tag/' + sys.argv[4] + ' for details.', | |
| 'pub_date': '$(date -u +%Y-%m-%dT%H:%M:%SZ)', | |
| 'platforms': { | |
| 'darwin-aarch64': { | |
| 'signature': sys.argv[2], | |
| 'url': 'https://github.com/maxbeech/OpenHelm/releases/download/' + sys.argv[4] + '/OpenHelm_aarch64.app.tar.gz' | |
| }, | |
| 'darwin-x86_64': { | |
| 'signature': sys.argv[3], | |
| 'url': 'https://github.com/maxbeech/OpenHelm/releases/download/' + sys.argv[4] + '/OpenHelm_x64.app.tar.gz' | |
| } | |
| } | |
| } | |
| print(json.dumps(manifest, indent=2)) | |
| " "$VER" "$ARM_SIG" "$X64_SIG" "$TAG" > update-manifest/latest.json | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add update-manifest/latest.json | |
| git commit -m "chore: update manifest for $TAG [skip ci]" | |
| git push origin main |