Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ docker compose -f oci://ghcr.io/chatbotkit/platform-community:latest logs platfo

See [Deployment](./docs/deployment.md) for details.

On an Apple silicon Mac you can skip Docker altogether:
[ChatBotKit Studio](https://github.com/chatbotkit/studio) is a native macOS
app that runs this same platform in an app-private VM, with model providers,
logs and updates managed from the app.

## Local development

Binary assets are stored with Git LFS, so install it (`git lfs install`)
Expand Down
7 changes: 2 additions & 5 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,10 @@ ENV NODE_OPTIONS="--max-old-space-size=$NODE_HEAP_MB --require /app/platform/scr
ARG SITE_URL=http://cbk.localhost:3000
ENV SITE_URL=$SITE_URL

# @note space, portal and app hosts are selected at runtime through proxy.ts;
# partner host rewrites are generated at build time, so their runtime values
# must match the build
# @note space, portal, app and partner hosts are selected at server startup;
# app apex is also used for build defaults
ARG APP_APEX=
ENV APP_APEX=$APP_APEX
ARG PARTNERS_APEX=
ENV PARTNERS_APEX=$PARTNERS_APEX

# @note build defaults only; shell host routing uses the values supplied at
# server startup
Expand Down
6 changes: 5 additions & 1 deletion docker/distro/community/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,22 @@ services:
NODE_ENV: production
PORT: 3000
SITE_URL: ${SITE_URL:-http://cbk.localhost:3000}
STATIC_URL: ${STATIC_URL:-}
API_URL: ${API_URL:-}
HOSTS_CONFIG: ${HOSTS_CONFIG:-}
NEXTAUTH_URL: ${NEXTAUTH_URL:-http://cbk.localhost:3000}
# @note realtime channels (voice, avatars) meet at a relay the platform
# process hosts itself on RELAY_PORT. Both that process and a host
# browser dial RELAY_URL, so loopback serves both; a browser elsewhere
# needs an address it can reach instead (and TLS if the site has it)
RELAY_PORT: 3001
RELAY_URL: ${RELAY_URL:-http://cbk-relay.localhost:3001}
# @note space, portal and app hosts read their settings at server startup;
# @note host routing reads its settings at server startup;
# recreate the container to change domains without rebuilding the image
SPACE_APEX: ${SPACE_APEX:-cbk-space.localhost}
PORTAL_APEX: ${PORTAL_APEX:-cbk-portal.localhost}
APP_APEX: ${APP_APEX:-}
PARTNERS_APEX: ${PARTNERS_APEX:-}
# @note cookies do not cross hosts, so sign in on the shell host itself
APP_MAIN_ORIGIN: ${APP_MAIN_ORIGIN:-http://cbk-apps.localhost:3000}
APP_LABS_ORIGIN: ${APP_LABS_ORIGIN:-http://cbk-labs.localhost:3000}
Expand Down
6 changes: 5 additions & 1 deletion docker/distro/studio/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ services:
NODE_ENV: production
PORT: 3000
SITE_URL: ${SITE_URL:-http://cbk.localhost:3000}
STATIC_URL: ${STATIC_URL:-}
API_URL: ${API_URL:-}
HOSTS_CONFIG: ${HOSTS_CONFIG:-}
NEXTAUTH_URL: ${NEXTAUTH_URL:-http://cbk.localhost:3000}
# @note an explicitly empty value restores ordinary email sign-in
NEXTAUTH_TRUSTED_SIGNIN: ${NEXTAUTH_TRUSTED_SIGNIN-true}
Expand All @@ -76,11 +79,12 @@ services:
# needs an address it can reach instead (and TLS if the site has it)
RELAY_PORT: 3001
RELAY_URL: ${RELAY_URL:-http://cbk-relay.localhost:3001}
# @note space, portal and app hosts read their settings at server startup;
# @note host routing reads its settings at server startup;
# recreate the container to change domains without rebuilding the image
SPACE_APEX: ${SPACE_APEX:-cbk-space.localhost}
PORTAL_APEX: ${PORTAL_APEX:-cbk-portal.localhost}
APP_APEX: ${APP_APEX:-}
PARTNERS_APEX: ${PARTNERS_APEX:-}
# @note cookies do not cross hosts, so sign in on the shell host itself
APP_MAIN_ORIGIN: ${APP_MAIN_ORIGIN:-http://cbk-apps.localhost:3000}
APP_LABS_ORIGIN: ${APP_LABS_ORIGIN:-http://cbk-labs.localhost:3000}
Expand Down
38 changes: 27 additions & 11 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ stops booting until they edit it. So:
migration easier.
- Any exception travels in the release notes as a configuration migration.

`HOSTS_CONFIG` is shared by build-time routing and runtime URL selection. The
build flattens every configured API and static target into its routing rules.
`HOSTS_CONFIG` is shared by runtime routing and URL selection. The proxy reads
all configured API and static targets at server startup.
At runtime, request-context setup selects a mapping once from the authenticated
frontend host or trusted normalized request host. URL helpers then read only
the resolved context; the raw mapping is not exposed to the browser. The
Expand Down Expand Up @@ -225,9 +225,12 @@ Standalone app host routing also reads `APP_APEX` at server startup. Setting
catalogue receive subdomain routes. Community and Studio leave this apex empty
by default, so apps remain available by path.

Partner host rewrites are still generated when Next builds, so `PARTNERS_APEX`
must match the image's build-time value; see
[Deployment](./deployment.md#production-boundary).
Partner host routing and branding also read `PARTNERS_APEX` at server startup.
Changing it and recreating the container moves partner subdomains without
rebuilding. Partner custom domains come from the installed partner catalogue
and work even when the apex is unset. The public catalogue is empty by default;
adding or changing catalogue entries still requires packaging that catalogue
into the image. Community and Studio leave `PARTNERS_APEX` empty by default.

## App shell origins

Expand Down Expand Up @@ -287,12 +290,25 @@ Values are exact hostnames without a protocol, wildcard, path, query, or hash.
Every target that can receive a request should also appear in `match`, so a
request arriving on an API or static host selects the same mapping.

At build time, every `api` and `static` target is enabled unconditionally. At
runtime, context injection selects the mapping once when the authenticated
frontend host or normalized request host appears in `match`. Server URL helpers
read the resolved targets from that context. The HTML document exposes only the
resolved site, API, static, and widget hosts for client hooks. An unknown host
keeps the existing custom-domain behavior.
Every `api` and `static` target is enabled at server startup. Targets that are
also site hosts are excluded from host-specific routing, so a shared host
continues serving the application and its API at `/api/v1`. Changing targets,
`API_URL` or `STATIC_URL` requires restarting the server without rebuilding
the routing table. At runtime, context injection selects the mapping once when
the authenticated frontend host or normalized request host appears in `match`.
Server URL helpers read the resolved targets from that context. The HTML
document exposes only the resolved site, API, static, and widget hosts for
client hooks. An unknown host keeps the existing custom-domain behavior.

The proxy also uses this API host classification for browser security headers.
A dedicated API host is exempt regardless of its name; an ordinary site named
`api.example.com` keeps its browser protections. API paths and embeddable widget
paths retain their separate policies. These policies are loaded at server
startup, including the optional `SENTRY_HEADERS_REPORT_URI` reporting endpoint.
The proxy also owns trailing-slash normalization, so those responses receive
the same runtime policy before they leave the server. Their permanent status,
destinations and query parameters are preserved. Keep redirects that require
these headers in the proxy; Next's configured redirects run before it.

`SITE_URL` remains the canonical and requestless default. When no mapping is
selected, `API_URL`, `STATIC_URL`, and `WIDGET_URL` all fall back to
Expand Down
51 changes: 41 additions & 10 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ its workspaces kept under `/data/sandbox` in the same volume.
| `studio` | SQLite (default module) | Redis | Qdrant | Garage |

Studio starts as a copy of Community, with the same Docker build targets,
module defaults and services. Its separate Compose file lives at
module defaults and services. It is the flavor embedded by
[ChatBotKit Studio](https://github.com/chatbotkit/studio), the native macOS
app that runs the platform in an app-private VM without a Docker install, and
can also be run directly with Compose. Its separate Compose file lives at
[docker/distro/studio/compose.yml](../docker/distro/studio/compose.yml), and the
publish workflow produces `platform-studio`, `platform-studio-app` and
`platform-studio-init` under `ghcr.io/chatbotkit`, using the same channel tags
Expand Down Expand Up @@ -292,8 +295,9 @@ startup. Changing them and recreating the container moves those sites to the
new domains without rebuilding the image. Portal authentication and app
configuration continue to apply on the new domain. App hosts work the same way
through `APP_APEX`, `APP_MAIN_ORIGIN` and `APP_LABS_ORIGIN`; see
[Configuration](./configuration.md#app-shell-origins). Only `PARTNERS_APEX`
still has to match the build. Runtime service variables
[Configuration](./configuration.md#app-shell-origins). Partner routing and
branding also read `PARTNERS_APEX` at startup, using the installed partner
catalogue for custom domains and branding. Runtime service variables
such as the database, Redis, Qdrant and S3-compatible storage endpoints remain
configurable. Deployment identity that Next currently exposes through
`next.config.js` is still frozen at build time; do not present the same digest
Expand All @@ -304,13 +308,40 @@ as portable across arbitrary public domains until that migration is complete.
Every deployment serves the API at `/api/v1` on its own host - nothing to
configure. To advertise and serve it on a dedicated origin instead, set
`API_URL` (e.g. `https://api.example.com`), point that DNS name at the
deployment, and rebuild: the host is then routed to the API (answering under
the clean `/v1` path) and every externally advertised URL - webhook
registrations, embeds, the OpenAPI spec - follows it. Unset, advertised URLs
stay on the site host under `/api`. Multi-domain deployments name their API
hosts in `HOSTS_CONFIG` instead; see
[Configuration](./configuration.md#hosts_config). Both are read at build time,
so changing them requires a rebuild, not just a restart.
deployment, and restart the server: the host is then routed to the API under
the clean `/v1` path. Runtime API URL helpers use the configured origin. Unset,
advertised URLs stay on the site host under `/api`. Multi-domain deployments
name their API hosts in `HOSTS_CONFIG` instead; see
[Configuration](./configuration.md#hosts_config). Both are read at server
startup, so changing API hosts requires recreating the container without
rebuilding. The existing CORS policy applies to `/v1` on dedicated API hosts
and `/api/v1` on every host: any origin may call the API with a bearer token;
cookie credentials are not enabled. Community and Studio expose `API_URL` and
`HOSTS_CONFIG`, leaving both empty by default.

## Static host

`STATIC_URL` selects the origin for public assets and widget embeds. A dedicated
static hostname applies the existing static path restrictions, including the
text fallback for application pages. Leaving it unset serves those assets on
`SITE_URL` without restricting the site.

Static host routing reads `STATIC_URL` and the static targets in `HOSTS_CONFIG`
at server startup. Change the values and recreate the container to move static
hosts without rebuilding. Community and Studio expose `STATIC_URL` and leave
it empty by default.

## Sitemaps and crawl policy

The platform does not generate a root sitemap or sitemap chunks during the
build. Public examples, hub resources, connections and model pages retain their
dynamic section sitemaps. A frontend site can include those endpoints in its
own sitemap index.

`public/robots.txt` is a checked-in, origin-independent crawl policy. It keeps
the existing allow and content-signal directives without embedding a deployment
host or sitemap URL. `SITE_URL` remains runtime configuration for absolute URLs,
including links emitted by the dynamic section sitemaps.

## Reverse proxy trust

Expand Down
3 changes: 3 additions & 0 deletions platform/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# Crawl policy is source, independent of the deployment origin
!/public/robots.txt
6 changes: 3 additions & 3 deletions platform/config/hosts.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ export const hostMappingSchema = z
* }
* }
*
* Build-time routing consumes the flattened target lists. Request-context
* setup selects one mapping when a trusted request or frontend host matches;
* runtime URL helpers only read that resolved context.
* API and static host routing read the flattened targets at startup.
* Request-context setup selects one mapping when a trusted request or frontend
* host matches; runtime URL helpers only read that resolved context.
*/
export const hostsSchema = z
.record(hostMappingSchema)
Expand Down
Loading