mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
1728a4c0af
Drop the Tavily and DuckDuckGo (ddgs) web_search backends for a single self-hosted SearxNG service bundled into the docker-compose stacks. Core: - New SearXNGClient + _format_searxng; rewrite resolve_web_search_client to (backend, searxng_url, searxng_engines, ...). MCP backend + oauth_user guard unchanged. _resolve_search_client follows storage -> toml -> env -> default precedence (explicit "" disables, via ConfigStore.stored_keys()). - Drop the Tavily-era topic=finance (no SearxNG category); topic is now general/news. Settings/config: - Remove tools.tavily_api_key, get_tavily_key, $TAVILY_API_KEY, [api].tavily_key. - Add tools.searxng_url (default http://searxng:8080) + tools.searxng_engines, with get_searxng_url/get_searxng_engines. Compose + bundled config: - Internal-only searxng service (no published API port, :ro config, /healthz healthcheck, persistent searxng-cache volume) in both stacks; bundle turnstone/deploy/searxng/settings.yml (JSON output on, limiter off). - Caddy serves the SearxNG web UI on :8444 (dev: localhost-only; prod: opt-in). - bootstrap extractor + wheel packaging updated. Deps: drop the ddg extra + ddgs mypy override (regenerates uv.lock, removing the lxml/h2/brotli transitives). Docs: tools/docker/architecture/openshell + diagrams + config example + CHANGELOG; docs/docker.md carries the AGPL-3.0 §13 operator note. BREAKING: tools.web_search_backend no longer accepts "tavily"/"ddg"; tools.tavily_api_key and the ddg extra are removed. Run the bundled SearxNG (ships in the compose stacks) or set TURNSTONE_SEARXNG_URL to an external instance. Closes #545
252 lines
9.5 KiB
YAML
252 lines
9.5 KiB
YAML
# =============================================================================
|
|
# Turnstone — production stack (docker compose)
|
|
#
|
|
# Same shape as the dev stack at the repo root (Caddy-fronted console, shared
|
|
# Postgres, channel gateway) but it pulls released images from ghcr.io instead
|
|
# of building, runs a single server node, and requires real secrets. Bundled
|
|
# with the turnstone wheel and written by turnstone-bootstrap.
|
|
#
|
|
# docker compose -f turnstone/deploy/compose.yaml up
|
|
#
|
|
# Dashboard: https://localhost:8443 (Caddy's local CA — trust it once; for a
|
|
# real domain/cert, edit turnstone/deploy/Caddyfile — see docs/tls.md)
|
|
#
|
|
# Access is via Caddy only — the console's plain-HTTP port is intentionally not
|
|
# published (HTTP/2 from Caddy avoids the browser's 6-connection cap on the
|
|
# dashboard's SSE streams).
|
|
#
|
|
# No baked-in secrets: set TURNSTONE_JWT_SECRET and POSTGRES_PASSWORD in .env
|
|
# (turnstone-bootstrap generates them). Pin images with TURNSTONE_IMAGE_TAG
|
|
# (default: latest).
|
|
#
|
|
# Join a bare-metal host: Postgres is published on 127.0.0.1:5432, so a
|
|
# turnstone-server on this machine can join the cluster. Put its secrets in
|
|
# ~/.config/turnstone/config.toml (chmod 0600) — see "Join a bare-metal host"
|
|
# in docs/docker.md.
|
|
#
|
|
# Enable mTLS between services with the overlay:
|
|
# docker compose -f turnstone/deploy/compose.yaml -f deploy/docker-compose.tls.yml up
|
|
# =============================================================================
|
|
|
|
name: turnstone
|
|
|
|
networks:
|
|
turnstone-net:
|
|
driver: bridge
|
|
|
|
volumes:
|
|
turnstone-data:
|
|
workspace:
|
|
postgres-data:
|
|
caddy-data:
|
|
caddy-config:
|
|
searxng-cache:
|
|
|
|
# Defined once, referenced (*alias) by every service so values can't drift.
|
|
x-shared:
|
|
# Generate with: python -c "import secrets; print(secrets.token_hex(32))"
|
|
jwt-secret: &jwt-secret "${TURNSTONE_JWT_SECRET:?Set TURNSTONE_JWT_SECRET in .env}"
|
|
db-backend: &db-backend "${TURNSTONE_DB_BACKEND:-postgresql}"
|
|
# Every service shares one Postgres. Node discovery REQUIRES a shared DB: the
|
|
# server registers + heartbeats into a `services` table the console polls.
|
|
db-url: &db-url "${TURNSTONE_DB_URL:-postgresql+psycopg://${POSTGRES_USER:-turnstone}:${POSTGRES_PASSWORD}@postgres:5432/turnstone}"
|
|
image: &image "ghcr.io/turnstonelabs/turnstone:${TURNSTONE_IMAGE_TAG:-latest}"
|
|
|
|
services:
|
|
# -------------------------------------------------------------------
|
|
# PostgreSQL — the shared database that ties the deployment together.
|
|
# Published on localhost so a bare-metal turnstone-server on this host
|
|
# can join (set POSTGRES_BIND=0.0.0.0 to let another machine connect).
|
|
# -------------------------------------------------------------------
|
|
postgres:
|
|
image: pgautoupgrade/pgautoupgrade:18-alpine
|
|
command:
|
|
- postgres
|
|
- -c
|
|
- max_connections=${POSTGRES_MAX_CONNECTIONS:-300}
|
|
- -c
|
|
- shared_buffers=128MB
|
|
environment:
|
|
POSTGRES_DB: turnstone
|
|
POSTGRES_USER: ${POSTGRES_USER:-turnstone}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
|
|
PGDATA: /var/lib/postgresql/data
|
|
ports:
|
|
- "${POSTGRES_BIND:-127.0.0.1}:${POSTGRES_PORT:-5432}:5432"
|
|
volumes:
|
|
- postgres-data:/var/lib/postgresql/data
|
|
networks:
|
|
- turnstone-net
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-turnstone}"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 5
|
|
start_period: 30s
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 1G
|
|
restart: unless-stopped
|
|
|
|
# -------------------------------------------------------------------
|
|
# turnstone-console — cluster dashboard. Reach it ONLY through Caddy at
|
|
# https://localhost:8443. The console port (8090) is not published; Caddy
|
|
# serves the browser over HTTP/2 and proxies to console:8090 internally.
|
|
# extra_hosts lets it reach a bare-metal server on host.docker.internal.
|
|
# -------------------------------------------------------------------
|
|
console:
|
|
image: *image
|
|
command:
|
|
- turnstone-console
|
|
- --host=0.0.0.0
|
|
- --port=8090
|
|
environment:
|
|
TURNSTONE_JWT_SECRET: *jwt-secret
|
|
TURNSTONE_DB_BACKEND: *db-backend
|
|
TURNSTONE_DB_URL: *db-url
|
|
TURNSTONE_CONSOLE_URL: http://console:8090
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
networks:
|
|
- turnstone-net
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD", "python", "/usr/local/bin/healthcheck.py", "http://127.0.0.1:8090/health"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
|
|
# -------------------------------------------------------------------
|
|
# caddy — browser TLS for the dashboard. Terminates HTTPS → console:8090.
|
|
# Uses the bundled Caddyfile (Caddy's local CA). For a real domain/cert,
|
|
# edit turnstone/deploy/Caddyfile (see docs/tls.md).
|
|
# -------------------------------------------------------------------
|
|
caddy:
|
|
image: caddy:2.11
|
|
depends_on:
|
|
- console
|
|
ports:
|
|
- "${CONSOLE_HTTPS_PORT:-8443}:443"
|
|
# SearxNG web UI — NOT exposed by default. SearxNG has no auth, so
|
|
# publishing it is an open search proxy and triggers the AGPL-3.0 §13
|
|
# source-offer obligation (see docs/docker.md). Uncomment to opt in:
|
|
# - "127.0.0.1:${SEARXNG_HTTPS_PORT:-8444}:8444"
|
|
volumes:
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- caddy-data:/data # persist Caddy's local CA across restarts
|
|
- caddy-config:/config
|
|
networks:
|
|
- turnstone-net
|
|
restart: unless-stopped
|
|
|
|
# -------------------------------------------------------------------
|
|
# searxng — self-hosted metasearch backing the web_search tool.
|
|
# Internal-network only (no published port): the server reaches it at
|
|
# http://searxng:8080. Config (JSON output on, limiter off) is the bundled
|
|
# ./searxng/settings.yml, mounted read-only. Commercial models use native
|
|
# provider search and never hit this; it serves local/vLLM models. Override
|
|
# the tag with SEARXNG_IMAGE_TAG in .env.
|
|
# -------------------------------------------------------------------
|
|
searxng:
|
|
image: searxng/searxng:${SEARXNG_IMAGE_TAG:-latest}
|
|
volumes:
|
|
- ./searxng:/etc/searxng:ro
|
|
- searxng-cache:/var/cache/searxng # favicon + internal SQLite cache (survives restarts)
|
|
networks:
|
|
- turnstone-net
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/healthz"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 20s
|
|
restart: unless-stopped
|
|
|
|
# -------------------------------------------------------------------
|
|
# turnstone-server — Web UI + chat workstreams + LLM interaction.
|
|
# Registers itself in Postgres on boot so the console can see it. Boots
|
|
# even with no model configured yet — add model backends in the console UI.
|
|
# -------------------------------------------------------------------
|
|
server:
|
|
image: *image
|
|
command:
|
|
- sh
|
|
- -c
|
|
- >-
|
|
turnstone-server
|
|
--host 0.0.0.0
|
|
--port 8080
|
|
--base-url "$${LLM_BASE_URL}"
|
|
--api-key "$${OPENAI_API_KEY}"
|
|
$${MODEL:+--model $$MODEL}
|
|
$${SKIP_PERMISSIONS:+--skip-permissions}
|
|
$${MCP_CONFIG:+--mcp-config $$MCP_CONFIG}
|
|
volumes:
|
|
- turnstone-data:/data
|
|
- ${WORKSPACE_MOUNT:-workspace}:/workspace
|
|
environment:
|
|
TURNSTONE_JWT_SECRET: *jwt-secret
|
|
TURNSTONE_DB_BACKEND: *db-backend
|
|
TURNSTONE_DB_URL: *db-url
|
|
# Bootstrap LLM defaults — real backends are configured in the console UI.
|
|
LLM_BASE_URL: ${LLM_BASE_URL:-http://host.docker.internal:8000/v1}
|
|
OPENAI_API_KEY: ${OPENAI_API_KEY:-dummy}
|
|
# web_search backend. Defaults to the bundled searxng service; point at an
|
|
# external SearxNG by setting TURNSTONE_SEARXNG_URL in .env (empty disables).
|
|
TURNSTONE_SEARXNG_URL: ${TURNSTONE_SEARXNG_URL:-http://searxng:8080}
|
|
MODEL: ${MODEL:-}
|
|
MCP_CONFIG: ${MCP_CONFIG:-}
|
|
SKIP_PERMISSIONS: ${SKIP_PERMISSIONS:-}
|
|
TURNSTONE_NODE_ID: ${TURNSTONE_NODE_ID:-node-1}
|
|
TURNSTONE_ADVERTISE_URL: ${TURNSTONE_ADVERTISE_URL:-http://server:8080}
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
networks:
|
|
- turnstone-net
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
searxng:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD", "python", "/usr/local/bin/healthcheck.py", "http://127.0.0.1:8080/health"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 60s
|
|
restart: unless-stopped
|
|
|
|
# -------------------------------------------------------------------
|
|
# turnstone-channel — channel gateway (Discord and/or Slack).
|
|
# Runs HTTP-only with no adapters until you set a token. See docs/channels.md.
|
|
# -------------------------------------------------------------------
|
|
channel:
|
|
image: *image
|
|
command:
|
|
- sh
|
|
- -c
|
|
- >-
|
|
turnstone-channel
|
|
--http-host=0.0.0.0
|
|
$${TURNSTONE_DISCORD_GUILD:+--discord-guild $$TURNSTONE_DISCORD_GUILD}
|
|
environment:
|
|
TURNSTONE_JWT_SECRET: *jwt-secret
|
|
TURNSTONE_DB_BACKEND: *db-backend
|
|
TURNSTONE_DB_URL: *db-url
|
|
TURNSTONE_DISCORD_TOKEN: ${TURNSTONE_DISCORD_TOKEN:-}
|
|
TURNSTONE_DISCORD_GUILD: ${TURNSTONE_DISCORD_GUILD:-0}
|
|
TURNSTONE_SLACK_TOKEN: ${TURNSTONE_SLACK_TOKEN:-}
|
|
TURNSTONE_SLACK_APP_TOKEN: ${TURNSTONE_SLACK_APP_TOKEN:-}
|
|
TURNSTONE_CHANNEL_ADVERTISE_URL: http://channel:8091
|
|
networks:
|
|
- turnstone-net
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
restart: unless-stopped
|