diff --git a/.fly/gitnexus/Dockerfile b/.fly/gitnexus/Dockerfile index cf5cd29756..911f90d244 100644 --- a/.fly/gitnexus/Dockerfile +++ b/.fly/gitnexus/Dockerfile @@ -2,23 +2,37 @@ FROM node:24-slim ARG GITNEXUS_VERSION=1.5.3 -# Build tools for native addons (LadybugDB, tree-sitter), cleaned up after install +# 1. Build native addons with Bookworm toolchain, then remove build tools RUN apt-get update \ - && apt-get install -y --no-install-recommends python3 make g++ caddy \ + && apt-get install -y --no-install-recommends python3 make g++ caddy curl \ && npm install -g gitnexus@${GITNEXUS_VERSION} \ && apt-get purge -y --auto-remove python3 make g++ \ && rm -rf /var/lib/apt/lists/* /root/.npm -WORKDIR /repo +# 2. Upgrade libstdc++ from Trixie — @ladybugdb/core prebuilt binary needs +# GLIBCXX_3.4.32 which Bookworm (3.4.31) doesn't ship. +# Done AFTER removing g++ to avoid libc6-dev version conflict. +RUN echo "deb http://deb.debian.org/debian trixie main" > /etc/apt/sources.list.d/trixie.list \ + && apt-get update \ + && apt-get install -y -t trixie libstdc++6 \ + && rm /etc/apt/sources.list.d/trixie.list \ + && rm -rf /var/lib/apt/lists/* -# Copy the pre-built GitNexus index (from CI artifact) -COPY .gitnexus/ .gitnexus/ - -# Register the index so `gitnexus serve` can discover it -RUN gitnexus index /repo --allow-non-git +# Copy pre-built GitNexus indexes (one per branch) and register each. +# The directory name becomes the repo name in `list_repos` output, so +# each branch lands in /LibreChat or /LibreChat-dev etc. +COPY indexes/ /indexes/ +RUN set -e && \ + for dir in /indexes/*/; do \ + name=$(basename "$dir"); \ + target="/$name"; \ + mkdir -p "$target"; \ + cp -r "$dir.gitnexus" "$target/.gitnexus"; \ + gitnexus index "$target" --allow-non-git; \ + done && \ + rm -rf /indexes # Caddy reverse proxy: bearer token auth in front of gitnexus serve -# Token is set via FLY_API_SECRET (flyctl secrets set API_TOKEN=...) COPY Caddyfile /etc/caddy/Caddyfile COPY entrypoint.sh /entrypoint.sh diff --git a/.fly/gitnexus/entrypoint.sh b/.fly/gitnexus/entrypoint.sh index 8a127848b4..42ada8499c 100644 --- a/.fly/gitnexus/entrypoint.sh +++ b/.fly/gitnexus/entrypoint.sh @@ -7,8 +7,35 @@ if [ -z "$API_TOKEN" ]; then exit 1 fi -# Start gitnexus serve in background -gitnexus serve --host 127.0.0.1 --port 4747 & +# Cap Node heap to match the Fly machine (leaves headroom for Caddy + OS). +# Without this, gitnexus defaults to --max-old-space-size=8192 which over-commits +# and gets killed by the OOM killer on small machines. +export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=768}" + +# Start gitnexus serve in background, pipe output to stdout/stderr +gitnexus serve --host 127.0.0.1 --port 4747 2>&1 & +GITNEXUS_PID=$! + +# Wait for gitnexus to be ready (up to 30s) +echo "Waiting for gitnexus serve to start..." +for i in $(seq 1 30); do + if curl -sf http://127.0.0.1:4747/api/info > /dev/null 2>&1; then + echo "gitnexus serve is ready (pid $GITNEXUS_PID)" + break + fi + # Check if process died + if ! kill -0 "$GITNEXUS_PID" 2>/dev/null; then + echo "ERROR: gitnexus serve exited prematurely" + exit 1 + fi + sleep 1 +done + +# Final check +if ! curl -sf http://127.0.0.1:4747/api/info > /dev/null 2>&1; then + echo "ERROR: gitnexus serve failed to start within 30s" + exit 1 +fi # Start caddy auth proxy in foreground exec caddy run --config /etc/caddy/Caddyfile diff --git a/.fly/gitnexus/fly.toml b/.fly/gitnexus/fly.toml index 086a64a640..65417bc3db 100644 --- a/.fly/gitnexus/fly.toml +++ b/.fly/gitnexus/fly.toml @@ -13,7 +13,10 @@ primary_region = 'iad' [[vm]] size = 'shared-cpu-1x' - memory = '512mb' + memory = '1gb' + +# 512MB swap file to absorb transient memory spikes +swap_size_mb = 512 [checks] [checks.health] diff --git a/.github/workflows/gitnexus-deploy.yml b/.github/workflows/gitnexus-deploy.yml index 7ee6e23fc7..8e6ac2d84b 100644 --- a/.github/workflows/gitnexus-deploy.yml +++ b/.github/workflows/gitnexus-deploy.yml @@ -1,8 +1,9 @@ -# Deploys the GitNexus index to Fly.io as a persistent MCP + REST server. +# Deploys the GitNexus indexes for both main and dev to Fly.io as a +# persistent MCP + REST server, serving both branches simultaneously. # # Endpoints available after deploy: # /api/mcp — MCP-over-HTTP (StreamableHTTP transport) -# /api/query — Search execution flows +# /api/query — Search execution flows (specify repo: LibreChat or LibreChat-dev) # /api/search — Hybrid BM25 + semantic search # /api/repos — List indexed repositories # /api/info — Server version and status @@ -20,7 +21,7 @@ name: GitNexus Deploy on: workflow_run: workflows: ['GitNexus Index'] - branches: [main] + branches: [main, dev] types: [completed] workflow_dispatch: @@ -29,7 +30,7 @@ permissions: concurrency: group: gitnexus-deploy - cancel-in-progress: true + cancel-in-progress: false env: GITNEXUS_VERSION: '1.5.3' @@ -40,7 +41,7 @@ jobs: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 steps: - name: Checkout deploy config uses: actions/checkout@v4 @@ -48,37 +49,52 @@ jobs: sparse-checkout: .fly/gitnexus fetch-depth: 1 - - name: Resolve index run + - name: Resolve latest successful index runs per branch id: resolve uses: actions/github-script@v7 with: script: | - const runId = context.payload.workflow_run?.id; - if (runId) { - core.setOutput('run_id', String(runId)); + const branches = ['main', 'dev']; + const result = {}; + for (const branch of branches) { + const { data } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'gitnexus-index.yml', + branch, + status: 'success', + per_page: 1, + }); + if (data.workflow_runs.length) { + result[branch] = data.workflow_runs[0].id; + core.info(`${branch}: run ${result[branch]}`); + } else { + core.warning(`No successful index run found for ${branch}`); + } + } + if (!Object.keys(result).length) { + core.setFailed('No successful index runs found for any branch'); return; } - // workflow_dispatch: find latest successful index run on main - const { data } = await github.rest.actions.listWorkflowRuns({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'gitnexus-index.yml', - branch: 'main', - status: 'success', - per_page: 1, - }); - if (!data.workflow_runs.length) { - core.setFailed('No successful GitNexus Index runs found on main'); - return; - } - core.setOutput('run_id', String(data.workflow_runs[0].id)); + core.setOutput('main_run', result.main || ''); + core.setOutput('dev_run', result.dev || ''); - - name: Download GitNexus index + - name: Download main index + if: steps.resolve.outputs.main_run != '' uses: actions/download-artifact@v4 with: name: gitnexus-index-main - path: deploy/.gitnexus - run-id: ${{ steps.resolve.outputs.run_id }} + path: deploy/indexes/LibreChat/.gitnexus + run-id: ${{ steps.resolve.outputs.main_run }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Download dev index + if: steps.resolve.outputs.dev_run != '' + uses: actions/download-artifact@v4 + with: + name: gitnexus-index-dev + path: deploy/indexes/LibreChat-dev/.gitnexus + run-id: ${{ steps.resolve.outputs.dev_run }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: Prepare deploy context @@ -88,7 +104,8 @@ jobs: cp .fly/gitnexus/entrypoint.sh deploy/entrypoint.sh echo "Deploy context:" ls -la deploy/ - ls -la deploy/.gitnexus/ + echo "Indexes staged:" + ls -la deploy/indexes/ || echo "(none)" - name: Setup Fly uses: superfly/flyctl-actions/setup-flyctl@master diff --git a/.github/workflows/gitnexus-index.yml b/.github/workflows/gitnexus-index.yml index f6fab79587..bd395247b9 100644 --- a/.github/workflows/gitnexus-index.yml +++ b/.github/workflows/gitnexus-index.yml @@ -37,7 +37,7 @@ jobs: github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 25 steps: - name: Checkout repository uses: actions/checkout@v4 @@ -59,7 +59,9 @@ jobs: - name: Run GitNexus Analyze run: | FLAGS="--skip-agents-md --verbose" - if [ "${{ inputs.embeddings }}" = "true" ]; then + # Auto-enable embeddings for main branch pushes; opt-in via dispatch input elsewhere + if [ "${{ inputs.embeddings }}" = "true" ] || \ + ([ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]); then FLAGS="$FLAGS --embeddings" fi if [ "${{ inputs.force }}" = "true" ]; then