ci: trigger build #214
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: CI Alloomi (Main Branch) | |
| on: | |
| push: | |
| branches: | |
| - main | |
| schedule: | |
| - cron: '0 11 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| commit_sha: | |
| description: 'Specific commit SHA to build (optional)' | |
| required: false | |
| type: string | |
| env: | |
| # CI trigger: bump to re-run on main | |
| NODE_VERSION: '22.14' | |
| RUST_VERSION: '1.93' | |
| ALLOOMI_REPO: 'melandlabs/alloomi' | |
| ALLOOMI_BRANCH: 'main' | |
| jobs: | |
| # ─── MacOS arm64 CI ─────────────────────────────────────────────────────────────── | |
| ci-alloomi-macos-arm64: | |
| runs-on: macos-14 | |
| env: | |
| SKIP_SIGNING: 'false' | |
| steps: | |
| - name: Checkout release repo | |
| uses: actions/checkout@v4 | |
| - name: Clone alloomi source code (main branch) | |
| run: | | |
| git clone https://x-access-token:${{ secrets.DEPLOY_ACCESS_TOKEN }}@github.com/${{ env.ALLOOMI_REPO }}.git alloomi | |
| cd alloomi | |
| if [ "${{ github.event.inputs.commit_sha }}" != "" ]; then | |
| git checkout ${{ github.event.inputs.commit_sha }} | |
| else | |
| git checkout ${{ env.ALLOOMI_BRANCH }} | |
| fi | |
| echo "Commit: $(git log -1 --oneline)" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: 9 | |
| - name: Add pnpm to PATH | |
| run: | | |
| echo "$HOME/.local/bin" >> $GITHUB_PATH | |
| echo "PNPM_HOME=$HOME/.local" >> $GITHUB_ENV | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: ${{ env.RUST_VERSION }} | |
| - name: Install Rust targets | |
| run: | | |
| rustup target add aarch64-apple-darwin | |
| rustup target add x86_64-apple-darwin | |
| - name: Get pnpm store directory | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v3 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('alloomi/**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Install dependencies | |
| working-directory: ./alloomi | |
| run: pnpm install --frozen-lockfile --ignore-scripts | |
| - name: Install better-sqlite3 prebuilt binary | |
| working-directory: ./alloomi | |
| run: | | |
| pnpm install -w better-sqlite3@12.11.1 | |
| mkdir -p node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build | |
| cp node_modules/better-sqlite3/build/Release/better_sqlite3.node \ | |
| node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build/better_sqlite3.node | |
| - name: Verify native modules are built | |
| working-directory: ./alloomi | |
| run: | | |
| echo "=== Checking top-level better-sqlite3 ===" | |
| ls -la node_modules/better-sqlite3/build/Release/better_sqlite3.node || exit 1 | |
| echo "=== Checking nested better-sqlite3 in imessage-kit ===" | |
| ls -la node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build/better_sqlite3.node || exit 1 | |
| echo "✅ All native modules built successfully" | |
| - name: Create .env file | |
| working-directory: ./alloomi/apps/web | |
| run: | | |
| cat > .env << 'EOF' | |
| # Cloud API Configuration for Tauri Desktop App | |
| CLOUD_API_URL=https://app.alloomi.ai | |
| NEXT_PUBLIC_CLOUD_API_URL=https://app.alloomi.ai | |
| # Local development URL (for Tauri mode) | |
| NEXT_PUBLIC_APP_URL=http://localhost:3415 | |
| LLM_BASE_URL=https://openrouter.ai/api/v1 | |
| LLM_MODEL=google/gemini-3-flash-preview | |
| LLM_REASONING_MODEL=google/gemini-3-flash-preview | |
| LLM_VISION_LANGUAGE_MODEL=google/gemini-3-flash-preview | |
| LLM_IMAGE_MODEL=openai/gpt-5-image | |
| LLM_EMBEDDING_MODEL=qwen/qwen3-embedding-4b | |
| LLM_EMBEDDING_BASE_URL=https://openrouter.ai/api/v1 | |
| TELEGRAM_MODE=pooling | |
| ANTHROPIC_BASE_URL=http://localhost:3415/api/ai | |
| API_TIMEOUT_MS=3000000 | |
| IS_TAURI=true | |
| EOF | |
| - name: Import Apple Certificate and Intermediate Certificates | |
| if: env.SKIP_SIGNING != 'true' | |
| env: | |
| MACOSX_DEPLOYMENT_TARGET: "12.0" | |
| APPLE_CERTIFICATE: "${{ secrets.ED_APPLE_CERTIFICATE }}" | |
| APPLE_CERTIFICATE_PASSWORD: "${{ secrets.ED_APPLE_CERTIFICATE_PASSWORD }}" | |
| run: | | |
| # Create a new keychain | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| security create-keychain -p "" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "" $KEYCHAIN_PATH | |
| # Import Apple certificate | |
| echo "$APPLE_CERTIFICATE" | base64 --decode > cert.p12 | |
| security import cert.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -k $KEYCHAIN_PATH | |
| rm cert.p12 | |
| # Download and import Apple intermediate certificates (not available in CI) | |
| curl -s -o /tmp/AppleWWDRCAG3.cer https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer | |
| curl -s -o /tmp/DeveloperIDG2CA.cer https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer | |
| security import /tmp/AppleWWDRCAG3.cer -A -k $KEYCHAIN_PATH | |
| security import /tmp/DeveloperIDG2CA.cer -A -k $KEYCHAIN_PATH | |
| # List keychains to ensure system can access certificates | |
| security list-keychain -d user -s $KEYCHAIN_PATH | |
| - name: Build Tauri app (Apple Silicon) | |
| working-directory: ./alloomi/apps/web | |
| run: pnpm tauri:build | |
| env: | |
| MACOSX_DEPLOYMENT_TARGET: "12.0" | |
| APPLE_CERTIFICATE: "${{ secrets.ED_APPLE_CERTIFICATE }}" | |
| APPLE_CERTIFICATE_PASSWORD: "${{ secrets.ED_APPLE_CERTIFICATE_PASSWORD }}" | |
| APPLE_SIGNING_IDENTITY: "${{ secrets.ED_APPLE_SIGNING_IDENTITY }}" | |
| SIGNING_IDENTITY: "${{ secrets.ED_APPLE_SIGNING_IDENTITY }}" | |
| SKIP_SIGNING: 'false' | |
| - name: Verify finalized app signatures | |
| if: env.SKIP_SIGNING != 'true' | |
| run: | | |
| set -e | |
| APP_BUNDLE="./alloomi/apps/web/src-tauri/target/release/bundle/macos/Alloomi.app" | |
| codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE" | |
| echo "✅ App bundle signatures verified" | |
| - name: Create DMG (beautified, from signed app) | |
| env: | |
| # Build-step env is scoped per-step, so re-inject the identity used | |
| # to sign the DMG container itself. | |
| APPLE_SIGNING_IDENTITY: "${{ secrets.ED_APPLE_SIGNING_IDENTITY }}" | |
| run: | | |
| set -e | |
| APP_BUNDLE="./alloomi/apps/web/src-tauri/target/release/bundle/macos/Alloomi.app" | |
| DMG_DIR="./alloomi/apps/web/src-tauri/target/release/bundle/dmg" | |
| SRC_TAURI="./alloomi/apps/web/src-tauri" | |
| mkdir -p "$DMG_DIR" | |
| # optimize-tauri-bundle.js has already finalized $APP_BUNDLE using | |
| # Alloomi's inside-out signer. beautify-dmg.js only | |
| # `cp -R`s the .app into the DMG (never modifies its contents), so the | |
| # signature survives intact → notarization can cover the inner .app → | |
| # no more "已损坏,无法打开" on install. (Previously we copied the DMG | |
| # tauri:build emitted, whose inner .app was signed BEFORE the Developer | |
| # ID step, so the DMG shipped an unsigned ad-hoc .app.) | |
| echo "Verifying app signature before packing DMG..." | |
| codesign -dv "$APP_BUNDLE" 2>&1 | head -5 | |
| # Drop any DMG tauri:build left behind so the glob below is unambiguous. | |
| rm -f "$DMG_DIR"/Alloomi*.dmg | |
| # Regenerate the beautified DMG against the SIGNED .app. The script | |
| # auto-discovers appPath + outDmg from target/release/bundle, reads the | |
| # version from tauri.conf.json, and emits Alloomi_<version>_aarch64.dmg. | |
| # Under CI (env CI=true) it uses the committed .DS_Store template | |
| # (resources/dmg_DS_Store) since the runner has no Finder/Aqua session. | |
| pushd "$SRC_TAURI" >/dev/null | |
| node scripts/beautify-dmg.js | |
| popd >/dev/null | |
| VERSION=$(node -p "require('./alloomi/apps/web/src-tauri/tauri.conf.json').version") | |
| BEAUTIFIED_DMG="$DMG_DIR/Alloomi_${VERSION}_aarch64.dmg" | |
| if [ ! -f "$BEAUTIFIED_DMG" ]; then | |
| echo "❌ Beautified DMG not found: $BEAUTIFIED_DMG" | |
| ls -la "$DMG_DIR" 2>/dev/null || true | |
| exit 1 | |
| fi | |
| # Guard against beautify-dmg.js failing silently: a DMG with no | |
| # .background/ means the layout never landed. | |
| ATTACH=$(hdiutil attach -nobrowse -noverify "$BEAUTIFIED_DMG" | grep -o '/Volumes/Alloomi.*' | tail -1) | |
| if [ -z "$ATTACH" ] || [ ! -d "$ATTACH/.background" ]; then | |
| echo "❌ DMG has no .background/ — beautification did not run" | |
| [ -n "$ATTACH" ] && hdiutil detach "$ATTACH" -force | |
| exit 1 | |
| fi | |
| hdiutil detach "$ATTACH" -force | |
| # Rename to the CI convention; downstream Notarize/Staple/Upload glob | |
| # Alloomi*.dmg, so we leave exactly ONE DMG in dmg/. | |
| COMMIT_SHA=$(cd alloomi && git rev-parse --short HEAD) | |
| DMG_PATH="$DMG_DIR/Alloomi_ci_${COMMIT_SHA}_aarch64.dmg" | |
| mv "$BEAUTIFIED_DMG" "$DMG_PATH" | |
| # Sign the DMG *container* itself. notarytool requires the submitted | |
| # DMG (not just the inner .app) to carry a Developer ID signature; | |
| # otherwise `xcrun notarytool submit` fails with | |
| # "code object is not signed at all". beautify-dmg.js only `hdiutil`s | |
| # the already-signed .app into the DMG and never signs the DMG, so we | |
| # add it here: Sign app → pack DMG → sign DMG → notarize → staple. | |
| codesign --force --sign "$APPLE_SIGNING_IDENTITY" "$DMG_PATH" | |
| codesign -dv "$DMG_PATH" 2>&1 | head -5 | |
| echo "✅ Beautified DMG (from signed app) at: $DMG_PATH" | |
| ls -la "$DMG_PATH" | |
| - name: Notarize DMG | |
| if: env.SKIP_SIGNING != 'true' | |
| timeout-minutes: 360 | |
| run: | | |
| DMG_PATH=$(ls ./alloomi/apps/web/src-tauri/target/release/bundle/dmg/Alloomi*.dmg) | |
| echo "Notarizing: $DMG_PATH" | |
| codesign -dv "$DMG_PATH" 2>&1 | head -5 | |
| APPLE_ID="${{ secrets.ED_APPLE_ID }}" | |
| APPLE_PASSWORD="${{ secrets.ED_APPLE_PASSWORD }}" | |
| APPLE_TEAM_ID="${{ secrets.ED_APPLE_TEAM_ID }}" | |
| # `set -e` (default for GitHub Actions `run:` blocks) aborts the whole | |
| # script on a failing command substitution, so a failed `notarytool | |
| # submit` would exit BEFORE the `echo "$OUTPUT"` that prints its error | |
| # — leaving only "Signature size=9045" then "exit 1" in the log. Run | |
| # submit under `set +e` and capture output without aborting, so the | |
| # real notarytool error (auth failure, code rejection, etc.) always | |
| # surfaces. | |
| set +e | |
| OUTPUT=$(xcrun notarytool submit "$DMG_PATH" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" 2>&1) | |
| SUBMIT_EXIT=$? | |
| set -e | |
| echo "----- notarytool submit output (exit=$SUBMIT_EXIT) -----" | |
| echo "$OUTPUT" | |
| echo "--------------------------------------------------------" | |
| if [ "$SUBMIT_EXIT" -ne 0 ]; then | |
| echo "❌ notarytool submit failed (exit=$SUBMIT_EXIT). See output above." | |
| exit 1 | |
| fi | |
| SUBMISSION_ID=$(echo "$OUTPUT" | grep "id:" | head -1 | awk '{print $2}') | |
| if [ -z "$SUBMISSION_ID" ]; then | |
| echo "❌ notarytool submit returned no submission id. See output above." | |
| exit 1 | |
| fi | |
| echo "Submission ID: $SUBMISSION_ID" | |
| echo "Waiting for notarization to complete..." | |
| set +e | |
| WAIT_OUTPUT=$(xcrun notarytool wait "$SUBMISSION_ID" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" 2>&1) | |
| WAIT_EXIT=$? | |
| set -e | |
| echo "----- notarytool wait output (exit=$WAIT_EXIT) -----" | |
| echo "$WAIT_OUTPUT" | |
| echo "--------------------------------------------------------" | |
| if echo "$WAIT_OUTPUT" | grep -q "status: Rejected"; then | |
| echo "❌ Notarization rejected. Fetching Apple's detailed log..." | |
| # The Apple-side log explains exactly why the DMG/.app was rejected | |
| # (e.g. "the binary is not signed", "code failed to satisfy | |
| # designated code requirement"). This is the decisive diagnostic. | |
| xcrun notarytool log "$SUBMISSION_ID" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" 2>&1 || true | |
| exit 1 | |
| elif echo "$WAIT_OUTPUT" | grep -q "status: Accepted"; then | |
| echo "✅ Notarization accepted!" | |
| else | |
| echo "❌ Notarization status unclear (exit=$WAIT_EXIT). See output above." | |
| exit 1 | |
| fi | |
| echo "Waiting for ticket propagation..." | |
| sleep 300 | |
| - name: Staple DMG | |
| if: env.SKIP_SIGNING != 'true' | |
| continue-on-error: true | |
| run: | | |
| DMG_PATH=$(ls ./alloomi/apps/web/src-tauri/target/release/bundle/dmg/Alloomi*.dmg) | |
| echo "Stapling: $DMG_PATH" | |
| STAPLED=false | |
| for i in $(seq 1 20); do | |
| echo "Staple attempt $i..." | |
| if xcrun stapler staple "$DMG_PATH" 2>&1; then | |
| echo "✅ Staple succeeded on attempt $i" | |
| STAPLED=true | |
| break | |
| else | |
| echo "Staple failed, waiting 60 seconds before retry..." | |
| sleep 60 | |
| fi | |
| done | |
| if [ "$STAPLED" = "true" ]; then | |
| if xcrun stapler validate "$DMG_PATH" 2>&1; then | |
| echo "✅ DMG stapled and validated successfully" | |
| else | |
| echo "❌ DMG stapled but validation failed" | |
| exit 1 | |
| fi | |
| else | |
| echo "⚠️ Failed to staple DMG after 20 attempts" | |
| fi | |
| - name: Upload CI DMG | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: alloomi-ci-dmg-macos | |
| path: ${{ github.workspace }}/alloomi/apps/web/src-tauri/target/release/bundle/dmg/Alloomi*.dmg | |
| # ─── macOS amd64 CI ──────────────────────────────────────────────────────── | |
| ci-alloomi-macos-amd64: | |
| runs-on: macos-15-intel | |
| env: | |
| SKIP_SIGNING: 'false' | |
| steps: | |
| - name: Checkout release repo | |
| uses: actions/checkout@v4 | |
| - name: Clone alloomi source code (main branch) | |
| run: | | |
| git clone https://x-access-token:${{ secrets.DEPLOY_ACCESS_TOKEN }}@github.com/${{ env.ALLOOMI_REPO }}.git alloomi | |
| cd alloomi | |
| if [ "${{ github.event.inputs.commit_sha }}" != "" ]; then | |
| git checkout ${{ github.event.inputs.commit_sha }} | |
| else | |
| git checkout ${{ env.ALLOOMI_BRANCH }} | |
| fi | |
| echo "Commit: $(git log -1 --oneline)" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: 9 | |
| - name: Add pnpm to PATH | |
| run: | | |
| echo "$HOME/.local/bin" >> $GITHUB_PATH | |
| echo "PNPM_HOME=$HOME/.local" >> $GITHUB_ENV | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: ${{ env.RUST_VERSION }} | |
| - name: Install Rust targets | |
| run: | | |
| rustup target add aarch64-apple-darwin | |
| rustup target add x86_64-apple-darwin | |
| - name: Get pnpm store directory | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v3 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('alloomi/**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Install dependencies | |
| working-directory: ./alloomi | |
| run: pnpm install --frozen-lockfile --ignore-scripts | |
| - name: Install better-sqlite3 prebuilt binary (x86_64) | |
| working-directory: ./alloomi | |
| env: | |
| npm_config_arch: x64 | |
| run: | | |
| pnpm install -w better-sqlite3@12.11.1 | |
| mkdir -p node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build | |
| cp node_modules/better-sqlite3/build/Release/better_sqlite3.node \ | |
| node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build/better_sqlite3.node | |
| - name: Verify native modules are built | |
| working-directory: ./alloomi | |
| run: | | |
| echo "=== Checking top-level better-sqlite3 ===" | |
| ls -la node_modules/better-sqlite3/build/Release/better_sqlite3.node || exit 1 | |
| echo "=== Checking nested better-sqlite3 in imessage-kit ===" | |
| ls -la node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build/better_sqlite3.node || exit 1 | |
| echo "✅ All native modules built successfully" | |
| - name: Create .env file | |
| working-directory: ./alloomi/apps/web | |
| run: | | |
| cat > .env << 'EOF' | |
| CLOUD_API_URL=https://app.alloomi.ai | |
| NEXT_PUBLIC_CLOUD_API_URL=https://app.alloomi.ai | |
| NEXT_PUBLIC_APP_URL=http://localhost:3415 | |
| LLM_BASE_URL=https://openrouter.ai/api/v1 | |
| LLM_MODEL=google/gemini-3-flash-preview | |
| LLM_REASONING_MODEL=google/gemini-3-flash-preview | |
| LLM_VISION_LANGUAGE_MODEL=google/gemini-3-flash-preview | |
| LLM_IMAGE_MODEL=openai/gpt-5-image | |
| LLM_EMBEDDING_MODEL=qwen/qwen3-embedding-4b | |
| LLM_EMBEDDING_BASE_URL=https://openrouter.ai/api/v1 | |
| TELEGRAM_MODE=pooling | |
| ANTHROPIC_BASE_URL=http://localhost:3415/api/ai | |
| API_TIMEOUT_MS=3000000 | |
| IS_TAURI=true | |
| EOF | |
| - name: Import Apple Certificate and Intermediate Certificates | |
| if: env.SKIP_SIGNING != 'true' | |
| env: | |
| MACOSX_DEPLOYMENT_TARGET: "12.0" | |
| APPLE_CERTIFICATE: "${{ secrets.ED_APPLE_CERTIFICATE }}" | |
| APPLE_CERTIFICATE_PASSWORD: "${{ secrets.ED_APPLE_CERTIFICATE_PASSWORD }}" | |
| run: | | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| security create-keychain -p "" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "" $KEYCHAIN_PATH | |
| echo "$APPLE_CERTIFICATE" | base64 --decode > cert.p12 | |
| security import cert.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -k $KEYCHAIN_PATH | |
| rm cert.p12 | |
| curl -s -o /tmp/AppleWWDRCAG3.cer https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer | |
| curl -s -o /tmp/DeveloperIDG2CA.cer https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer | |
| security import /tmp/AppleWWDRCAG3.cer -A -k $KEYCHAIN_PATH | |
| security import /tmp/DeveloperIDG2CA.cer -A -k $KEYCHAIN_PATH | |
| security list-keychain -d user -s $KEYCHAIN_PATH | |
| - name: Patch tauri.conf.json to skip bundler on macOS (Intel) | |
| working-directory: ./alloomi/apps/web | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const c = JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json', 'utf8')); | |
| c.bundle.targets = ['app']; | |
| fs.writeFileSync('src-tauri/tauri.conf.json', JSON.stringify(c, null, 2)); | |
| console.log('Patched targets to app'); | |
| " | |
| - name: Build Tauri app (Intel) | |
| working-directory: ./alloomi/apps/web | |
| run: pnpm tauri:build | |
| env: | |
| MACOSX_DEPLOYMENT_TARGET: "12.0" | |
| APPLE_CERTIFICATE: "${{ secrets.ED_APPLE_CERTIFICATE }}" | |
| APPLE_CERTIFICATE_PASSWORD: "${{ secrets.ED_APPLE_CERTIFICATE_PASSWORD }}" | |
| APPLE_SIGNING_IDENTITY: "${{ secrets.ED_APPLE_SIGNING_IDENTITY }}" | |
| SIGNING_IDENTITY: "${{ secrets.ED_APPLE_SIGNING_IDENTITY }}" | |
| SKIP_SIGNING: 'false' | |
| - name: Verify finalized app signatures | |
| if: env.SKIP_SIGNING != 'true' | |
| run: | | |
| set -e | |
| APP_BUNDLE="./alloomi/apps/web/src-tauri/target/release/bundle/macos/Alloomi.app" | |
| codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE" | |
| echo "✅ App bundle signatures verified" | |
| - name: Create DMG (beautified, from signed app) | |
| env: | |
| # Build-step env is scoped per-step, so re-inject the identity used | |
| # to sign the DMG container itself. | |
| APPLE_SIGNING_IDENTITY: "${{ secrets.ED_APPLE_SIGNING_IDENTITY }}" | |
| run: | | |
| set -e | |
| APP_BUNDLE="./alloomi/apps/web/src-tauri/target/release/bundle/macos/Alloomi.app" | |
| DMG_DIR="./alloomi/apps/web/src-tauri/target/release/bundle/dmg" | |
| SRC_TAURI="./alloomi/apps/web/src-tauri" | |
| mkdir -p "$DMG_DIR" | |
| # optimize-tauri-bundle.js has already finalized $APP_BUNDLE using | |
| # Alloomi's inside-out signer. beautify-dmg.js only | |
| # `cp -R`s the .app into the DMG (never modifies its contents), so the | |
| # signature survives intact → notarization can cover the inner .app → | |
| # no more "已损坏,无法打开" on install. (Previously we copied the DMG | |
| # tauri:build emitted, whose inner .app was signed BEFORE the Developer | |
| # ID step, so the DMG shipped an unsigned ad-hoc .app.) | |
| echo "Verifying app signature before packing DMG..." | |
| codesign -dv "$APP_BUNDLE" 2>&1 | head -5 | |
| # Drop any DMG tauri:build left behind so the glob below is unambiguous. | |
| rm -f "$DMG_DIR"/Alloomi*.dmg | |
| # Regenerate the beautified DMG against the SIGNED .app. The script | |
| # auto-discovers appPath + outDmg from target/release/bundle, reads the | |
| # version from tauri.conf.json, and emits Alloomi_<version>_x64.dmg. | |
| # Under CI (env CI=true) it uses the committed .DS_Store template | |
| # (resources/dmg_DS_Store) since the runner has no Finder/Aqua session. | |
| pushd "$SRC_TAURI" >/dev/null | |
| node scripts/beautify-dmg.js | |
| popd >/dev/null | |
| VERSION=$(node -p "require('./alloomi/apps/web/src-tauri/tauri.conf.json').version") | |
| BEAUTIFIED_DMG="$DMG_DIR/Alloomi_${VERSION}_x64.dmg" | |
| if [ ! -f "$BEAUTIFIED_DMG" ]; then | |
| echo "❌ Beautified DMG not found: $BEAUTIFIED_DMG" | |
| ls -la "$DMG_DIR" 2>/dev/null || true | |
| exit 1 | |
| fi | |
| # Guard against beautify-dmg.js failing silently: a DMG with no | |
| # .background/ means the layout never landed. | |
| ATTACH=$(hdiutil attach -nobrowse -noverify "$BEAUTIFIED_DMG" | grep -o '/Volumes/Alloomi.*' | tail -1) | |
| if [ -z "$ATTACH" ] || [ ! -d "$ATTACH/.background" ]; then | |
| echo "❌ DMG has no .background/ — beautification did not run" | |
| [ -n "$ATTACH" ] && hdiutil detach "$ATTACH" -force | |
| exit 1 | |
| fi | |
| hdiutil detach "$ATTACH" -force | |
| # Rename to the CI convention; downstream Notarize/Staple/Upload glob | |
| # Alloomi*.dmg, so we leave exactly ONE DMG in dmg/. | |
| COMMIT_SHA=$(cd alloomi && git rev-parse --short HEAD) | |
| DMG_PATH="$DMG_DIR/Alloomi_ci_${COMMIT_SHA}_x64.dmg" | |
| mv "$BEAUTIFIED_DMG" "$DMG_PATH" | |
| # Sign the DMG *container* itself. notarytool requires the submitted | |
| # DMG (not just the inner .app) to carry a Developer ID signature; | |
| # otherwise `xcrun notarytool submit` fails with | |
| # "code object is not signed at all". beautify-dmg.js only `hdiutil`s | |
| # the already-signed .app into the DMG and never signs the DMG, so we | |
| # add it here: Sign app → pack DMG → sign DMG → notarize → staple. | |
| codesign --force --sign "$APPLE_SIGNING_IDENTITY" "$DMG_PATH" | |
| codesign -dv "$DMG_PATH" 2>&1 | head -5 | |
| echo "✅ Beautified DMG (from signed app) at: $DMG_PATH" | |
| ls -la "$DMG_PATH" | |
| - name: Notarize DMG | |
| if: env.SKIP_SIGNING != 'true' | |
| timeout-minutes: 60 | |
| run: | | |
| DMG_PATH=$(ls ./alloomi/apps/web/src-tauri/target/release/bundle/dmg/Alloomi*.dmg) | |
| echo "Notarizing: $DMG_PATH" | |
| codesign -dv "$DMG_PATH" 2>&1 | head -5 | |
| APPLE_ID="${{ secrets.ED_APPLE_ID }}" | |
| APPLE_PASSWORD="${{ secrets.ED_APPLE_PASSWORD }}" | |
| APPLE_TEAM_ID="${{ secrets.ED_APPLE_TEAM_ID }}" | |
| # `set -e` (default for GitHub Actions `run:` blocks) aborts the whole | |
| # script on a failing command substitution, so a failed `notarytool | |
| # submit` would exit BEFORE the `echo "$OUTPUT"` that prints its error | |
| # — leaving only "Signature size=9045" then "exit 1" in the log. Run | |
| # submit under `set +e` and capture output without aborting, so the | |
| # real notarytool error (auth failure, code rejection, etc.) always | |
| # surfaces. | |
| set +e | |
| OUTPUT=$(xcrun notarytool submit "$DMG_PATH" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" 2>&1) | |
| SUBMIT_EXIT=$? | |
| set -e | |
| echo "----- notarytool submit output (exit=$SUBMIT_EXIT) -----" | |
| echo "$OUTPUT" | |
| echo "--------------------------------------------------------" | |
| if [ "$SUBMIT_EXIT" -ne 0 ]; then | |
| echo "❌ notarytool submit failed (exit=$SUBMIT_EXIT). See output above." | |
| exit 1 | |
| fi | |
| SUBMISSION_ID=$(echo "$OUTPUT" | grep "id:" | head -1 | awk '{print $2}') | |
| if [ -z "$SUBMISSION_ID" ]; then | |
| echo "❌ notarytool submit returned no submission id. See output above." | |
| exit 1 | |
| fi | |
| echo "Submission ID: $SUBMISSION_ID" | |
| echo "Waiting for notarization to complete..." | |
| set +e | |
| WAIT_OUTPUT=$(xcrun notarytool wait "$SUBMISSION_ID" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" 2>&1) | |
| WAIT_EXIT=$? | |
| set -e | |
| echo "----- notarytool wait output (exit=$WAIT_EXIT) -----" | |
| echo "$WAIT_OUTPUT" | |
| echo "--------------------------------------------------------" | |
| if echo "$WAIT_OUTPUT" | grep -q "status: Rejected"; then | |
| echo "❌ Notarization rejected. Fetching Apple's detailed log..." | |
| # The Apple-side log explains exactly why the DMG/.app was rejected | |
| # (e.g. "the binary is not signed", "code failed to satisfy | |
| # designated code requirement"). This is the decisive diagnostic. | |
| xcrun notarytool log "$SUBMISSION_ID" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" 2>&1 || true | |
| exit 1 | |
| elif echo "$WAIT_OUTPUT" | grep -q "status: Accepted"; then | |
| echo "✅ Notarization accepted!" | |
| else | |
| echo "❌ Notarization status unclear (exit=$WAIT_EXIT). See output above." | |
| exit 1 | |
| fi | |
| echo "Waiting for ticket propagation..." | |
| sleep 300 | |
| - name: Staple DMG | |
| if: env.SKIP_SIGNING != 'true' | |
| continue-on-error: true | |
| run: | | |
| DMG_PATH=$(ls ./alloomi/apps/web/src-tauri/target/release/bundle/dmg/Alloomi*.dmg) | |
| echo "Stapling: $DMG_PATH" | |
| STAPLED=false | |
| for i in $(seq 1 20); do | |
| echo "Staple attempt $i..." | |
| if xcrun stapler staple "$DMG_PATH" 2>&1; then | |
| echo "✅ Staple succeeded on attempt $i" | |
| STAPLED=true | |
| break | |
| else | |
| echo "Staple failed, waiting 60 seconds before retry..." | |
| sleep 60 | |
| fi | |
| done | |
| if [ "$STAPLED" = "true" ]; then | |
| if xcrun stapler validate "$DMG_PATH" 2>&1; then | |
| echo "✅ DMG stapled and validated successfully" | |
| else | |
| echo "❌ DMG stapled but validation failed" | |
| exit 1 | |
| fi | |
| else | |
| echo "⚠️ Failed to staple DMG after 20 attempts" | |
| fi | |
| - name: Upload CI Intel DMG | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: alloomi-ci-dmg-x64 | |
| path: ${{ github.workspace }}/alloomi/apps/web/src-tauri/target/release/bundle/dmg/Alloomi*.dmg | |
| ci-alloomi-linux-amd64: | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - name: Checkout release repo | |
| uses: actions/checkout@v4 | |
| - name: Clone alloomi source code (main branch) | |
| run: | | |
| git clone https://x-access-token:${{ secrets.DEPLOY_ACCESS_TOKEN }}@github.com/${{ env.ALLOOMI_REPO }}.git alloomi | |
| cd alloomi | |
| if [ "${{ github.event.inputs.commit_sha }}" != "" ]; then | |
| git checkout ${{ github.event.inputs.commit_sha }} | |
| else | |
| git checkout ${{ env.ALLOOMI_BRANCH }} | |
| fi | |
| echo "Commit: $(git log -1 --oneline)" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: 9 | |
| - name: Add pnpm to PATH | |
| run: | | |
| echo "$HOME/.local/bin" >> $GITHUB_PATH | |
| echo "PNPM_HOME=$HOME/.local" >> $GITHUB_ENV | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: ${{ env.RUST_VERSION }} | |
| - name: Install Rust targets | |
| run: | | |
| rustup target add aarch64-unknown-linux-gnu | |
| rustup target add x86_64-unknown-linux-gnu | |
| - name: Install Linux build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libgtk-3-dev \ | |
| libwebkit2gtk-4.1-dev \ | |
| libappindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf \ | |
| libxdo-dev \ | |
| libxkbcommon-dev \ | |
| libssl-dev \ | |
| build-essential \ | |
| libpipewire-0.3-dev \ | |
| libasound2-dev | |
| - name: Get pnpm store directory | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v3 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('alloomi/**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Install dependencies | |
| working-directory: ./alloomi | |
| run: pnpm install --frozen-lockfile --ignore-scripts | |
| - name: Install better-sqlite3 prebuilt binary | |
| working-directory: ./alloomi | |
| run: | | |
| pnpm install -w better-sqlite3@12.11.1 | |
| mkdir -p node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build | |
| cp node_modules/better-sqlite3/build/Release/better_sqlite3.node \ | |
| node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build/better_sqlite3.node | |
| - name: Verify native modules are built | |
| working-directory: ./alloomi | |
| run: | | |
| echo "=== Checking top-level better-sqlite3 ===" | |
| ls -la node_modules/better-sqlite3/build/Release/better_sqlite3.node || exit 1 | |
| echo "=== Checking nested better-sqlite3 in imessage-kit ===" | |
| ls -la node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build/better_sqlite3.node || exit 1 | |
| echo "All native modules built successfully" | |
| - name: Create .env file | |
| working-directory: ./alloomi/apps/web | |
| run: | | |
| cat > .env << 'EOF' | |
| CLOUD_API_URL=https://app.alloomi.ai | |
| NEXT_PUBLIC_CLOUD_API_URL=https://app.alloomi.ai | |
| NEXT_PUBLIC_APP_URL=http://localhost:3415 | |
| LLM_BASE_URL=https://openrouter.ai/api/v1 | |
| LLM_MODEL=google/gemini-3-flash-preview | |
| LLM_REASONING_MODEL=google/gemini-3-flash-preview | |
| LLM_VISION_LANGUAGE_MODEL=google/gemini-3-flash-preview | |
| LLM_IMAGE_MODEL=openai/gpt-5-image | |
| LLM_EMBEDDING_MODEL=qwen/qwen3-embedding-4b | |
| LLM_EMBEDDING_BASE_URL=https://openrouter.ai/api/v1 | |
| TELEGRAM_MODE=pooling | |
| ANTHROPIC_BASE_URL=http://localhost:3415/api/ai | |
| API_TIMEOUT_MS=3000000 | |
| IS_TAURI=true | |
| EOF | |
| - name: Patch tauri.conf.json to skip AppImage on Linux | |
| working-directory: ./alloomi/apps/web | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const c = JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json', 'utf8')); | |
| c.bundle.targets = ['deb']; | |
| fs.writeFileSync('src-tauri/tauri.conf.json', JSON.stringify(c, null, 2)); | |
| console.log('Patched targets to deb'); | |
| " | |
| - name: Build Tauri app (Linux) | |
| working-directory: ./alloomi/apps/web | |
| run: pnpm tauri:build | |
| - name: Upload CI Linux binary | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: alloomi-ci-linux | |
| path: ${{ github.workspace }}/alloomi/apps/web/src-tauri/target/release/bundle/deb/*.deb | |
| ci-alloomi-windows: | |
| runs-on: windows-2022 | |
| steps: | |
| - name: Checkout release repo | |
| uses: actions/checkout@v4 | |
| - name: Clone alloomi source code (main branch) | |
| shell: bash | |
| run: | | |
| git clone https://x-access-token:${{ secrets.DEPLOY_ACCESS_TOKEN }}@github.com/${{ env.ALLOOMI_REPO }}.git alloomi | |
| cd alloomi | |
| if [ "${{ github.event.inputs.commit_sha }}" != "" ]; then | |
| git checkout ${{ github.event.inputs.commit_sha }} | |
| else | |
| git checkout ${{ env.ALLOOMI_BRANCH }} | |
| fi | |
| echo "Commit: $(git log -1 --oneline)" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: 9 | |
| - name: Add pnpm to PATH | |
| shell: bash | |
| run: | | |
| echo "$HOME/AppData/Local/pnpm" >> $GITHUB_PATH | |
| echo "PNPM_HOME=$HOME/AppData/Local/pnpm" >> $GITHUB_ENV | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: ${{ env.RUST_VERSION }} | |
| - name: Install Rust targets | |
| shell: bash | |
| run: | | |
| rustup target add aarch64-pc-windows-msvc | |
| rustup target add x86_64-pc-windows-msvc | |
| - name: Get pnpm store directory | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v3 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('alloomi/**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Install dependencies | |
| working-directory: ./alloomi | |
| shell: bash | |
| run: pnpm install --frozen-lockfile --ignore-scripts | |
| - name: Install better-sqlite3 prebuilt binary | |
| working-directory: ./alloomi | |
| shell: bash | |
| run: | | |
| pnpm install -w better-sqlite3@12.11.1 | |
| mkdir -p node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build | |
| cp node_modules/better-sqlite3/build/Release/better_sqlite3.node \ | |
| node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build/better_sqlite3.node | |
| - name: Verify native modules are built | |
| working-directory: ./alloomi | |
| shell: bash | |
| run: | | |
| echo "=== Checking top-level better-sqlite3 ===" | |
| ls -la node_modules/better-sqlite3/build/Release/better_sqlite3.node || exit 1 | |
| echo "=== Checking nested better-sqlite3 in imessage-kit ===" | |
| ls -la node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build/better_sqlite3.node || exit 1 | |
| echo "All native modules built successfully" | |
| - name: Create .env file | |
| working-directory: ./alloomi/apps/web | |
| shell: bash | |
| run: | | |
| cat > .env << 'EOF' | |
| CLOUD_API_URL=https://app.alloomi.ai | |
| NEXT_PUBLIC_CLOUD_API_URL=https://app.alloomi.ai | |
| NEXT_PUBLIC_APP_URL=http://localhost:3415 | |
| LLM_BASE_URL=https://openrouter.ai/api/v1 | |
| LLM_MODEL=google/gemini-3-flash-preview | |
| LLM_REASONING_MODEL=google/gemini-3-flash-preview | |
| LLM_VISION_LANGUAGE_MODEL=google/gemini-3-flash-preview | |
| LLM_IMAGE_MODEL=openai/gpt-5-image | |
| LLM_EMBEDDING_MODEL=qwen/qwen3-embedding-4b | |
| LLM_EMBEDDING_BASE_URL=https://openrouter.ai/api/v1 | |
| TELEGRAM_MODE=pooling | |
| ANTHROPIC_BASE_URL=http://localhost:3415/api/ai | |
| API_TIMEOUT_MS=3000000 | |
| IS_TAURI=true | |
| EOF | |
| - name: Patch tauri.conf.json to skip MSI on Windows | |
| working-directory: ./alloomi/apps/web | |
| shell: pwsh | |
| run: | | |
| $content = Get-Content src-tauri\tauri.conf.json -Raw | |
| $content = $content -replace '"targets"\s*:\s*"all"', '"targets": ["nsis"]' | |
| $content = $content -replace '"targets"\s*:\s*\[.*?\]', '"targets": ["nsis"]' | |
| $content = $content -replace '"wix"\s*:\s*\{[^}]*\}', '"wix": null' | |
| Set-Content -Path src-tauri\tauri.conf.json -Value $content -NoNewline | |
| Get-Content src-tauri\tauri.conf.json | Select-String 'targets' | |
| Get-Content src-tauri\tauri.conf.json | Select-String 'wix' | |
| - name: Build Tauri app (Windows) | |
| working-directory: ./alloomi/apps/web | |
| shell: bash | |
| run: pnpm tauri:build | |
| env: | |
| RUSTUP_WINDOWS_PATH: 1 | |
| TAURI_BUNDLE_TARGET: nsis | |
| - name: Upload CI Windows installer | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: alloomi-ci-windows | |
| path: ${{ github.workspace }}/alloomi/apps/web/src-tauri/target/release/bundle/nsis/*.exe | |
| ci-alloomi-linux-arm64: | |
| runs-on: ubuntu-22.04-arm | |
| steps: | |
| - name: Checkout release repo | |
| uses: actions/checkout@v4 | |
| - name: Clone alloomi source code (main branch) | |
| run: | | |
| git clone https://x-access-token:${{ secrets.DEPLOY_ACCESS_TOKEN }}@github.com/${{ env.ALLOOMI_REPO }}.git alloomi | |
| cd alloomi | |
| if [ "${{ github.event.inputs.commit_sha }}" != "" ]; then | |
| git checkout ${{ github.event.inputs.commit_sha }} | |
| else | |
| git checkout ${{ env.ALLOOMI_BRANCH }} | |
| fi | |
| echo "Commit: $(git log -1 --oneline)" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: 9 | |
| - name: Add pnpm to PATH | |
| run: | | |
| echo "$HOME/.local/bin" >> $GITHUB_PATH | |
| echo "PNPM_HOME=$HOME/.local" >> $GITHUB_ENV | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: ${{ env.RUST_VERSION }} | |
| - name: Install Linux build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libgtk-3-dev \ | |
| libwebkit2gtk-4.1-dev \ | |
| libappindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf \ | |
| libxdo-dev \ | |
| libxkbcommon-dev \ | |
| libssl-dev \ | |
| build-essential \ | |
| xdg-utils \ | |
| libpipewire-0.3-dev \ | |
| libasound2-dev | |
| - name: Get pnpm store directory | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v3 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('alloomi/**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Install dependencies | |
| working-directory: ./alloomi | |
| run: pnpm install --frozen-lockfile --ignore-scripts | |
| - name: Install better-sqlite3 prebuilt binary | |
| working-directory: ./alloomi | |
| run: | | |
| pnpm install -w better-sqlite3@12.11.1 | |
| mkdir -p node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build | |
| cp node_modules/better-sqlite3/build/Release/better_sqlite3.node \ | |
| node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build/better_sqlite3.node | |
| - name: Verify native modules are built | |
| working-directory: ./alloomi | |
| run: | | |
| echo "=== Checking top-level better-sqlite3 ===" | |
| ls -la node_modules/better-sqlite3/build/Release/better_sqlite3.node || exit 1 | |
| echo "=== Checking nested better-sqlite3 in imessage-kit ===" | |
| ls -la node_modules/@photon-ai/imessage-kit/node_modules/better-sqlite3/build/better_sqlite3.node || exit 1 | |
| echo "All native modules built successfully" | |
| - name: Create .env file | |
| working-directory: ./alloomi/apps/web | |
| run: | | |
| cat > .env << 'EOF' | |
| CLOUD_API_URL=https://app.alloomi.ai | |
| NEXT_PUBLIC_CLOUD_API_URL=https://app.alloomi.ai | |
| NEXT_PUBLIC_APP_URL=http://localhost:3415 | |
| LLM_BASE_URL=https://openrouter.ai/api/v1 | |
| LLM_MODEL=google/gemini-3-flash-preview | |
| LLM_REASONING_MODEL=google/gemini-3-flash-preview | |
| LLM_VISION_LANGUAGE_MODEL=google/gemini-3-flash-preview | |
| LLM_IMAGE_MODEL=openai/gpt-5-image | |
| LLM_EMBEDDING_MODEL=qwen/qwen3-embedding-4b | |
| LLM_EMBEDDING_BASE_URL=https://openrouter.ai/api/v1 | |
| TELEGRAM_MODE=pooling | |
| ANTHROPIC_BASE_URL=http://localhost:3415/api/ai | |
| API_TIMEOUT_MS=3000000 | |
| IS_TAURI=true | |
| EOF | |
| - name: Patch tauri.conf.json to skip AppImage on Linux ARM64 | |
| working-directory: ./alloomi/apps/web | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const c = JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json', 'utf8')); | |
| c.bundle.targets = ['deb']; | |
| fs.writeFileSync('src-tauri/tauri.conf.json', JSON.stringify(c, null, 2)); | |
| console.log('Patched targets to deb'); | |
| " | |
| - name: Build Tauri app (Linux ARM64) | |
| working-directory: ./alloomi/apps/web | |
| run: pnpm tauri:build | |
| - name: Upload CI Linux ARM64 binary | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: alloomi-ci-linux-arm64 | |
| path: ${{ github.workspace }}/alloomi/apps/web/src-tauri/target/release/bundle/deb/*.deb |