From 68d557d50ad2bcd769b0ad5dd4b9b28fd43f536c Mon Sep 17 00:00:00 2001 From: Vivek R <123vivekr@gmail.com> Date: Fri, 1 Aug 2025 17:11:41 +0530 Subject: [PATCH] ci: add GitHub Actions workflows for multi-platform builds - Add build-linux.yml for Ubuntu/Linux x86_64 builds - Installs required system dependencies (webkit2gtk, GTK3, etc.) - Builds Tauri application for Linux platform - Uploads build artifacts for distribution - Add build-macos.yml for macOS Intel and Apple Silicon builds - Supports both x86_64 and aarch64 architectures - Handles Apple certificate import and code signing - Creates notarized DMG installers - Includes Homebrew cask generation - Allows skipping builds and using previous artifacts - Add release.yml for automated releases - Triggers on version tags (v*) - Orchestrates builds across all platforms - Creates GitHub releases with all artifacts - Supports manual workflow dispatch with version input --- .github/workflows/build-linux.yml | 60 +++++++ .github/workflows/build-macos.yml | 272 ++++++++++++++++++++++++++++++ .github/workflows/release.yml | 131 ++++++++++++++ 3 files changed, 463 insertions(+) create mode 100644 .github/workflows/build-linux.yml create mode 100644 .github/workflows/build-macos.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml new file mode 100644 index 0000000..01d8e68 --- /dev/null +++ b/.github/workflows/build-linux.yml @@ -0,0 +1,60 @@ +name: Build Linux + +on: + workflow_dispatch: + push: + branches: [main, test-linux-workflow] + +jobs: + build: + name: Build Linux x86_64 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + pkg-config \ + libwebkit2gtk-4.1-dev \ + libgtk-3-dev \ + libssl-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-unknown-linux-gnu + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + with: + workspaces: src-tauri + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install + + - name: Build Tauri app + run: bun run tauri build --target x86_64-unknown-linux-gnu + + - name: Create artifacts directory + run: | + mkdir -p dist/linux-x86_64 + cp src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb dist/linux-x86_64/ || true + cp src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage dist/linux-x86_64/ || true + + # Generate checksums + cd dist/linux-x86_64 + sha256sum * > checksums.txt + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: linux-x86_64 + path: dist/linux-x86_64/* diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml new file mode 100644 index 0000000..0a1dc3c --- /dev/null +++ b/.github/workflows/build-macos.yml @@ -0,0 +1,272 @@ +name: Build macOS + +on: + workflow_dispatch: + inputs: + skip_build: + description: 'Skip build and use artifacts from a previous run' + required: false + default: false + type: boolean + run_id: + description: 'Run ID to download artifacts from (leave empty for latest)' + required: false + type: string + push: + branches: [main, test-linux-workflow] + +jobs: + build: + name: Build macOS ${{ matrix.target }} + if: ${{ !inputs.skip_build }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: macos-13 # Intel + target: x86_64-apple-darwin + arch: x86_64 + - os: macos-14 # Apple Silicon + target: aarch64-apple-darwin + arch: aarch64 + + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + with: + workspaces: src-tauri + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install + + - name: Import Apple certificates + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + # Create variables + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + # Import certificate from secrets + echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH + + # Create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # Import certificate to keychain + security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + - name: Build native + run: bun run tauri build + + - name: Upload architecture-specific artifacts + uses: actions/upload-artifact@v4 + with: + name: macos-${{ matrix.arch }} + path: | + src-tauri/target/release/bundle/macos/Claudia.app + src-tauri/target/release/bundle/dmg/*.dmg + retention-days: 1 + + universal: + name: Create Universal Binary + needs: [build] + if: ${{ !cancelled() && (needs.build.result == 'success' || needs.build.result == 'skipped') }} + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Download artifacts from current workflow + if: ${{ !inputs.skip_build }} + uses: actions/download-artifact@v4 + with: + pattern: macos-* + path: artifacts + + - name: Download artifacts from specific run + if: ${{ inputs.skip_build && inputs.run_id != '' }} + uses: dawidd6/action-download-artifact@v3 + with: + workflow: build-macos.yml + run_id: ${{ inputs.run_id }} + name: macos-* + path: artifacts + + - name: Download artifacts from latest run + if: ${{ inputs.skip_build && inputs.run_id == '' }} + uses: dawidd6/action-download-artifact@v3 + with: + workflow: build-macos.yml + workflow_conclusion: success + name: macos-* + path: artifacts + + - name: List downloaded artifacts + run: | + echo "๐Ÿ“ Artifact structure:" + find artifacts -type f -name "*.app" -o -name "*.dmg" | head -20 + echo "" + echo "๐Ÿ“ Full directory structure:" + ls -la artifacts/ + ls -la artifacts/macos-aarch64/ || echo "macos-aarch64 directory not found" + ls -la artifacts/macos-x86_64/ || echo "macos-x86_64 directory not found" + + - name: Import Apple certificates + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + # Create variables + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + # Import certificate from secrets + echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH + + # Create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # Import certificate to keychain + security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + - name: Create universal app + run: | + # Create temp directory + mkdir -p dmg_temp + + # Extract zip files if they exist + if [ -f "artifacts/macos-aarch64.zip" ]; then + echo "๐Ÿ“ฆ Extracting macos-aarch64.zip..." + unzip -q artifacts/macos-aarch64.zip -d artifacts/macos-aarch64/ + fi + + if [ -f "artifacts/macos-x86_64.zip" ]; then + echo "๐Ÿ“ฆ Extracting macos-x86_64.zip..." + unzip -q artifacts/macos-x86_64.zip -d artifacts/macos-x86_64/ + fi + + # Find the actual app paths + AARCH64_APP=$(find artifacts/macos-aarch64 -name "Claudia.app" -type d | head -1) + X86_64_APP=$(find artifacts/macos-x86_64 -name "Claudia.app" -type d | head -1) + + if [ -z "$AARCH64_APP" ] || [ -z "$X86_64_APP" ]; then + echo "โŒ Could not find app bundles" + echo "AARCH64_APP: $AARCH64_APP" + echo "X86_64_APP: $X86_64_APP" + exit 1 + fi + + echo "โœ… Found app bundles:" + echo " ARM64: $AARCH64_APP" + echo " x86_64: $X86_64_APP" + + # Copy ARM64 app as base + cp -R "$AARCH64_APP" dmg_temp/ + + # Create universal binary using lipo + lipo -create -output dmg_temp/Claudia.app/Contents/MacOS/claudia \ + "$AARCH64_APP/Contents/MacOS/claudia" \ + "$X86_64_APP/Contents/MacOS/claudia" + + echo "โœ… Universal binary created" + lipo -info dmg_temp/Claudia.app/Contents/MacOS/claudia + + - name: Sign app bundle + env: + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + run: | + codesign --sign "$APPLE_SIGNING_IDENTITY" \ + --timestamp \ + --options runtime \ + --force \ + --deep \ + --entitlements src-tauri/entitlements.plist \ + dmg_temp/Claudia.app + + - name: Create DMG + run: | + hdiutil create -volname "Claudia Installer" \ + -srcfolder dmg_temp \ + -ov -format UDZO Claudia.dmg + + - name: Sign DMG + env: + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + run: | + codesign --sign "$APPLE_SIGNING_IDENTITY" \ + --timestamp \ + --force Claudia.dmg + + - name: Notarize DMG + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + run: | + # Store notarization credentials + xcrun notarytool store-credentials "notarytool-profile" \ + --apple-id "$APPLE_ID" \ + --team-id "$APPLE_TEAM_ID" \ + --password "$APPLE_PASSWORD" + + # Submit for notarization + xcrun notarytool submit Claudia.dmg \ + --keychain-profile "notarytool-profile" \ + --wait + + - name: Staple notarization + run: xcrun stapler staple Claudia.dmg + + - name: Verify DMG + run: | + spctl -a -t open -vvv --context context:primary-signature Claudia.dmg + echo "โœ… DMG verification complete" + + - name: Create artifacts directory + run: | + mkdir -p dist/macos-universal + cp Claudia.dmg dist/macos-universal/ + + # Also save the app bundle + cd dmg_temp && zip -r ../dist/macos-universal/Claudia.app.zip Claudia.app && cd .. + + # Generate checksum + shasum -a 256 dist/macos-universal/* > dist/macos-universal/checksums.txt + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: macos-universal + path: dist/macos-universal/* + + - name: Cleanup + if: always() + run: | + echo "๐Ÿงน Cleaning up temporary directories..." + rm -rf dmg_temp temp_x86 artifacts + + # Clean up keychain + if [ -n "$RUNNER_TEMP" ] && [ -f "$RUNNER_TEMP/app-signing.keychain-db" ]; then + security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" || true + fi + + echo "โœ… Cleanup complete" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..271221e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,131 @@ +name: Release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + version: + description: 'Version to release (e.g., v1.0.0)' + required: true + type: string + +permissions: + contents: write + +jobs: + # Build jobs for each platform + build-linux: + uses: ./.github/workflows/build-linux.yml + secrets: inherit + + build-macos: + uses: ./.github/workflows/build-macos.yml + secrets: inherit + + + # Create release after all builds complete + create-release: + name: Create Release + needs: [build-linux, build-macos] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Determine version + id: version + run: | + if [ "${{ github.event_name }}" = "push" ]; then + VERSION="${GITHUB_REF#refs/tags/}" + else + VERSION="${{ inputs.version }}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Version: $VERSION" + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Prepare release assets + run: | + mkdir -p release-assets + + # Linux artifacts + if [ -d "artifacts/linux-x86_64" ]; then + cp artifacts/linux-x86_64/*.deb release-assets/Claudia_${{ steps.version.outputs.version }}_linux_x86_64.deb || true + cp artifacts/linux-x86_64/*.AppImage release-assets/Claudia_${{ steps.version.outputs.version }}_linux_x86_64.AppImage || true + fi + + # macOS artifacts + if [ -d "artifacts/macos-universal" ]; then + cp artifacts/macos-universal/Claudia.dmg release-assets/Claudia_${{ steps.version.outputs.version }}_macos_universal.dmg || true + cp artifacts/macos-universal/Claudia.app.zip release-assets/Claudia_${{ steps.version.outputs.version }}_macos_universal.app.tar.gz || true + fi + + # Create source code archives + # Clean version without 'v' prefix for archive names + CLEAN_VERSION="${{ steps.version.outputs.version }}" + CLEAN_VERSION="${CLEAN_VERSION#v}" + + # Create source code archives (excluding .git and other unnecessary files) + echo "Creating source code archives..." + + # Create a clean export of the repository + git archive --format=tar.gz --prefix=claudia-${CLEAN_VERSION}/ -o release-assets/claudia-${CLEAN_VERSION}.tar.gz HEAD + git archive --format=zip --prefix=claudia-${CLEAN_VERSION}/ -o release-assets/claudia-${CLEAN_VERSION}.zip HEAD + + # Generate signatures for all files + cd release-assets + for file in *; do + if [ -f "$file" ]; then + sha256sum "$file" > "$file.sha256" + fi + done + cd .. + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.version.outputs.version }} + name: Claudia ${{ steps.version.outputs.version }} + draft: true + prerelease: false + generate_release_notes: true + files: release-assets/* + body: | + ## Claudia ${{ steps.version.outputs.version }} + + ### Downloads + + #### macOS + - Universal binary (Intel + Apple Silicon) + - `.dmg` - Disk image installer (recommended) + - `.app.tar.gz` - Application bundle + + + #### Linux + - `.AppImage` - Universal Linux package (recommended) + - `.deb` - Debian/Ubuntu package + + #### Source Code + - `claudia-{version}.tar.gz` - Source code (tar.gz) + - `claudia-{version}.zip` - Source code (zip) + + ### Installation + + **macOS**: Download the `.dmg` file, open it, and drag Claudia to your Applications folder. + + + **Linux**: Download the `.AppImage` file, make it executable (`chmod +x`), and run it. For Debian/Ubuntu, use the `.deb` file. + + ### Verification + + All files include `.sha256` signature files for verification. + + ### What's Changed + + See below for a full list of changes in this release.