From 8a9147abedf6ecd94eace3f602aa724e3755eb60 Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Fri, 15 May 2026 04:33:12 +0000 Subject: [PATCH 1/8] feat: add docker-compose.gateway.yaml overlay for co-located gateway Adds a Docker Compose overlay that lets users bring up the CoreBrain gateway alongside the core stack with a single two-file compose command. The gateway joins the internal `core` network so it talks to the webapp at http://core:3000 without exposing the API publicly, and uses ${GATEWAY_VERSION:-latest} to allow version pinning via .env. Co-Authored-By: Claude Sonnet 4.6 --- hosting/docker/docker-compose.gateway.yaml | 94 ++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 hosting/docker/docker-compose.gateway.yaml diff --git a/hosting/docker/docker-compose.gateway.yaml b/hosting/docker/docker-compose.gateway.yaml new file mode 100644 index 00000000..9bd8cf07 --- /dev/null +++ b/hosting/docker/docker-compose.gateway.yaml @@ -0,0 +1,94 @@ +# CoreBrain Gateway — overlay for the core stack. +# +# Run with: +# docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml up -d +# +# The gateway joins the existing `core` Docker network so it can reach the +# webapp at http://core:3000 without exposing the API publicly. All env vars +# (except COREBRAIN_API_URL, which is hardcoded to the internal address) are +# sourced from the shared .env file alongside docker-compose.yaml. +# +# Persistent volumes: +# workspace → /app cloned repos + worktrees +# corebrain-home → /home/corebrain gateway state (.corebrain), agent +# login + session state (.claude, +# .codex, …), playwright chromium +# cache (.cache/ms-playwright). One +# mount covers every agent's $HOME +# state so adding a new coding agent +# doesn't need a new volume. + +x-logging: &logging-config + driver: ${LOGGING_DRIVER:-local} + options: + max-size: ${LOGGING_MAX_SIZE:-20m} + max-file: ${LOGGING_MAX_FILES:-5} + compress: ${LOGGING_COMPRESS:-true} + +services: + corebrain-gateway: + container_name: corebrain-gateway + image: redplanethq/core-gateway:${GATEWAY_VERSION:-latest} + # Image is published amd64-only today. Apple Silicon hosts run it under + # Rosetta emulation; remove this line once we publish a multi-arch image. + platform: linux/amd64 + restart: ${RESTART_POLICY:-unless-stopped} + logging: *logging-config + depends_on: + core: + condition: service_started + environment: + # ── CORE webapp wiring ─────────────────────────────────────────── + # API_URL is hardcoded to the internal Docker network address so the + # gateway talks directly to the webapp container without going through + # the public internet. API_KEY is sourced from the shared .env. + - COREBRAIN_API_URL=http://core:3000 + - COREBRAIN_API_KEY=${COREBRAIN_API_KEY} + + # ── Gateway identity ──────────────────────────────────────────── + - COREBRAIN_GATEWAY_NAME=${COREBRAIN_GATEWAY_NAME:-cloud-gateway} + - COREBRAIN_GATEWAY_DESCRIPTION=${COREBRAIN_GATEWAY_DESCRIPTION:-Cloud-hosted CoreBrain gateway} + + # Security key used by the webapp to authenticate to this gateway. + # Leave unset on first boot; the gateway will generate one and print + # it to `docker compose logs`. Pin it to a known value here once you + # have it, so subsequent restarts don't re-prompt. + - COREBRAIN_GATEWAY_SECURITY_KEY=${COREBRAIN_GATEWAY_SECURITY_KEY:-} + + # ── Coding agent auth (env-only — no webapp UI for tokens) ────── + # claude-code: long-lived token from `claude setup-token`. + - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN:-} + # codex-cli: standard OpenAI API key. + - OPENAI_API_KEY=${OPENAI_API_KEY:-} + + # ── Git credentials for `git clone` of private repos ──────────── + # The entrypoint configures the credential helper from this token and + # auto-fetches author identity (name/email) from the GitHub API. + - GITHUB_TOKEN=${GITHUB_TOKEN:-} + + # ── Container baked-in defaults ────────────────────────────────- + - COREBRAIN_DEFAULT_WORKSPACE=/app + - COREBRAIN_DEPLOY_MODE=docker + + # ── Slot toggles ───────────────────────────────────────────────- + # `COREBRAIN_SLOT_=true|false` overrides the on-disk slot + # config (set via `corebrain gateway config`). `files` defaults + # off in docker because `exec` already covers read/write/edit via + # shell — flip to `true` if you want both tool groups exposed. + # The same pattern works for BROWSER / CODING / EXEC. + - COREBRAIN_SLOT_FILES=${COREBRAIN_SLOT_FILES:-false} + ports: + - "${COREBRAIN_GATEWAY_HTTP_PORT:-7787}:7787" + volumes: + - workspace:/app + - corebrain-home:/home/corebrain + networks: + - core + +networks: + core: + external: true + +volumes: + workspace: + corebrain-home: From 16bb55da6c140e9467789a7f50b03f8b0a5783b1 Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Fri, 15 May 2026 04:38:34 +0000 Subject: [PATCH 2/8] feat: add gateway env vars to hosting/docker/.env.example Co-Authored-By: Claude Sonnet 4.6 --- hosting/docker/.env.example | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/hosting/docker/.env.example b/hosting/docker/.env.example index c743a086..c3ff824a 100644 --- a/hosting/docker/.env.example +++ b/hosting/docker/.env.example @@ -85,3 +85,34 @@ POSTHOG_PROJECT_KEY=phc_SwfGIzzX5gh5bazVWoRxZTBhkr7FwvzArS0NRyGXm1a GRAPH_PROVIDER=neo4j VECTOR_PROVIDER=pgvector MODEL_PROVIDER=vercel-ai + +########### Gateway (optional) ############ +# Add the gateway to your stack with: +# docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml up -d +# +# Step 1 — Pre-set a security key so you don't need to copy it from logs. +# Generate with: openssl rand -hex 32 +# This same value goes in hosting/gateway/.env (COREBRAIN_GATEWAY_SECURITY_KEY) +# OR use the overlay file above which shares this .env automatically. +COREBRAIN_GATEWAY_SECURITY_KEY= + +# Your personal access token from Core → Settings → Tokens. +# Required for the gateway to authenticate against the webapp. +COREBRAIN_API_KEY= + +# Step 2 (optional) — Auto-register the gateway for every new workspace. +# Set DEFAULT_GATEWAY_URL to the URL where the gateway is reachable FROM the webapp +# container (not from your browser). When using the docker-compose overlay the +# gateway is on the same Docker network, so use the Docker service name: +DEFAULT_GATEWAY_URL=http://corebrain-gateway:7787 +DEFAULT_GATEWAY_NAME=local-gateway + +# Gateway image tag to deploy. Defaults to latest; pin to a specific version for stability. +# GATEWAY_VERSION=0.6.4 + +# Public-facing port mapping. Adjust if 7787 is taken on your host. +COREBRAIN_GATEWAY_HTTP_PORT=7787 + +# Coding agent credentials (stored in gateway container only, not in webapp) +CLAUDE_CODE_OAUTH_TOKEN= +GITHUB_TOKEN= From 5e7efcc08447d63b90945cc7a6a343167327f2e2 Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Fri, 15 May 2026 04:39:26 +0000 Subject: [PATCH 3/8] docs: add hosting/docker/README.md for core + gateway Docker Compose setup Covers quick-start (core only), running both compose files with -f, env var wiring, auto-registration via DEFAULT_GATEWAY_URL, and how to retrieve the auto-generated security key from container logs. Co-Authored-By: Claude Sonnet 4.6 --- hosting/docker/README.md | 76 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 hosting/docker/README.md diff --git a/hosting/docker/README.md b/hosting/docker/README.md new file mode 100644 index 00000000..8346aa5e --- /dev/null +++ b/hosting/docker/README.md @@ -0,0 +1,76 @@ +# Docker Compose — CoreBrain Self-Hosted + +All commands are run from this directory (`hosting/docker/`). + +## Quick start (core stack only) + +```bash +cp .env.example .env +# Edit .env — set secrets, AI provider keys, NEO4J_PASSWORD, etc. +docker compose up -d +``` + +The webapp is available at `http://localhost:3033`. + +## Adding the gateway + +Run both compose files together with `-f`: + +```bash +docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml up -d +``` + +The gateway container (`corebrain-gateway`) joins the same `core` Docker network as the webapp and is reachable at `http://corebrain-gateway:7787` from other containers. It is exposed on your host at the port set by `COREBRAIN_GATEWAY_HTTP_PORT` (default `7787`). + +To stop everything: + +```bash +docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml down +``` + +## Environment variable wiring + +Both compose files read from the same `.env` file. The gateway-specific variables are already included in `.env.example`: + +| Variable | Where used | Purpose | +|---|---|---| +| `COREBRAIN_GATEWAY_SECURITY_KEY` | gateway container + webapp | Shared secret that authenticates the webapp to the gateway | +| `COREBRAIN_API_KEY` | gateway container | Personal access token from Core → Settings → Tokens | +| `DEFAULT_GATEWAY_URL` | webapp | Internal URL the webapp uses to reach the gateway (`http://corebrain-gateway:7787`) | +| `DEFAULT_GATEWAY_NAME` | webapp | Display name stored in the DB (default: `local-gateway`) | +| `COREBRAIN_GATEWAY_HTTP_PORT` | host port mapping | Host port the gateway listens on (default: `7787`) | +| `GATEWAY_VERSION` | gateway image tag | Pin to a specific release, e.g. `0.6.4`; omit for `latest` | + +The gateway's `COREBRAIN_API_URL` is hardcoded to `http://core:3000` (the webapp's internal address) in `docker-compose.gateway.yaml` — do not set this in `.env`. + +## Auto-registration + +When both `DEFAULT_GATEWAY_URL` and `COREBRAIN_GATEWAY_SECURITY_KEY` are set in `.env`, the webapp automatically registers the gateway for every new workspace. No manual "Register gateway" step in the UI is needed. + +Minimum `.env` additions to enable auto-registration: + +```env +COREBRAIN_GATEWAY_SECURITY_KEY= +COREBRAIN_API_KEY= +DEFAULT_GATEWAY_URL=http://corebrain-gateway:7787 +DEFAULT_GATEWAY_NAME=local-gateway +``` + +## First-boot note: security key from logs + +If `COREBRAIN_GATEWAY_SECURITY_KEY` is left blank in `.env`, the gateway generates a random key on first boot and prints it to stdout: + +```bash +docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml logs corebrain-gateway | grep -i "security key" +``` + +Copy the printed key into `.env` as `COREBRAIN_GATEWAY_SECURITY_KEY`, then either: + +- Set `DEFAULT_GATEWAY_URL` and restart so the webapp auto-registers it, **or** +- Register it manually via the workspace gateway UI using that key. + +After setting the key in `.env`, restart the gateway so it picks it up: + +```bash +docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml restart corebrain-gateway +``` From 2b02653c85e7df8d7cf003f86c299531b8576f83 Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Fri, 15 May 2026 04:41:18 +0000 Subject: [PATCH 4/8] docs: add hosting/docker/README.md for core + gateway Docker Compose setup Also pass DEFAULT_GATEWAY_URL, DEFAULT_GATEWAY_NAME, COREBRAIN_GATEWAY_SECURITY_KEY env vars through to the core service in docker-compose.yaml so auto-registration works. Co-Authored-By: Claude Sonnet 4.6 --- hosting/docker/README.md | 9 ++++++--- hosting/docker/docker-compose.yaml | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/hosting/docker/README.md b/hosting/docker/README.md index 8346aa5e..0a84f2da 100644 --- a/hosting/docker/README.md +++ b/hosting/docker/README.md @@ -37,8 +37,11 @@ Both compose files read from the same `.env` file. The gateway-specific variable | `COREBRAIN_GATEWAY_SECURITY_KEY` | gateway container + webapp | Shared secret that authenticates the webapp to the gateway | | `COREBRAIN_API_KEY` | gateway container | Personal access token from Core → Settings → Tokens | | `DEFAULT_GATEWAY_URL` | webapp | Internal URL the webapp uses to reach the gateway (`http://corebrain-gateway:7787`) | -| `DEFAULT_GATEWAY_NAME` | webapp | Display name stored in the DB (default: `local-gateway`) | +| `DEFAULT_GATEWAY_NAME` | webapp | Name stored in DB when auto-registering (default: `local-gateway`) | +| `COREBRAIN_GATEWAY_NAME` | gateway container | Name the gateway reports in its own manifest (default: `cloud-gateway`) | | `COREBRAIN_GATEWAY_HTTP_PORT` | host port mapping | Host port the gateway listens on (default: `7787`) | +| `CLAUDE_CODE_OAUTH_TOKEN` | gateway container | Long-lived Claude Code token (`claude setup-token`); optional | +| `GITHUB_TOKEN` | gateway container | Token for `git clone` of private repos and author identity; optional | | `GATEWAY_VERSION` | gateway image tag | Pin to a specific release, e.g. `0.6.4`; omit for `latest` | The gateway's `COREBRAIN_API_URL` is hardcoded to `http://core:3000` (the webapp's internal address) in `docker-compose.gateway.yaml` — do not set this in `.env`. @@ -69,8 +72,8 @@ Copy the printed key into `.env` as `COREBRAIN_GATEWAY_SECURITY_KEY`, then eithe - Set `DEFAULT_GATEWAY_URL` and restart so the webapp auto-registers it, **or** - Register it manually via the workspace gateway UI using that key. -After setting the key in `.env`, restart the gateway so it picks it up: +After setting the key in `.env`, re-create the gateway container so it picks up the new value (plain `restart` does not re-read `.env`): ```bash -docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml restart corebrain-gateway +docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml up -d corebrain-gateway ``` diff --git a/hosting/docker/docker-compose.yaml b/hosting/docker/docker-compose.yaml index 2165fb94..df3fb2db 100644 --- a/hosting/docker/docker-compose.yaml +++ b/hosting/docker/docker-compose.yaml @@ -60,6 +60,9 @@ services: - GRAPH_PROVIDER=${GRAPH_PROVIDER} - VECTOR_PROVIDER=${VECTOR_PROVIDER} - MODEL_PROVIDER=${MODEL_PROVIDER} + - DEFAULT_GATEWAY_URL=${DEFAULT_GATEWAY_URL:-} + - DEFAULT_GATEWAY_NAME=${DEFAULT_GATEWAY_NAME:-local-gateway} + - COREBRAIN_GATEWAY_SECURITY_KEY=${COREBRAIN_GATEWAY_SECURITY_KEY:-} ports: - "3033:3000" - "5555:5555" From 489877c59112ceec485b5b1c21ad8a6babc85cea Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Fri, 15 May 2026 04:44:19 +0000 Subject: [PATCH 5/8] feat: auto-register default gateway for new workspaces When DEFAULT_GATEWAY_URL and COREBRAIN_GATEWAY_SECURITY_KEY env vars are set, every newly-created workspace automatically registers the configured gateway with no user action required. Registration failures are logged but never block workspace creation. Co-Authored-By: Claude Sonnet 4.6 --- apps/webapp/app/env.server.ts | 5 +++ apps/webapp/app/models/workspace.server.ts | 7 ++++ .../app/services/gateway/default.server.ts | 36 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 apps/webapp/app/services/gateway/default.server.ts diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index e0eafa1b..a6a5b4d2 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -240,6 +240,11 @@ const EnvironmentSchema = z // Resend RESEND_WEBHOOK_SECRET: z.string().optional(), + // Default gateway auto-registration + DEFAULT_GATEWAY_URL: z.string().url().optional(), + DEFAULT_GATEWAY_NAME: z.string().min(1).max(64).default("local-gateway"), + COREBRAIN_GATEWAY_SECURITY_KEY: z.string().optional(), + // Sentry SENTRY_DSN: z.string().optional(), }) diff --git a/apps/webapp/app/models/workspace.server.ts b/apps/webapp/app/models/workspace.server.ts index 03ad9bbb..23ec58d8 100644 --- a/apps/webapp/app/models/workspace.server.ts +++ b/apps/webapp/app/models/workspace.server.ts @@ -2,6 +2,7 @@ import { type Workspace } from "@core/database"; import { prisma } from "~/db.server"; import { ensureBillingInitialized } from "~/services/billing.server"; import { ensureDefaultProviders } from "~/services/llm-provider.server"; +import { maybeRegisterDefaultGateway } from "~/services/gateway/default.server"; import { sendEmail } from "~/services/email.server"; import { logger } from "~/services/logger.service"; import { LabelService } from "~/services/label.server"; @@ -53,6 +54,12 @@ export async function createWorkspace( await ensureBillingInitialized(workspace.id, input.userId); await ensureDefaultProviders(); + // Auto-register the default gateway if configured via env vars. + await maybeRegisterDefaultGateway({ + workspaceId: workspace.id, + userId: input.userId, + }); + // Create persona document and label try { const labelService = new LabelService(); diff --git a/apps/webapp/app/services/gateway/default.server.ts b/apps/webapp/app/services/gateway/default.server.ts new file mode 100644 index 00000000..0f202d3d --- /dev/null +++ b/apps/webapp/app/services/gateway/default.server.ts @@ -0,0 +1,36 @@ +import { env } from "~/env.server"; +import { registerGateway } from "./register.server"; +import { logger } from "~/services/logger.service"; + +export async function maybeRegisterDefaultGateway({ + workspaceId, + userId, +}: { + workspaceId: string; + userId: string; +}): Promise { + const url = env.DEFAULT_GATEWAY_URL; + const key = env.COREBRAIN_GATEWAY_SECURITY_KEY; + if (!url || !key) return; + + try { + const result = await registerGateway({ + baseUrl: url, + securityKey: key, + name: env.DEFAULT_GATEWAY_NAME, + workspaceId, + userId, + }); + if (!result.ok) { + logger.warn( + `[default-gateway] auto-registration failed: ${result.error}`, + ); + } else { + logger.info( + `[default-gateway] registered gateway ${result.gatewayId} for workspace ${workspaceId}`, + ); + } + } catch (err) { + logger.warn(`[default-gateway] auto-registration threw: ${err}`); + } +} From 633ec2323188b30df1e7d7aabb596dee2a30ed26 Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Fri, 15 May 2026 04:49:45 +0000 Subject: [PATCH 6/8] fix: rename gateway env vars to COREBRAIN_DEFAULT_GATEWAY_* prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename DEFAULT_GATEWAY_URL → COREBRAIN_DEFAULT_GATEWAY_URL - Rename DEFAULT_GATEWAY_NAME → COREBRAIN_DEFAULT_GATEWAY_NAME - Add COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY (distinct from the gateway container's own COREBRAIN_GATEWAY_SECURITY_KEY, same value) - Add defensive try/catch at workspace.server.ts call site - Update .env.example, docker-compose.yaml passthrough, and README Co-Authored-By: Claude Sonnet 4.6 --- apps/webapp/app/env.server.ts | 6 +++--- apps/webapp/app/models/workspace.server.ts | 12 ++++++++---- .../app/services/gateway/default.server.ts | 6 +++--- hosting/docker/.env.example | 13 ++++++++----- hosting/docker/README.md | 18 ++++++++++-------- hosting/docker/docker-compose.yaml | 6 +++--- 6 files changed, 35 insertions(+), 26 deletions(-) diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index a6a5b4d2..b919918c 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -241,9 +241,9 @@ const EnvironmentSchema = z RESEND_WEBHOOK_SECRET: z.string().optional(), // Default gateway auto-registration - DEFAULT_GATEWAY_URL: z.string().url().optional(), - DEFAULT_GATEWAY_NAME: z.string().min(1).max(64).default("local-gateway"), - COREBRAIN_GATEWAY_SECURITY_KEY: z.string().optional(), + COREBRAIN_DEFAULT_GATEWAY_URL: z.string().url().optional(), + COREBRAIN_DEFAULT_GATEWAY_NAME: z.string().min(1).max(64).default("local-gateway"), + COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY: z.string().optional(), // Sentry SENTRY_DSN: z.string().optional(), diff --git a/apps/webapp/app/models/workspace.server.ts b/apps/webapp/app/models/workspace.server.ts index 23ec58d8..3c5a535a 100644 --- a/apps/webapp/app/models/workspace.server.ts +++ b/apps/webapp/app/models/workspace.server.ts @@ -55,10 +55,14 @@ export async function createWorkspace( await ensureDefaultProviders(); // Auto-register the default gateway if configured via env vars. - await maybeRegisterDefaultGateway({ - workspaceId: workspace.id, - userId: input.userId, - }); + try { + await maybeRegisterDefaultGateway({ + workspaceId: workspace.id, + userId: input.userId, + }); + } catch (e) { + logger.error(`[default-gateway] unexpected error during auto-registration: ${e}`); + } // Create persona document and label try { diff --git a/apps/webapp/app/services/gateway/default.server.ts b/apps/webapp/app/services/gateway/default.server.ts index 0f202d3d..ad3787fb 100644 --- a/apps/webapp/app/services/gateway/default.server.ts +++ b/apps/webapp/app/services/gateway/default.server.ts @@ -9,15 +9,15 @@ export async function maybeRegisterDefaultGateway({ workspaceId: string; userId: string; }): Promise { - const url = env.DEFAULT_GATEWAY_URL; - const key = env.COREBRAIN_GATEWAY_SECURITY_KEY; + const url = env.COREBRAIN_DEFAULT_GATEWAY_URL; + const key = env.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY; if (!url || !key) return; try { const result = await registerGateway({ baseUrl: url, securityKey: key, - name: env.DEFAULT_GATEWAY_NAME, + name: env.COREBRAIN_DEFAULT_GATEWAY_NAME, workspaceId, userId, }); diff --git a/hosting/docker/.env.example b/hosting/docker/.env.example index c3ff824a..1c6ff75e 100644 --- a/hosting/docker/.env.example +++ b/hosting/docker/.env.example @@ -101,11 +101,14 @@ COREBRAIN_GATEWAY_SECURITY_KEY= COREBRAIN_API_KEY= # Step 2 (optional) — Auto-register the gateway for every new workspace. -# Set DEFAULT_GATEWAY_URL to the URL where the gateway is reachable FROM the webapp -# container (not from your browser). When using the docker-compose overlay the -# gateway is on the same Docker network, so use the Docker service name: -DEFAULT_GATEWAY_URL=http://corebrain-gateway:7787 -DEFAULT_GATEWAY_NAME=local-gateway +# The webapp reads COREBRAIN_DEFAULT_GATEWAY_* to find and verify the gateway. +# Set COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY to the same value as COREBRAIN_GATEWAY_SECURITY_KEY above. +# Set COREBRAIN_DEFAULT_GATEWAY_URL to the URL where the gateway is reachable FROM +# the webapp container (not from your browser). When using the docker-compose overlay +# the gateway is on the same Docker network, so use the Docker service name: +COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY= +COREBRAIN_DEFAULT_GATEWAY_URL=http://corebrain-gateway:7787 +COREBRAIN_DEFAULT_GATEWAY_NAME=local-gateway # Gateway image tag to deploy. Defaults to latest; pin to a specific version for stability. # GATEWAY_VERSION=0.6.4 diff --git a/hosting/docker/README.md b/hosting/docker/README.md index 0a84f2da..b49ed615 100644 --- a/hosting/docker/README.md +++ b/hosting/docker/README.md @@ -34,10 +34,11 @@ Both compose files read from the same `.env` file. The gateway-specific variable | Variable | Where used | Purpose | |---|---|---| -| `COREBRAIN_GATEWAY_SECURITY_KEY` | gateway container + webapp | Shared secret that authenticates the webapp to the gateway | +| `COREBRAIN_GATEWAY_SECURITY_KEY` | gateway container | Security key the gateway uses for its own identity | +| `COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY` | webapp | Same key value — used by the webapp to verify and register the gateway | | `COREBRAIN_API_KEY` | gateway container | Personal access token from Core → Settings → Tokens | -| `DEFAULT_GATEWAY_URL` | webapp | Internal URL the webapp uses to reach the gateway (`http://corebrain-gateway:7787`) | -| `DEFAULT_GATEWAY_NAME` | webapp | Name stored in DB when auto-registering (default: `local-gateway`) | +| `COREBRAIN_DEFAULT_GATEWAY_URL` | webapp | Internal URL the webapp uses to reach the gateway (`http://corebrain-gateway:7787`) | +| `COREBRAIN_DEFAULT_GATEWAY_NAME` | webapp | Name stored in DB when auto-registering (default: `local-gateway`) | | `COREBRAIN_GATEWAY_NAME` | gateway container | Name the gateway reports in its own manifest (default: `cloud-gateway`) | | `COREBRAIN_GATEWAY_HTTP_PORT` | host port mapping | Host port the gateway listens on (default: `7787`) | | `CLAUDE_CODE_OAUTH_TOKEN` | gateway container | Long-lived Claude Code token (`claude setup-token`); optional | @@ -48,15 +49,16 @@ The gateway's `COREBRAIN_API_URL` is hardcoded to `http://core:3000` (the webapp ## Auto-registration -When both `DEFAULT_GATEWAY_URL` and `COREBRAIN_GATEWAY_SECURITY_KEY` are set in `.env`, the webapp automatically registers the gateway for every new workspace. No manual "Register gateway" step in the UI is needed. +When both `COREBRAIN_DEFAULT_GATEWAY_URL` and `COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY` are set in `.env`, the webapp automatically registers the gateway for every new workspace. No manual "Register gateway" step in the UI is needed. Minimum `.env` additions to enable auto-registration: ```env COREBRAIN_GATEWAY_SECURITY_KEY= +COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY= COREBRAIN_API_KEY= -DEFAULT_GATEWAY_URL=http://corebrain-gateway:7787 -DEFAULT_GATEWAY_NAME=local-gateway +COREBRAIN_DEFAULT_GATEWAY_URL=http://corebrain-gateway:7787 +COREBRAIN_DEFAULT_GATEWAY_NAME=local-gateway ``` ## First-boot note: security key from logs @@ -67,9 +69,9 @@ If `COREBRAIN_GATEWAY_SECURITY_KEY` is left blank in `.env`, the gateway generat docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml logs corebrain-gateway | grep -i "security key" ``` -Copy the printed key into `.env` as `COREBRAIN_GATEWAY_SECURITY_KEY`, then either: +Copy the printed key into `.env` as both `COREBRAIN_GATEWAY_SECURITY_KEY` and `COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY`, then either: -- Set `DEFAULT_GATEWAY_URL` and restart so the webapp auto-registers it, **or** +- Set `COREBRAIN_DEFAULT_GATEWAY_URL` and re-create the workspace so the webapp auto-registers it, **or** - Register it manually via the workspace gateway UI using that key. After setting the key in `.env`, re-create the gateway container so it picks up the new value (plain `restart` does not re-read `.env`): diff --git a/hosting/docker/docker-compose.yaml b/hosting/docker/docker-compose.yaml index df3fb2db..8965f965 100644 --- a/hosting/docker/docker-compose.yaml +++ b/hosting/docker/docker-compose.yaml @@ -60,9 +60,9 @@ services: - GRAPH_PROVIDER=${GRAPH_PROVIDER} - VECTOR_PROVIDER=${VECTOR_PROVIDER} - MODEL_PROVIDER=${MODEL_PROVIDER} - - DEFAULT_GATEWAY_URL=${DEFAULT_GATEWAY_URL:-} - - DEFAULT_GATEWAY_NAME=${DEFAULT_GATEWAY_NAME:-local-gateway} - - COREBRAIN_GATEWAY_SECURITY_KEY=${COREBRAIN_GATEWAY_SECURITY_KEY:-} + - COREBRAIN_DEFAULT_GATEWAY_URL=${COREBRAIN_DEFAULT_GATEWAY_URL:-} + - COREBRAIN_DEFAULT_GATEWAY_NAME=${COREBRAIN_DEFAULT_GATEWAY_NAME:-local-gateway} + - COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY=${COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY:-} ports: - "3033:3000" - "5555:5555" From 1e6d8f7b1fad3ca78a40b14f96806b9e3c35fb1f Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Fri, 15 May 2026 04:59:55 +0000 Subject: [PATCH 7/8] test: add unit tests for maybeRegisterDefaultGateway MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 15 tests covering: - Guard conditions (missing URL, key, both, empty string) - Successful registration with correct args - Custom gateway name from env - Success logging - Failure logging (ok: false) - Error resilience (registerGateway throws → logged, not re-thrown) - Idempotency pass-through to registerGateway Co-Authored-By: Claude Sonnet 4.6 --- .../gateway/__tests__/default.server.test.ts | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 apps/webapp/app/services/gateway/__tests__/default.server.test.ts diff --git a/apps/webapp/app/services/gateway/__tests__/default.server.test.ts b/apps/webapp/app/services/gateway/__tests__/default.server.test.ts new file mode 100644 index 00000000..3cb0a0d5 --- /dev/null +++ b/apps/webapp/app/services/gateway/__tests__/default.server.test.ts @@ -0,0 +1,198 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; + +// ── Mock env ──────────────────────────────────────────────────────────────── +// We control the env object so tests can override individual vars. +const mockEnv = { + COREBRAIN_DEFAULT_GATEWAY_URL: undefined as string | undefined, + COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY: undefined as string | undefined, + COREBRAIN_DEFAULT_GATEWAY_NAME: "local-gateway", +}; + +vi.mock("~/env.server", () => ({ env: mockEnv })); + +// ── Mock registerGateway ──────────────────────────────────────────────────── +const mockRegisterGateway = vi.fn(); +vi.mock("../register.server", () => ({ registerGateway: mockRegisterGateway })); + +// ── Mock logger ───────────────────────────────────────────────────────────── +const mockLogger = { warn: vi.fn(), info: vi.fn(), error: vi.fn() }; +vi.mock("~/services/logger.service", () => ({ logger: mockLogger })); + +// Import AFTER mocks are registered +import { maybeRegisterDefaultGateway } from "../default.server"; + +// ── Helpers ────────────────────────────────────────────────────────────────── +const WORKSPACE_ID = "ws-test-001"; +const USER_ID = "user-test-001"; +const GATEWAY_URL = "http://corebrain-gateway:7787"; +const SECURITY_KEY = "test-security-key-32chars-minimum"; + +function callSubject() { + return maybeRegisterDefaultGateway({ workspaceId: WORKSPACE_ID, userId: USER_ID }); +} + +describe("maybeRegisterDefaultGateway", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = undefined; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = undefined; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_NAME = "local-gateway"; + }); + + // ── Guard conditions ─────────────────────────────────────────────────────── + + it("returns without calling registerGateway when URL is not set", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = SECURITY_KEY; + await callSubject(); + expect(mockRegisterGateway).not.toHaveBeenCalled(); + }); + + it("returns without calling registerGateway when security key is not set", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = GATEWAY_URL; + await callSubject(); + expect(mockRegisterGateway).not.toHaveBeenCalled(); + }); + + it("returns without calling registerGateway when both vars are absent", async () => { + await callSubject(); + expect(mockRegisterGateway).not.toHaveBeenCalled(); + }); + + it("returns without calling registerGateway when URL is empty string", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = ""; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = SECURITY_KEY; + await callSubject(); + expect(mockRegisterGateway).not.toHaveBeenCalled(); + }); + + it("returns without calling registerGateway when security key is empty string", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = GATEWAY_URL; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = ""; + await callSubject(); + expect(mockRegisterGateway).not.toHaveBeenCalled(); + }); + + // ── Successful registration ──────────────────────────────────────────────── + + it("calls registerGateway with correct args when both env vars are set", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = GATEWAY_URL; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = SECURITY_KEY; + mockRegisterGateway.mockResolvedValue({ ok: true, gatewayId: "gw-123" }); + + await callSubject(); + + expect(mockRegisterGateway).toHaveBeenCalledOnce(); + expect(mockRegisterGateway).toHaveBeenCalledWith({ + baseUrl: GATEWAY_URL, + securityKey: SECURITY_KEY, + name: "local-gateway", + workspaceId: WORKSPACE_ID, + userId: USER_ID, + }); + }); + + it("uses COREBRAIN_DEFAULT_GATEWAY_NAME from env", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = GATEWAY_URL; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = SECURITY_KEY; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_NAME = "my-custom-gateway"; + mockRegisterGateway.mockResolvedValue({ ok: true, gatewayId: "gw-456" }); + + await callSubject(); + + expect(mockRegisterGateway).toHaveBeenCalledWith( + expect.objectContaining({ name: "my-custom-gateway" }), + ); + }); + + it("logs info on successful registration", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = GATEWAY_URL; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = SECURITY_KEY; + mockRegisterGateway.mockResolvedValue({ ok: true, gatewayId: "gw-789" }); + + await callSubject(); + + expect(mockLogger.info).toHaveBeenCalledOnce(); + expect(mockLogger.info).toHaveBeenCalledWith( + expect.stringContaining("[default-gateway]"), + ); + expect(mockLogger.warn).not.toHaveBeenCalled(); + }); + + // ── Failed registration ──────────────────────────────────────────────────── + + it("logs warn and does not throw when registerGateway returns ok: false", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = GATEWAY_URL; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = SECURITY_KEY; + mockRegisterGateway.mockResolvedValue({ ok: false, error: "gateway unreachable" }); + + await expect(callSubject()).resolves.toBeUndefined(); + + expect(mockLogger.warn).toHaveBeenCalledOnce(); + expect(mockLogger.warn).toHaveBeenCalledWith( + expect.stringContaining("[default-gateway]"), + ); + expect(mockLogger.info).not.toHaveBeenCalled(); + }); + + it("includes the error message in the warn log", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = GATEWAY_URL; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = SECURITY_KEY; + mockRegisterGateway.mockResolvedValue({ ok: false, error: "gateway unreachable" }); + + await callSubject(); + + expect(mockLogger.warn).toHaveBeenCalledWith( + expect.stringContaining("gateway unreachable"), + ); + }); + + // ── Error resilience ─────────────────────────────────────────────────────── + + it("does not throw when registerGateway rejects", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = GATEWAY_URL; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = SECURITY_KEY; + mockRegisterGateway.mockRejectedValue(new Error("network timeout")); + + await expect(callSubject()).resolves.toBeUndefined(); + }); + + it("logs warn (not re-throws) when registerGateway throws", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = GATEWAY_URL; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = SECURITY_KEY; + mockRegisterGateway.mockRejectedValue(new Error("network timeout")); + + await callSubject(); + + expect(mockLogger.warn).toHaveBeenCalledOnce(); + expect(mockLogger.warn).toHaveBeenCalledWith( + expect.stringContaining("[default-gateway]"), + ); + }); + + // ── Idempotency (via registerGateway contract) ───────────────────────────── + + it("passes the same args on repeated calls (idempotency delegated to registerGateway)", async () => { + mockEnv.COREBRAIN_DEFAULT_GATEWAY_URL = GATEWAY_URL; + mockEnv.COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY = SECURITY_KEY; + mockRegisterGateway.mockResolvedValue({ ok: true, gatewayId: "gw-123" }); + + await callSubject(); + await callSubject(); + + expect(mockRegisterGateway).toHaveBeenCalledTimes(2); + expect(mockRegisterGateway).toHaveBeenNthCalledWith(1, { + baseUrl: GATEWAY_URL, + securityKey: SECURITY_KEY, + name: "local-gateway", + workspaceId: WORKSPACE_ID, + userId: USER_ID, + }); + expect(mockRegisterGateway).toHaveBeenNthCalledWith(2, { + baseUrl: GATEWAY_URL, + securityKey: SECURITY_KEY, + name: "local-gateway", + workspaceId: WORKSPACE_ID, + userId: USER_ID, + }); + }); +}); From bf48bedbe57e44a2cbe46f7ed961d5b65675b046 Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Fri, 15 May 2026 10:36:43 +0000 Subject: [PATCH 8/8] refactor: use hosting/gateway/docker-compose.yaml instead of overlay - Delete hosting/docker/docker-compose.gateway.yaml (duplicate overlay) - Update README to instruct running both compose files from the repo root: docker compose \ -f hosting/docker/docker-compose.yaml \ -f hosting/gateway/docker-compose.yaml \ up -d - Document network topology: gateway reaches webapp via host-exposed port (COREBRAIN_API_URL), not Docker internal DNS, since the two services are on different Docker networks - Update .env.example: COREBRAIN_API_URL + COREBRAIN_DEFAULT_GATEWAY_URL now documented with host.docker.internal examples instead of Docker service-name addresses Co-Authored-By: Claude Sonnet 4.6 --- hosting/docker/.env.example | 35 ++++---- hosting/docker/README.md | 74 ++++++++++++----- hosting/docker/docker-compose.gateway.yaml | 94 ---------------------- 3 files changed, 72 insertions(+), 131 deletions(-) delete mode 100644 hosting/docker/docker-compose.gateway.yaml diff --git a/hosting/docker/.env.example b/hosting/docker/.env.example index 1c6ff75e..fad2fa55 100644 --- a/hosting/docker/.env.example +++ b/hosting/docker/.env.example @@ -87,35 +87,38 @@ VECTOR_PROVIDER=pgvector MODEL_PROVIDER=vercel-ai ########### Gateway (optional) ############ -# Add the gateway to your stack with: -# docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml up -d +# Add the gateway alongside the core stack with: +# docker compose \ +# -f hosting/docker/docker-compose.yaml \ +# -f hosting/gateway/docker-compose.yaml \ +# up -d +# (run from the repo root; this file is loaded automatically as the .env for both services) # # Step 1 — Pre-set a security key so you don't need to copy it from logs. # Generate with: openssl rand -hex 32 -# This same value goes in hosting/gateway/.env (COREBRAIN_GATEWAY_SECURITY_KEY) -# OR use the overlay file above which shares this .env automatically. COREBRAIN_GATEWAY_SECURITY_KEY= +# Where the gateway reports back to (your CORE webapp). +# Docker Desktop: http://host.docker.internal:3033 +# Linux server: your server's public URL or IP, e.g. http://192.168.1.10:3033 +COREBRAIN_API_URL= + # Your personal access token from Core → Settings → Tokens. -# Required for the gateway to authenticate against the webapp. COREBRAIN_API_KEY= -# Step 2 (optional) — Auto-register the gateway for every new workspace. -# The webapp reads COREBRAIN_DEFAULT_GATEWAY_* to find and verify the gateway. -# Set COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY to the same value as COREBRAIN_GATEWAY_SECURITY_KEY above. -# Set COREBRAIN_DEFAULT_GATEWAY_URL to the URL where the gateway is reachable FROM -# the webapp container (not from your browser). When using the docker-compose overlay -# the gateway is on the same Docker network, so use the Docker service name: +# Step 2 (optional) — Auto-register the gateway for every new workspace so no +# manual "Register gateway" UI step is needed. +# COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY: same value as COREBRAIN_GATEWAY_SECURITY_KEY above. +# COREBRAIN_DEFAULT_GATEWAY_URL: URL where the webapp container can reach the gateway. +# Docker Desktop: http://host.docker.internal:7787 +# Linux server: your server's public URL or IP, e.g. http://192.168.1.10:7787 COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY= -COREBRAIN_DEFAULT_GATEWAY_URL=http://corebrain-gateway:7787 +COREBRAIN_DEFAULT_GATEWAY_URL= COREBRAIN_DEFAULT_GATEWAY_NAME=local-gateway -# Gateway image tag to deploy. Defaults to latest; pin to a specific version for stability. -# GATEWAY_VERSION=0.6.4 - # Public-facing port mapping. Adjust if 7787 is taken on your host. COREBRAIN_GATEWAY_HTTP_PORT=7787 -# Coding agent credentials (stored in gateway container only, not in webapp) +# Coding agent credentials (optional — stored in gateway container only) CLAUDE_CODE_OAUTH_TOKEN= GITHUB_TOKEN= diff --git a/hosting/docker/README.md b/hosting/docker/README.md index b49ed615..1e53eb0f 100644 --- a/hosting/docker/README.md +++ b/hosting/docker/README.md @@ -1,10 +1,11 @@ # Docker Compose — CoreBrain Self-Hosted -All commands are run from this directory (`hosting/docker/`). +All commands below are run from the **repo root** (or adjust paths accordingly). ## Quick start (core stack only) ```bash +cd hosting/docker cp .env.example .env # Edit .env — set secrets, AI provider keys, NEO4J_PASSWORD, etc. docker compose up -d @@ -14,50 +15,75 @@ The webapp is available at `http://localhost:3033`. ## Adding the gateway -Run both compose files together with `-f`: +Run the core stack and the gateway together using two `-f` flags: ```bash -docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml up -d +docker compose \ + -f hosting/docker/docker-compose.yaml \ + -f hosting/gateway/docker-compose.yaml \ + up -d ``` -The gateway container (`corebrain-gateway`) joins the same `core` Docker network as the webapp and is reachable at `http://corebrain-gateway:7787` from other containers. It is exposed on your host at the port set by `COREBRAIN_GATEWAY_HTTP_PORT` (default `7787`). +Docker Compose reads `.env` from the directory of the first `-f` file (`hosting/docker/`), so a single `hosting/docker/.env` covers both services. To stop everything: ```bash -docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml down +docker compose \ + -f hosting/docker/docker-compose.yaml \ + -f hosting/gateway/docker-compose.yaml \ + down +``` + +### Network note + +The gateway runs in its own Docker network segment and reaches the webapp over the **host-exposed port** (`3033`), not the internal Docker network. Set `COREBRAIN_API_URL` accordingly: + +| Environment | `COREBRAIN_API_URL` value | +|---|---| +| Docker Desktop (Mac / Windows) | `http://host.docker.internal:3033` | +| Linux — add `extra_hosts` (see below) | `http://host.docker.internal:3033` | +| Remote server | `https://your-core-domain.example.com` | + +For Linux, add this to the `corebrain-gateway` service in a local override, or set it in `.env` and rely on the OS-level hostname: + +```yaml +# hosting/gateway/docker-compose.override.yaml (local only, git-ignored) +services: + corebrain-gateway: + extra_hosts: + - "host.docker.internal:host-gateway" ``` ## Environment variable wiring -Both compose files read from the same `.env` file. The gateway-specific variables are already included in `.env.example`: +Both compose files read from `hosting/docker/.env`. The gateway-specific variables are included in `.env.example`: | Variable | Where used | Purpose | |---|---|---| +| `COREBRAIN_API_URL` | gateway container | URL of the CORE webapp (see network note above) | +| `COREBRAIN_API_KEY` | gateway container | Personal access token from Core → Settings → Tokens | | `COREBRAIN_GATEWAY_SECURITY_KEY` | gateway container | Security key the gateway uses for its own identity | | `COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY` | webapp | Same key value — used by the webapp to verify and register the gateway | -| `COREBRAIN_API_KEY` | gateway container | Personal access token from Core → Settings → Tokens | -| `COREBRAIN_DEFAULT_GATEWAY_URL` | webapp | Internal URL the webapp uses to reach the gateway (`http://corebrain-gateway:7787`) | +| `COREBRAIN_DEFAULT_GATEWAY_URL` | webapp | URL the webapp uses to reach the gateway (same host note applies) | | `COREBRAIN_DEFAULT_GATEWAY_NAME` | webapp | Name stored in DB when auto-registering (default: `local-gateway`) | -| `COREBRAIN_GATEWAY_NAME` | gateway container | Name the gateway reports in its own manifest (default: `cloud-gateway`) | | `COREBRAIN_GATEWAY_HTTP_PORT` | host port mapping | Host port the gateway listens on (default: `7787`) | | `CLAUDE_CODE_OAUTH_TOKEN` | gateway container | Long-lived Claude Code token (`claude setup-token`); optional | -| `GITHUB_TOKEN` | gateway container | Token for `git clone` of private repos and author identity; optional | +| `GITHUB_TOKEN` | gateway container | Token for `git clone` of private repos; optional | | `GATEWAY_VERSION` | gateway image tag | Pin to a specific release, e.g. `0.6.4`; omit for `latest` | -The gateway's `COREBRAIN_API_URL` is hardcoded to `http://core:3000` (the webapp's internal address) in `docker-compose.gateway.yaml` — do not set this in `.env`. - ## Auto-registration -When both `COREBRAIN_DEFAULT_GATEWAY_URL` and `COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY` are set in `.env`, the webapp automatically registers the gateway for every new workspace. No manual "Register gateway" step in the UI is needed. +When both `COREBRAIN_DEFAULT_GATEWAY_URL` and `COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY` are set, the webapp automatically registers the gateway for every new workspace. No manual "Register gateway" step in the UI is needed. -Minimum `.env` additions to enable auto-registration: +Minimum `.env` additions to enable auto-registration (Docker Desktop example): ```env COREBRAIN_GATEWAY_SECURITY_KEY= -COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY= +COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY= COREBRAIN_API_KEY= -COREBRAIN_DEFAULT_GATEWAY_URL=http://corebrain-gateway:7787 +COREBRAIN_API_URL=http://host.docker.internal:3033 +COREBRAIN_DEFAULT_GATEWAY_URL=http://host.docker.internal:7787 COREBRAIN_DEFAULT_GATEWAY_NAME=local-gateway ``` @@ -66,16 +92,22 @@ COREBRAIN_DEFAULT_GATEWAY_NAME=local-gateway If `COREBRAIN_GATEWAY_SECURITY_KEY` is left blank in `.env`, the gateway generates a random key on first boot and prints it to stdout: ```bash -docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml logs corebrain-gateway | grep -i "security key" +docker compose \ + -f hosting/docker/docker-compose.yaml \ + -f hosting/gateway/docker-compose.yaml \ + logs corebrain-gateway | grep -i "security key" ``` Copy the printed key into `.env` as both `COREBRAIN_GATEWAY_SECURITY_KEY` and `COREBRAIN_DEFAULT_GATEWAY_SECURITY_KEY`, then either: -- Set `COREBRAIN_DEFAULT_GATEWAY_URL` and re-create the workspace so the webapp auto-registers it, **or** -- Register it manually via the workspace gateway UI using that key. +- Set `COREBRAIN_DEFAULT_GATEWAY_URL` and create a new workspace so the webapp auto-registers it, **or** +- Register it manually via the workspace gateway UI using that key and the gateway's public URL. -After setting the key in `.env`, re-create the gateway container so it picks up the new value (plain `restart` does not re-read `.env`): +After updating `.env`, re-create the gateway container so it picks up the new value (`restart` does not re-read `.env`): ```bash -docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml up -d corebrain-gateway +docker compose \ + -f hosting/docker/docker-compose.yaml \ + -f hosting/gateway/docker-compose.yaml \ + up -d corebrain-gateway ``` diff --git a/hosting/docker/docker-compose.gateway.yaml b/hosting/docker/docker-compose.gateway.yaml deleted file mode 100644 index 9bd8cf07..00000000 --- a/hosting/docker/docker-compose.gateway.yaml +++ /dev/null @@ -1,94 +0,0 @@ -# CoreBrain Gateway — overlay for the core stack. -# -# Run with: -# docker compose -f docker-compose.yaml -f docker-compose.gateway.yaml up -d -# -# The gateway joins the existing `core` Docker network so it can reach the -# webapp at http://core:3000 without exposing the API publicly. All env vars -# (except COREBRAIN_API_URL, which is hardcoded to the internal address) are -# sourced from the shared .env file alongside docker-compose.yaml. -# -# Persistent volumes: -# workspace → /app cloned repos + worktrees -# corebrain-home → /home/corebrain gateway state (.corebrain), agent -# login + session state (.claude, -# .codex, …), playwright chromium -# cache (.cache/ms-playwright). One -# mount covers every agent's $HOME -# state so adding a new coding agent -# doesn't need a new volume. - -x-logging: &logging-config - driver: ${LOGGING_DRIVER:-local} - options: - max-size: ${LOGGING_MAX_SIZE:-20m} - max-file: ${LOGGING_MAX_FILES:-5} - compress: ${LOGGING_COMPRESS:-true} - -services: - corebrain-gateway: - container_name: corebrain-gateway - image: redplanethq/core-gateway:${GATEWAY_VERSION:-latest} - # Image is published amd64-only today. Apple Silicon hosts run it under - # Rosetta emulation; remove this line once we publish a multi-arch image. - platform: linux/amd64 - restart: ${RESTART_POLICY:-unless-stopped} - logging: *logging-config - depends_on: - core: - condition: service_started - environment: - # ── CORE webapp wiring ─────────────────────────────────────────── - # API_URL is hardcoded to the internal Docker network address so the - # gateway talks directly to the webapp container without going through - # the public internet. API_KEY is sourced from the shared .env. - - COREBRAIN_API_URL=http://core:3000 - - COREBRAIN_API_KEY=${COREBRAIN_API_KEY} - - # ── Gateway identity ──────────────────────────────────────────── - - COREBRAIN_GATEWAY_NAME=${COREBRAIN_GATEWAY_NAME:-cloud-gateway} - - COREBRAIN_GATEWAY_DESCRIPTION=${COREBRAIN_GATEWAY_DESCRIPTION:-Cloud-hosted CoreBrain gateway} - - # Security key used by the webapp to authenticate to this gateway. - # Leave unset on first boot; the gateway will generate one and print - # it to `docker compose logs`. Pin it to a known value here once you - # have it, so subsequent restarts don't re-prompt. - - COREBRAIN_GATEWAY_SECURITY_KEY=${COREBRAIN_GATEWAY_SECURITY_KEY:-} - - # ── Coding agent auth (env-only — no webapp UI for tokens) ────── - # claude-code: long-lived token from `claude setup-token`. - - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN:-} - # codex-cli: standard OpenAI API key. - - OPENAI_API_KEY=${OPENAI_API_KEY:-} - - # ── Git credentials for `git clone` of private repos ──────────── - # The entrypoint configures the credential helper from this token and - # auto-fetches author identity (name/email) from the GitHub API. - - GITHUB_TOKEN=${GITHUB_TOKEN:-} - - # ── Container baked-in defaults ────────────────────────────────- - - COREBRAIN_DEFAULT_WORKSPACE=/app - - COREBRAIN_DEPLOY_MODE=docker - - # ── Slot toggles ───────────────────────────────────────────────- - # `COREBRAIN_SLOT_=true|false` overrides the on-disk slot - # config (set via `corebrain gateway config`). `files` defaults - # off in docker because `exec` already covers read/write/edit via - # shell — flip to `true` if you want both tool groups exposed. - # The same pattern works for BROWSER / CODING / EXEC. - - COREBRAIN_SLOT_FILES=${COREBRAIN_SLOT_FILES:-false} - ports: - - "${COREBRAIN_GATEWAY_HTTP_PORT:-7787}:7787" - volumes: - - workspace:/app - - corebrain-home:/home/corebrain - networks: - - core - -networks: - core: - external: true - -volumes: - workspace: - corebrain-home: