fix: feat: merge chat container into edge — run chat server inside edge container with full permissions (reverts sandbox from #706) (#1083)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/edge-subpath Pipeline was successful
ci/woodpecker/pr/smoke-init Pipeline was successful

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude 2026-04-20 15:27:21 +00:00
parent 398a7398a9
commit bcdf33e68a
7 changed files with 48 additions and 142 deletions

View file

@ -326,7 +326,6 @@ _generate_compose_impl() {
_record_service "edge" "base compose template" || return 1
_record_service "staging" "base compose template" || return 1
_record_service "staging-deploy" "base compose template" || return 1
_record_service "chat" "base compose template" || return 1
# Extract primary woodpecker_repo_id from project TOML files
local wp_repo_id
@ -615,6 +614,16 @@ COMPOSEEOF
- EDGE_TUNNEL_FQDN_CHAT=${EDGE_TUNNEL_FQDN_CHAT:-}
# Shared secret for Caddy ↔ chat forward_auth (#709)
- FORWARD_AUTH_SECRET=${FORWARD_AUTH_SECRET:-}
# Chat env vars (merged from chat container into edge, #1083)
- CHAT_HOST=127.0.0.1
- CHAT_PORT=8080
- CHAT_OAUTH_CLIENT_ID=${CHAT_OAUTH_CLIENT_ID:-}
- CHAT_OAUTH_CLIENT_SECRET=${CHAT_OAUTH_CLIENT_SECRET:-}
- DISINTO_CHAT_ALLOWED_USERS=${DISINTO_CHAT_ALLOWED_USERS:-}
# Cost caps / rate limiting (#711)
- CHAT_MAX_REQUESTS_PER_HOUR=${CHAT_MAX_REQUESTS_PER_HOUR:-60}
- CHAT_MAX_REQUESTS_PER_DAY=${CHAT_MAX_REQUESTS_PER_DAY:-500}
- CHAT_MAX_TOKENS_PER_DAY=${CHAT_MAX_TOKENS_PER_DAY:-1000000}
volumes:
- ./docker/Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
@ -622,6 +631,8 @@ COMPOSEEOF
- ./secrets/tunnel_key:/run/secrets/tunnel_key:ro
- ${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared}:${CLAUDE_SHARED_DIR:-/var/lib/disinto/claude-shared}
- ${CLAUDE_CONFIG_FILE:-${HOME}/.claude.json}:/home/agent/.claude.json:ro
# Chat history persistence (merged from chat container, #1083)
- ${CHAT_HISTORY_DIR:-./state/chat-history}:/var/lib/chat/history
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:2019/config/"]
interval: 30s
@ -670,64 +681,12 @@ COMPOSEEOF
- disinto-net
command: ["echo", "staging slot — replace with project image"]
# Chat container — Claude chat UI backend (#705)
# Internal service only; edge proxy routes to chat:8080
# Sandbox hardened per #706 — no docker.sock, read-only rootfs, minimal caps
chat:
build:
context: ./docker/chat
dockerfile: Dockerfile
container_name: disinto-chat
restart: unless-stopped
read_only: true
tmpfs:
- /tmp:size=64m
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
pids_limit: 128
mem_limit: 512m
memswap_limit: 512m
volumes:
# Mount claude binary from host (same as agents)
- ${CLAUDE_BIN_DIR}:/usr/local/bin/claude:ro
# Throwaway named volume for chat config (isolated from host ~/.claude)
- chat-config:/var/chat/config
# Chat history persistence: per-user NDJSON files on bind-mounted host volume
- ${CHAT_HISTORY_DIR:-./state/chat-history}:/var/lib/chat/history
environment:
CHAT_HOST: "0.0.0.0"
CHAT_PORT: "8080"
FORGE_URL: http://forgejo:3000
CHAT_OAUTH_CLIENT_ID: ${CHAT_OAUTH_CLIENT_ID:-}
CHAT_OAUTH_CLIENT_SECRET: ${CHAT_OAUTH_CLIENT_SECRET:-}
EDGE_TUNNEL_FQDN: ${EDGE_TUNNEL_FQDN:-}
EDGE_TUNNEL_FQDN_CHAT: ${EDGE_TUNNEL_FQDN_CHAT:-}
EDGE_ROUTING_MODE: ${EDGE_ROUTING_MODE:-subpath}
DISINTO_CHAT_ALLOWED_USERS: ${DISINTO_CHAT_ALLOWED_USERS:-}
# Shared secret for Caddy forward_auth verify endpoint (#709)
FORWARD_AUTH_SECRET: ${FORWARD_AUTH_SECRET:-}
# Cost caps / rate limiting (#711)
CHAT_MAX_REQUESTS_PER_HOUR: ${CHAT_MAX_REQUESTS_PER_HOUR:-60}
CHAT_MAX_REQUESTS_PER_DAY: ${CHAT_MAX_REQUESTS_PER_DAY:-500}
CHAT_MAX_TOKENS_PER_DAY: ${CHAT_MAX_TOKENS_PER_DAY:-1000000}
healthcheck:
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
networks:
- disinto-net
volumes:
forgejo-data:
woodpecker-data:
agent-data:
project-repos:
caddy_data:
chat-config:
networks:
disinto-net:
@ -786,7 +745,7 @@ COMPOSEEOF
# In build mode, replace image: with build: for locally-built images
if [ "$use_build" = true ]; then
sed -i '/^ image: ghcr\.io\/disinto\/agents:/{s|image: ghcr\.io/disinto/agents:.*|build:\n context: .\n dockerfile: docker/agents/Dockerfile\n pull_policy: build|}' "$compose_file"
sed -i '/^ image: ghcr\.io\/disinto\/edge:/{s|image: ghcr\.io/disinto/edge:.*|build: ./docker/edge\n pull_policy: build|}' "$compose_file"
sed -i '/^ image: ghcr\.io\/disinto\/edge:/{s|image: ghcr\.io/disinto/edge:.*|build:\n context: .\n dockerfile: docker/edge/Dockerfile\n pull_policy: build|}' "$compose_file"
fi
echo "Created: ${compose_file}"
@ -864,22 +823,22 @@ _generate_caddyfile_subpath() {
reverse_proxy staging:80
}
# Chat service — reverse proxy to disinto-chat backend (#705)
# Chat service — reverse proxy to in-process chat server (#705, #1083)
# OAuth routes bypass forward_auth — unauthenticated users need these (#709)
handle /chat/login {
reverse_proxy chat:8080
reverse_proxy 127.0.0.1:8080
}
handle /chat/oauth/callback {
reverse_proxy chat:8080
reverse_proxy 127.0.0.1:8080
}
# Defense-in-depth: forward_auth stamps X-Forwarded-User from session (#709)
handle /chat/* {
forward_auth chat:8080 {
forward_auth 127.0.0.1:8080 {
uri /chat/auth/verify
copy_headers X-Forwarded-User
header_up X-Forward-Auth-Secret {$FORWARD_AUTH_SECRET}
}
reverse_proxy chat:8080
reverse_proxy 127.0.0.1:8080
}
}
CADDYFILEEOF
@ -912,18 +871,18 @@ _generate_caddyfile_subdomain() {
# Chat — with forward_auth (#709, on its own host)
{$EDGE_TUNNEL_FQDN_CHAT} {
handle /login {
reverse_proxy chat:8080
reverse_proxy 127.0.0.1:8080
}
handle /oauth/callback {
reverse_proxy chat:8080
reverse_proxy 127.0.0.1:8080
}
handle /* {
forward_auth chat:8080 {
forward_auth 127.0.0.1:8080 {
uri /auth/verify
copy_headers X-Forwarded-User
header_up X-Forward-Auth-Secret {$FORWARD_AUTH_SECRET}
}
reverse_proxy chat:8080
reverse_proxy 127.0.0.1:8080
}
}
CADDYFILEEOF