35168b9b83
- chat.SanitizeHTML/SanitizePlain decoded-then-escape text runs, so HTML entities the browser emits (e.g. for the space after a mention chip, or & for a typed "&") no longer render as literal " "/"&". Re-escaping keeps output XSS-safe. Adds regression tests. - Rename ./volumes -> ./.volumes so `go build/vet/test ./...` skip the mongo data dir (700-perm files); Makefile lint now gofmt's tracked files only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
97 lines
3.3 KiB
YAML
97 lines
3.3 KiB
YAML
services:
|
|
mongo:
|
|
image: mongo:7
|
|
restart: unless-stopped
|
|
# modest cache so the whole stack fits comfortably on small hosts
|
|
command: ["mongod", "--wiredTigerCacheSizeGB", "0.5"]
|
|
# WiredTiger needs far more FDs than the 1024 some hosts default to;
|
|
# running out crashes mongod with a WT_PANIC during file creation
|
|
ulimits:
|
|
nofile:
|
|
soft: 64000
|
|
hard: 64000
|
|
# Mongo is reachable only on the compose network; never published to the
|
|
# host (§12).
|
|
volumes:
|
|
# data lives in-repo (gitignored, dot-prefixed so go tooling skips it)
|
|
- ./.volumes/mongo:/data/db
|
|
healthcheck:
|
|
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 15s
|
|
|
|
app:
|
|
build: .
|
|
restart: unless-stopped
|
|
env_file: .env
|
|
environment:
|
|
MONGO_URI: mongodb://mongo:27017
|
|
# in-network address handed to the external services for callbacks
|
|
# and signed attachment URLs (§5.2 example: http://app:8787/…)
|
|
APP_INTERNAL_URL: http://app:8787
|
|
# NOTE: published on all interfaces to match this host's pattern (gitea,
|
|
# outline, wekan are exposed the same way; docker-published ports bypass
|
|
# UFW). The spec-default loopback-only binding is the commented line —
|
|
# restore it once the app sits behind the reverse proxy exclusively.
|
|
ports:
|
|
- "${APP_PORT:-8787}:8787"
|
|
# - "127.0.0.1:${APP_PORT:-8787}:8787"
|
|
depends_on:
|
|
mongo:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8787/healthz"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
# --- placeholder external services (docker compose --profile mocks up) ---
|
|
# Two fully independent services: separate builds, ports, tokens (§5).
|
|
atomizer-mock:
|
|
build: ./services/atomizer
|
|
profiles: ["mocks"]
|
|
restart: unless-stopped
|
|
environment:
|
|
PORT: "8090"
|
|
ATOMIZER_TOKEN: ${ATOMIZER_TOKEN}
|
|
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
|
ANTHROPIC_OPENAI_BASE_URL: ${ANTHROPIC_OPENAI_BASE_URL:-https://api.anthropic.com/v1}
|
|
ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-claude-sonnet-4-6}
|
|
LLM_API_STYLE: ${LLM_API_STYLE:-openai}
|
|
ports:
|
|
- "127.0.0.1:8090:8090"
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8090/healthz"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 5s
|
|
|
|
work-performer:
|
|
build: ./services/work-performer
|
|
profiles: ["mocks"]
|
|
restart: unless-stopped
|
|
environment:
|
|
PORT: "8091"
|
|
WORK_PERFORMER_TOKEN: ${WORK_PERFORMER_TOKEN}
|
|
PUBLIC_BASE_URL: http://work-performer:8091
|
|
# host Claude Code config/secrets (§9.2); mounted into the node user's
|
|
# home because claude refuses --dangerously-skip-permissions as root
|
|
volumes:
|
|
- ${HOME}/.claude:/home/node/.claude
|
|
- ${HOME}/.claude.json:/home/node/.claude.json
|
|
- ./.volumes/work:/work
|
|
ports:
|
|
- "127.0.0.1:8091:8091"
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:8091/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 5s
|