name: "Build and Test - DevTest" on: workflow_dispatch: inputs: sign-build: description: 'Sign the build' required: false type: boolean default: false upload-artifacts: description: 'Upload build artifacts' required: false type: boolean default: true # Cancel duplicate workflow runs concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always jobs: build-devtest: name: Build ${{ matrix.platform-name }} runs-on: ${{ matrix.platform }} permissions: contents: write strategy: fail-fast: false matrix: include: - platform: macos-latest platform-name: macOS target: aarch64-apple-darwin args: --target aarch64-apple-darwin - platform: windows-latest platform-name: Windows target: x86_64-pc-windows-msvc args: --target x86_64-pc-windows-msvc - platform: ubuntu-22.04 platform-name: Linux (Ubuntu 22.04) target: x86_64-unknown-linux-gnu args: --target x86_64-unknown-linux-gnu --bundles deb - platform: ubuntu-24.04 platform-name: Linux (Ubuntu 24.04) target: x86_64-unknown-linux-gnu args: --target x86_64-unknown-linux-gnu --bundles appimage,rpm steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get version from tauri.conf.json id: get-version shell: bash run: | VERSION=$(grep -o '"version": "[^"]*"' frontend/src-tauri/tauri.conf.json | cut -d'"' -f4) echo "Application version: $VERSION" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 8 run_install: false - name: Setup Node uses: actions/setup-node@v4 with: node-version: '20' - name: Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - name: Setup pnpm cache uses: actions/cache@v4 with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: Rust cache uses: swatinem/rust-cache@v2 with: workspaces: ". -> target" key: ${{ matrix.platform }}-${{ matrix.target }} # Platform-specific dependencies - name: Install dependencies (Ubuntu 24.04) if: matrix.platform == 'ubuntu-24.04' run: | sudo apt-get update sudo apt-get install -y libappindicator3-dev librsvg2-dev patchelf libasound2-dev libopenblas-dev libx11-dev libxtst-dev libxrandr-dev \ libwebkit2gtk-4.1-0=2.44.0-2 \ libwebkit2gtk-4.1-dev=2.44.0-2 \ libjavascriptcoregtk-4.1-0=2.44.0-2 \ libjavascriptcoregtk-4.1-dev=2.44.0-2 \ gir1.2-javascriptcoregtk-4.1=2.44.0-2 \ gir1.2-webkit2-4.1=2.44.0-2 \ fuse libfuse2 - name: Install dependencies (Ubuntu 22.04) if: matrix.platform == 'ubuntu-22.04' run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libasound2-dev libopenblas-dev libx11-dev libxtst-dev libxrandr-dev fuse libfuse2 - name: Prepare Vulkan SDK (Ubuntu 24.04) if: matrix.platform == 'ubuntu-24.04' run: | wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.3.290-noble.list https://packages.lunarg.com/vulkan/1.3.290/lunarg-vulkan-1.3.290-noble.list sudo apt update sudo apt install vulkan-sdk -y sudo apt-get install -y mesa-vulkan-drivers - name: Prepare Vulkan SDK (Ubuntu 22.04) if: matrix.platform == 'ubuntu-22.04' run: | wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.3.290-jammy.list https://packages.lunarg.com/vulkan/1.3.290/lunarg-vulkan-1.3.290-jammy.list sudo apt update sudo apt install vulkan-sdk -y sudo apt-get install -y mesa-vulkan-drivers - name: Install Vulkan SDK (Windows) if: contains(matrix.platform, 'windows') uses: humbletim/install-vulkan-sdk@v1.2 with: version: 1.4.309.0 cache: true - name: Install frontend dependencies run: | cd frontend pnpm install # macOS signing setup - name: Import Apple Developer Certificate if: contains(matrix.platform, 'macos') && github.event.inputs.sign-build == 'true' env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security default-keychain -s build.keychain security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security set-keychain-settings -t 3600 -u 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 "$KEYCHAIN_PASSWORD" build.keychain security find-identity -v -p codesigning build.keychain - name: Verify Apple certificate if: contains(matrix.platform, 'macos') && github.event.inputs.sign-build == 'true' run: | CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application") CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV echo "Certificate imported: $CERT_ID" # Windows signing setup - name: Setup DigiCert KeyLocker if: contains(matrix.platform, 'windows') && github.event.inputs.sign-build == 'true' uses: digicert/ssm-code-signing@v1.1.1 - name: Setup DigiCert Environment if: contains(matrix.platform, 'windows') && github.event.inputs.sign-build == 'true' shell: pwsh run: | # Set environment variables "SM_HOST=${{ secrets.SM_HOST }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_API_KEY=${{ secrets.SM_API_KEY }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "SM_CODE_SIGNING_CERT_SHA1_HASH=${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append # Decode and save client certificate using PowerShell $certPath = "D:\Certificate_pkcs12.p12" $base64String = "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" $certBytes = [System.Convert]::FromBase64String($base64String) [System.IO.File]::WriteAllBytes($certPath, $certBytes) # Verify the certificate file was created and has content if (Test-Path $certPath) { $certFile = Get-Item $certPath Write-Host "Certificate file created: $certPath" Write-Host " Size: $($certFile.Length) bytes" Write-Host " Last Modified: $($certFile.LastWriteTime)" # Verify it's a valid PKCS12 file by checking if we can load it try { $testCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, "${{ secrets.SM_CLIENT_CERT_PASSWORD }}") Write-Host " Certificate file is valid PKCS12 format" Write-Host " Password is correct" Write-Host " Certificate Subject: $($testCert.Subject)" Write-Host " Certificate Thumbprint: $($testCert.Thumbprint)" } catch { Write-Warning "Certificate validation failed" Write-Warning "Error: $($_.Exception.Message)" Write-Warning "This may cause issues with signing, but we'll continue..." } } else { Write-Error "Certificate file was not created at $certPath" exit 1 } # Set the environment variable with Windows-style path "SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Verify DigiCert Setup if: contains(matrix.platform, 'windows') && github.event.inputs.sign-build == 'true' shell: pwsh run: | smctl --version smctl keypair ls smctl healthcheck - name: Sync Certificate to Windows Certificate Store if: contains(matrix.platform, 'windows') && github.event.inputs.sign-build == 'true' shell: pwsh run: | Write-Host "============================================================" Write-Host "=== SYNCING CERTIFICATE TO WINDOWS CERTIFICATE STORE ===" Write-Host "============================================================" Write-Host "This step syncs the DigiCert certificate from KeyLocker HSM" Write-Host "to the Windows Certificate Store (required for signing)" Write-Host "" # First, get the keypair alias from smctl keypair ls Write-Host "Retrieving keypair alias from DigiCert KeyLocker..." $keypairOutput = smctl keypair ls 2>&1 | Out-String Write-Host $keypairOutput # Extract the alias (looking for pattern like "key_XXXXXXXXXX") $aliasMatch = [regex]::Match($keypairOutput, 'key_\d+') if ($aliasMatch.Success) { $keypairAlias = $aliasMatch.Value Write-Host "Found keypair alias: $keypairAlias" } else { Write-Error "Could not find keypair alias in smctl output" Write-Error "Output was: $keypairOutput" exit 1 } Write-Host "" Write-Host "Syncing certificate using alias: $keypairAlias" $certsyncOutput = smctl windows certsync --keypair-alias $keypairAlias 2>&1 Write-Host $certsyncOutput if ($LASTEXITCODE -ne 0) { Write-Host "" Write-Error "Certificate sync FAILED" Write-Error "Exit code: $LASTEXITCODE" Write-Error "Output: $certsyncOutput" Write-Error "" Write-Error "Possible causes:" Write-Error " 1. Keypair alias '$keypairAlias' not found in KeyLocker" Write-Error " 2. Certificate is revoked or expired" Write-Error " 3. API credentials are invalid" exit 1 } Write-Host "" Write-Host "Certificate synced successfully" Write-Host "" # Verify certificate is now in Windows store Write-Host "Verifying certificate in Windows Certificate Store..." $cert = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1 if (-not $cert) { # Try LocalMachine store $cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1 } if ($cert) { Write-Host "Certificate found in Windows Certificate Store" Write-Host " Subject: $($cert.Subject)" Write-Host " Issuer: $($cert.Issuer)" Write-Host " Thumbprint: $($cert.Thumbprint)" Write-Host " Valid From: $($cert.NotBefore)" Write-Host " Valid Until: $($cert.NotAfter)" } else { Write-Warning "Certificate not found in Windows Certificate Store after sync" Write-Warning "Signing may fail. Continuing anyway..." } # Export keypair alias for Tauri signCommand Write-Host "" Write-Host "Exporting DIGICERT_KEYPAIR_ALIAS for Tauri signCommand..." "DIGICERT_KEYPAIR_ALIAS=$keypairAlias" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append Write-Host "DIGICERT_KEYPAIR_ALIAS=$keypairAlias" Write-Host "" Write-Host "============================================================" Write-Host "" # Determine build features for acceleration - name: Determine build features id: build-features shell: bash run: | FEATURES="" # Windows: Use Vulkan for GPU acceleration if [[ "${{ matrix.platform }}" == *"windows"* ]]; then FEATURES="--features vulkan" echo "Windows build with Vulkan GPU acceleration" fi # Linux: Use OpenBLAS for optimized CPU performance if [[ "${{ matrix.platform }}" == *"ubuntu"* ]]; then FEATURES="--features openblas" echo "Linux build with OpenBLAS CPU optimization" fi # macOS: Uses Metal by default, no additional features needed if [[ "${{ matrix.platform }}" == *"macos"* ]]; then echo "macOS build with Metal GPU acceleration (default)" fi echo "features=$FEATURES" >> "$GITHUB_OUTPUT" - name: Build llama-helper sidecar shell: bash run: | echo "Building llama-helper sidecar..." # Determine llama-helper features based on platform LLAMA_FEATURES="" if [[ "${{ matrix.platform }}" == *"macos"* ]]; then LLAMA_FEATURES="--features metal" echo "Using Metal GPU acceleration for macOS" elif [[ "${{ matrix.platform }}" == *"windows"* ]]; then LLAMA_FEATURES="--features vulkan" echo "Using Vulkan GPU acceleration for Windows" else echo "Using CPU-only mode for Linux" fi # Build llama-helper from workspace root cargo build --release -p llama-helper $LLAMA_FEATURES # Determine binary extension EXT="" if [[ "${{ matrix.platform }}" == *"windows"* ]]; then EXT=".exe" fi # Copy binary to binaries directory mkdir -p frontend/src-tauri/binaries cp target/release/llama-helper${EXT} frontend/src-tauri/binaries/llama-helper-${{ matrix.target }}${EXT} echo "Copied llama-helper to frontend/src-tauri/binaries/llama-helper-${{ matrix.target }}${EXT}" ls -la frontend/src-tauri/binaries/ # Build step (with code signing) - name: Build Tauri app (with code signing) if: github.event.inputs.sign-build == 'true' uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # macOS platform code signing APPLE_ID: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_ID || '' }} APPLE_ID_PASSWORD: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_ID_PASSWORD || '' }} APPLE_PASSWORD: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_PASSWORD || '' }} APPLE_TEAM_ID: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_TEAM_ID || '' }} APPLE_CERTIFICATE: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_CERTIFICATE || '' }} APPLE_CERTIFICATE_PASSWORD: ${{ contains(matrix.platform, 'macos') && secrets.APPLE_CERTIFICATE_PASSWORD || '' }} APPLE_SIGNING_IDENTITY: ${{ contains(matrix.platform, 'macos') && env.CERT_ID || '' }} # Tauri updater signing (ALWAYS enabled for .sig files) TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} # License validation RSA public key (embedded at build time) MEETILY_RSA_PUBLIC_KEY: ${{ secrets.MEETILY_RSA_PUBLIC_KEY }} # Supabase configuration (for online license verification) SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} with: projectPath: frontend args: ${{ matrix.args }} ${{ steps.build-features.outputs.features }} # Build step (without code signing - updater signing only) - name: Build Tauri app (unsigned) if: github.event.inputs.sign-build != 'true' uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Tauri updater signing (ALWAYS enabled for .sig files) TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} # License validation RSA public key (embedded at build time) MEETILY_RSA_PUBLIC_KEY: ${{ secrets.MEETILY_RSA_PUBLIC_KEY }} # Supabase configuration (for online license verification) SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} with: projectPath: frontend args: ${{ matrix.args }} ${{ steps.build-features.outputs.features }} # Linux post-build processing - name: Remove libwayland-client.so from AppImage if: contains(matrix.platform, 'ubuntu-24.04') run: | APPIMAGE_PATH=$(find target/x86_64-unknown-linux-gnu/release/bundle/appimage -name "*.AppImage" | head -1) if [ -n "$APPIMAGE_PATH" ]; then echo "Processing AppImage: $APPIMAGE_PATH" chmod +x "$APPIMAGE_PATH" cd "$(dirname "$APPIMAGE_PATH")" APPIMAGE_NAME=$(basename "$APPIMAGE_PATH") "./$APPIMAGE_NAME" --appimage-extract find squashfs-root -name "libwayland-client.so*" -type f -delete wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage chmod +x appimagetool-x86_64.AppImage ARCH=x86_64 ./appimagetool-x86_64.AppImage --no-appstream squashfs-root "$APPIMAGE_NAME" rm -rf squashfs-root appimagetool-x86_64.AppImage echo "AppImage processed successfully" fi # Verification steps - name: Verify build artifacts shell: bash run: | echo "Build artifacts:" find target -name "*.dmg" -o -name "*.app" -o -name "*.msi" -o -name "*.exe" -o -name "*.deb" -o -name "*.AppImage" -o -name "*.rpm" 2>/dev/null || echo "Finding artifacts..." - name: Verify code signing (macOS App) if: contains(matrix.platform, 'macos') && github.event.inputs.sign-build == 'true' run: | APP_PATH=$(find target/aarch64-apple-darwin/release/bundle/macos -name "*.app" | head -1) if [ -n "$APP_PATH" ]; then echo "=== Verifying App Bundle: $APP_PATH ===" codesign -dv --verbose=4 "$APP_PATH" 2>&1 | grep -i "signature\|authority" codesign --verify --deep --strict "$APP_PATH" && echo "Code signature is valid" spctl -a -vvv "$APP_PATH" && echo "App is notarized and accepted by Gatekeeper" fi DMG_PATH=$(find target/aarch64-apple-darwin/release/bundle/dmg -name "*.dmg" | head -1) if [ -n "$DMG_PATH" ]; then echo "=== Verifying DMG: $DMG_PATH ===" codesign -dv --verbose=4 "$DMG_PATH" 2>&1 | grep -i "signature\|authority" echo "DMG is signed (contains notarized .app)" fi - name: Verify code signing (Windows) if: contains(matrix.platform, 'windows') && github.event.inputs.sign-build == 'true' shell: pwsh run: | $msiPath = Get-ChildItem -Path "target/x86_64-pc-windows-msvc/release/bundle/msi" -Filter "*.msi" | Select-Object -First 1 -ExpandProperty FullName if ($msiPath) { Write-Host "Verifying MSI signature: $msiPath" Get-AuthenticodeSignature -FilePath "$msiPath" | Format-List } # Upload artifacts - name: Upload artifacts if: github.event.inputs.upload-artifacts == 'true' uses: actions/upload-artifact@v4 with: name: meetily-devtest-${{ matrix.platform-name }}-${{ matrix.target }}-${{ steps.get-version.outputs.version }} path: | target/aarch64-apple-darwin/release/bundle/dmg/*.dmg target/aarch64-apple-darwin/release/bundle/macos/*.app target/aarch64-apple-darwin/release/bundle/macos/*.app.tar.gz target/aarch64-apple-darwin/release/bundle/macos/*.app.tar.gz.sig target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi.sig target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe.sig target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm retention-days: 14 # Generate summary - name: Generate build summary shell: bash run: | echo "## DevTest Build Summary - ${{ matrix.platform-name }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- **Version:** ${{ steps.get-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "- **Platform:** ${{ matrix.platform-name }}" >> $GITHUB_STEP_SUMMARY echo "- **Target:** ${{ matrix.target }}" >> $GITHUB_STEP_SUMMARY echo "- **Signed:** ${{ github.event.inputs.sign-build == 'true' && 'Yes' || 'No (default)' }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY