From 044f1de8303aa67b6b760a96a2fc3aff93b4ba67 Mon Sep 17 00:00:00 2001 From: h Date: Sat, 29 Aug 2026 00:49:27 +0200 Subject: [PATCH] feat(config,caddy): single gateway port and site.caddy per the komodo convention --- .env.example | 6 +---- README.md | 21 ++++++++-------- caddy/.env.example | 4 ++++ caddy/Caddyfile.example | 52 ++++++++++++++-------------------------- caddy/docker-compose.yml | 4 +++- caddy/site.caddy | 11 +++++++++ config.py | 30 ++++++++++------------- docker-compose.yml | 7 ++---- 8 files changed, 63 insertions(+), 72 deletions(-) create mode 100644 caddy/site.caddy diff --git a/.env.example b/.env.example index c7aa747..630a754 100644 --- a/.env.example +++ b/.env.example @@ -78,10 +78,6 @@ KOMODO_SECRET= ### Порты хоста и внешний адрес ############################################### -PORT_MESSAGES=62990 -PORT_MCP=62991 -PORT_ADMIN=62992 -PORT_MARKDOWN=62993 -PORT_API=62994 +PORT_GATEWAY=62990 # Публичный URL за reverse proxy (см. ./caddy). Пусто для localhost/dev PUBLIC_BASE_URL= diff --git a/README.md b/README.md index 742d451..69ecd0b 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ with a whitelisted environment; the vault is read-only for it except ### 4. Mint a token -Open admin at `http://localhost:62992` (or `https:///admin/` if Caddy), sign in with `ADMIN_USER` / `ADMIN_PASS`, go to **Tokens → Create**, scope `*` for first run. +Open admin at `http://localhost:62990/` (or `https:///` if Caddy), sign in with `ADMIN_USER` / `ADMIN_PASS`, go to **Tokens → Create**, scope `*` for first run. ### 5. Smoke test @@ -76,7 +76,7 @@ Open admin at `http://localhost:62992` (or `https:///admin/` if Caddy), docker exec beaver-gateway ls /vault | head docker logs beaver-gateway | grep -i "agent registered" -curl http://localhost:62990/v1/models \ +curl http://localhost:62990/anthropic/v1/models \ -H "Authorization: Bearer " ``` @@ -84,24 +84,25 @@ curl http://localhost:62990/v1/models \ The admin dashboard renders ready-to-copy URLs and snippets for each of these -- any Anthropic client: `http://localhost:62990` or `https:///anthropic`, model = agent name (`beaver-opus-high` etc) -- MCP clients (Claude Desktop, Raycast extension): just find in admin -- Obsidian companion plugin: paste the dashboard's **plugin base** (`http://localhost:62993` raw, or `https:///md` behind Caddy) into the plugin's "Base URL" setting -- **Admin UI:** `http://localhost:62992` or `https:///admin`, login from `ADMIN_USER` / `ADMIN_PASS` +Everything is on one port under its own path; Caddy forwards the whole domain as is. + +- any Anthropic client: `http://localhost:62990/anthropic` or `https:///anthropic`, model = agent name (`beaver-opus-high` etc) +- MCP clients (Claude Desktop, Raycast extension): `/mcp//`, discovery page at `/mcp/` +- Obsidian companion plugin: `/md` as the plugin's "Base URL", `/api` for the panel +- **Admin UI:** `http://localhost:62990/` or `https:///`, login from `ADMIN_USER` / `ADMIN_PASS` ## Exposing to the internet -Reference config in `caddy`. Designed for: gateway on rpi, Caddy on a server with a public IP, wired through tailscale. +`caddy/site.caddy` holds the routes (one `reverse_proxy`, domain from `BEAVER_DOMAIN`); on a shared server it is bind-mounted into that server's Caddy as `projects.d/beaver-agent/`, on its own box `caddy/docker-compose.yml` runs a Caddy around it. ```bash cd caddy cp Caddyfile.example Caddyfile -cp .env.example .env # only for cloudflare -# replace ; point upstreams at wherever the gateway is reachable +cp .env.example .env # BEAVER_DOMAIN, BEAVER_UPSTREAM, Cloudflare token if any docker compose up -d ``` -Set `PUBLIC_BASE_URL=https://` in beaver-agent's `.env`. For per-subdomain layout, rewrite the Caddyfile and pass full `public_base_url=...` per frontend in `config.py`. +Set `PUBLIC_BASE_URL=https://` in beaver-agent's `.env` so advertised endpoints use the outside origin. ## Editing config.py on the mac diff --git a/caddy/.env.example b/caddy/.env.example index bb68773..dacbd60 100644 --- a/caddy/.env.example +++ b/caddy/.env.example @@ -1 +1,5 @@ +BEAVER_DOMAIN=beaver.localhost +# куда ходить за gateway; из контейнера caddy хост - host.docker.internal +BEAVER_UPSTREAM=host.docker.internal:62990 +# только с acme_dns в Caddyfile CLOUDFLARE_API_TOKEN= diff --git a/caddy/Caddyfile.example b/caddy/Caddyfile.example index 12fb245..8636632 100644 --- a/caddy/Caddyfile.example +++ b/caddy/Caddyfile.example @@ -1,40 +1,24 @@ +# Скелет для одиночного сервера: глобальный блок и import роутов проекта. +# Роуты - в site.caddy, домен и апстрим - в .env. { - admin off - # acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN} + admin off + # Локально - самоподписанные сертификаты. На сервере с Cloudflare + # закомментировать local_certs, раскомментировать acme_dns. + local_certs + # acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN} - log { - format console - } + log { + format console + } - servers { - trusted_proxies cloudflare - client_ip_headers Cf-Connecting-Ip - } + # servers { + # trusted_proxies cloudflare + # client_ip_headers Cf-Connecting-Ip + # } } - { - redir /anthropic /anthropic/ 308 - redir /mcp /mcp/ 308 - redir /admin /admin/ 308 - redir /md /md/ 308 - - handle_path /anthropic/* { - reverse_proxy host.docker.internal:62990 - } - handle_path /mcp/* { - reverse_proxy host.docker.internal:62991 - } - handle /admin/* { - reverse_proxy host.docker.internal:62992 - } - handle_path /md/* { - reverse_proxy host.docker.internal:62993 - } - # API разговоров + SSE для панели плагина: handle, не handle_path - - # приложение само живёт под /api/… - handle /api/* { - reverse_proxy host.docker.internal:62994 { - flush_interval -1 - } - } +(compress) { + encode zstd gzip } + +import /etc/caddy/projects.d/*/site.caddy diff --git a/caddy/docker-compose.yml b/caddy/docker-compose.yml index 8474432..707536f 100644 --- a/caddy/docker-compose.yml +++ b/caddy/docker-compose.yml @@ -9,7 +9,9 @@ services: extra_hosts: - "host.docker.internal:host-gateway" volumes: - - ./Caddyfile:/etc/caddy/Caddyfile + - ./Caddyfile:/etc/caddy/Caddyfile:ro + # каталог, не файл: git меняет inode, одиночный bind-mount залипает + - ./:/etc/caddy/projects.d/beaver-agent:ro - caddy_data:/data env_file: - .env diff --git a/caddy/site.caddy b/caddy/site.caddy new file mode 100644 index 0000000..6d72b3e --- /dev/null +++ b/caddy/site.caddy @@ -0,0 +1,11 @@ +# Роуты beaver. Один апстрим, префиксы не срезаются: gateway сам живёт под +# /anthropic, /mcp, /md, /api, /admin, а / ведёт в админку. +# Домен и апстрим - из env сервера (domains.env / .env), tls - забота +# серверного Caddyfile. +{$BEAVER_DOMAIN} { + import compress + reverse_proxy {$BEAVER_UPSTREAM:localhost:62990} { + # SSE и стримы ответов - без буферизации + flush_interval -1 + } +} diff --git a/config.py b/config.py index d0dc465..e338f33 100644 --- a/config.py +++ b/config.py @@ -212,11 +212,6 @@ agents: list[BaseAgent] = [ PUBLIC_BASE_URL = os.environ.get("PUBLIC_BASE_URL", "").rstrip("/") - -def _public(suffix: str) -> str | None: - return f"{PUBLIC_BASE_URL}{suffix}" if PUBLIC_BASE_URL else None - - # §3.8: личка с ботом - домашний фронтенд мастера и веток (General и топики). # Токен и id - секреты gateway, в env процесса модели они не попадают. TELEGRAM = ( @@ -230,31 +225,25 @@ TELEGRAM = ( else None ) +# Один порт: каждый HTTP-фронтенд живёт под своим путём (/anthropic, /mcp, +# /admin, /api, /md), `/` ведёт в админку. Caddy ничего не срезает. frontends: list[Frontend] = [ *([TELEGRAM] if TELEGRAM is not None else []), - AnthropicMessagesFrontend( - host="0.0.0.0", port=62990, public_base_url=_public("/anthropic") - ), - McpServerFrontend(host="0.0.0.0", port=62991, public_base_url=_public("/mcp")), - AdminFrontend(host="0.0.0.0", port=62992, public_base_url=_public("/admin")), + AnthropicMessagesFrontend(), + McpServerFrontend(), + AdminFrontend(), # §3.9: /api показывает всё; дефолт master/branch здесь - запасной на случай # gateway без телеграма, deep - у markdown. ApiFrontend( - host="0.0.0.0", - port=62994, - public_base_url=_public("/api"), master_agent="beaver-dispatcher", branch_agent="beaver-dispatcher", memory_root=BEAVER, ), MarkdownFrontend( - host="0.0.0.0", - port=62993, vault_path=CHATS_DIR, default_agent="beaver-opus-high", log_all_chats=True, chat_path=chat_path, - public_base_url=_public("/md"), ), ] @@ -277,4 +266,11 @@ texts = ConversationTexts( seed=seed_body, ) -gateway = Gateway(agents=agents, mcps=mcps, frontends=frontends, texts=texts) +gateway = Gateway( + agents=agents, + mcps=mcps, + frontends=frontends, + texts=texts, + port=62990, + public_url=PUBLIC_BASE_URL or None, +) diff --git a/docker-compose.yml b/docker-compose.yml index 70d24fe..2945bad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,11 +48,8 @@ services: CLAUDE_RUNNER_USER: beaver-runner CLAUDE_HOME: /home/beaver-runner ports: - - "${PORT_MESSAGES:-62990}:62990" # anthropic - - "${PORT_MCP:-62991}:62991" # mcp - - "${PORT_ADMIN:-62992}:62992" # admin - - "${PORT_MARKDOWN:-62993}:62993" # obsidian companion - - "${PORT_API:-62994}:62994" # conversations api + sse + # один порт: /anthropic, /mcp, /admin, /api, /md, / -> админка + - "${PORT_GATEWAY:-62990}:62990" volumes: - ./config.py:/config/config.py:ro - ./config.json:/config/config.json:ro