From 1dc5842a86f75fe90e9b71062eec0400813202b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 00:04:47 +0000 Subject: [PATCH 1/3] Initial plan From 6ea28e5dde86b270a8c9dda6fa04c21f218be9f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 00:08:21 +0000 Subject: [PATCH 2/3] Fix Docker networking: add server service, shared network, retry logic, and Dockerfile - docker-compose.yml: Add Node.js server service with SEARXNG_URL=http://searxng:8080, shared macai-net bridge network, depends_on for startup ordering, and fix SEARXNG_BASE_URL to use internal Docker hostname - Dockerfile: Add multi-stage build for the Node.js server - server.js: Replace single 3s startup health check with 5-attempt retry loop (5s timeout each, 3s delay between) to handle Docker cold-start timing - README.md: Document Docker and host-mode usage, environment variables Co-authored-by: sriail <225764385+sriail@users.noreply.github.com> --- Dockerfile | 8 ++++++++ README.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 23 ++++++++++++++++++++++- server.js | 31 +++++++++++++++++++++---------- 4 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3412291 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM node:20-alpine +WORKDIR /app +COPY package.json ./ +RUN npm install --production +COPY server.js ./ +COPY public ./public +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/README.md b/README.md index a3000d8..96dd0fc 100644 --- a/README.md +++ b/README.md @@ -1 +1,46 @@ # MacAI + +AI-powered search assistant using Cerebras LLM and SearXNG. + +## Quick Start (Docker) + +Run both the Node server and SearXNG together: + +```bash +# Create a .env file with your API key +echo "CEREBRAS_API_KEY=your-key-here" > .env + +# Start everything +docker compose up -d +``` + +Open [http://localhost:3000](http://localhost:3000) in your browser. + +Inside Docker Compose the Node server reaches SearXNG via `http://searxng:8080` +(the Docker service name and internal port). This is set automatically by +`docker-compose.yml`. + +## Running the Node Server on the Host + +If you prefer to run the Node server directly on your machine while SearXNG +stays in Docker: + +```bash +# Start only SearXNG +docker compose up -d searxng + +# Install dependencies and start the server +npm install +npm start +``` + +When running on the host, the default `SEARXNG_URL` is `http://localhost:8888`, +which maps to the container's port 8080 via the published Docker port. + +## Environment Variables + +| Variable | Default | Description | +| ------------------ | ------------------------ | ------------------------------- | +| `CEREBRAS_API_KEY` | *(required)* | API key for Cerebras | +| `SEARXNG_URL` | `http://localhost:8888` | SearXNG base URL | +| `PORT` | `3000` | Port for the Express server | diff --git a/docker-compose.yml b/docker-compose.yml index c63c14b..fc1f7ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,5 +8,26 @@ services: - ./searxng/settings.yml:/etc/searxng/settings.yml:ro - ./searxng/limiter.toml:/etc/searxng/limiter.toml:ro environment: - - SEARXNG_BASE_URL=http://localhost:8888/ + - SEARXNG_BASE_URL=http://searxng:8080/ restart: unless-stopped + networks: + - macai-net + + server: + build: . + container_name: macai-server + ports: + - "3000:3000" + environment: + - SEARXNG_URL=http://searxng:8080 + env_file: + - .env + depends_on: + - searxng + restart: unless-stopped + networks: + - macai-net + +networks: + macai-net: + driver: bridge diff --git a/server.js b/server.js index 1bf4064..66257c8 100644 --- a/server.js +++ b/server.js @@ -324,15 +324,26 @@ app.listen(PORT, async () => { console.log(`✓ http://localhost:${PORT}`); if (!CEREBRAS_KEY_RESOLVED) console.warn('⚠ CEREBRAS_API_KEY not set'); - // Check SearXNG connectivity on startup - try { - const ctrl = new AbortController(); - const timer = setTimeout(() => ctrl.abort(), 3000); - const resp = await fetch(`${SEARXNG_URL}/healthz`, { signal: ctrl.signal }); - clearTimeout(timer); - if (resp.ok) console.log(`✓ SearXNG reachable at ${SEARXNG_URL}`); - else console.warn(`⚠ SearXNG returned HTTP ${resp.status} at ${SEARXNG_URL}`); - } catch (_) { - console.warn(`⚠ SearXNG not reachable at ${SEARXNG_URL} — run: docker compose up -d`); + // Check SearXNG connectivity on startup (retry up to 5 times for Docker cold-start) + const maxRetries = 5; + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + const ctrl = new AbortController(); + const timer = setTimeout(() => ctrl.abort(), 5000); + const resp = await fetch(`${SEARXNG_URL}/healthz`, { signal: ctrl.signal }); + clearTimeout(timer); + if (resp.ok) { + console.log(`✓ SearXNG reachable at ${SEARXNG_URL}`); + break; + } + console.warn(`⚠ SearXNG returned HTTP ${resp.status} at ${SEARXNG_URL}`); + } catch (_) { + if (attempt < maxRetries) { + console.warn(`⚠ SearXNG not reachable at ${SEARXNG_URL} (attempt ${attempt}/${maxRetries}), retrying in 3s…`); + await new Promise(r => setTimeout(r, 3000)); + } else { + console.warn(`⚠ SearXNG not reachable at ${SEARXNG_URL} after ${maxRetries} attempts — run: docker compose up -d`); + } + } } }); From 79435e19866ef54a8b8feb1c34f98dac80125e7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 00:09:53 +0000 Subject: [PATCH 3/3] Break retry loop on HTTP errors since SearXNG is reachable Co-authored-by: sriail <225764385+sriail@users.noreply.github.com> --- server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server.js b/server.js index 66257c8..6996915 100644 --- a/server.js +++ b/server.js @@ -337,6 +337,7 @@ app.listen(PORT, async () => { break; } console.warn(`⚠ SearXNG returned HTTP ${resp.status} at ${SEARXNG_URL}`); + break; } catch (_) { if (attempt < maxRetries) { console.warn(`⚠ SearXNG not reachable at ${SEARXNG_URL} (attempt ${attempt}/${maxRetries}), retrying in 3s…`);