Skip to content

CI Alloomi (Main Branch) #224

CI Alloomi (Main Branch)

CI Alloomi (Main Branch) #224

Workflow file for this run

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