A self-hosted LanguageTool (dev: docker/docker-compose.yml, started by `make up`) checks what the user writes in the compose window, through POST /api/spellcheck — the browser never talks to the engine directly. - Marks are CSS highlights over the text, never written into the message: red wavy for spelling, blue for grammar/style. Clicking one shows the message, up to five suggestions (applied as typed, so Ctrl+Z undoes them) and Ignore. The popup, like the link popup, opens above its anchor when there is no room below. - Checked after a pause in typing; the signature and quoted original are skipped; marks on words just edited drop at once; a check overtaken by a newer one, by turning checking off, or by closing the window is discarded. - With "auto", text is checked in chunks of lines, each in its own detected language — LanguageTool settles on one language per request, so a mixed Polish/English message had every English word flagged as a Polish typo. - Settings > Appearance: on/off and language (Auto-detect or any of the engine's languages); the compose window's More menu overrides it for one message. The browser's own spellcheck steps aside while ours runs, and comes back when it is off or the server has no engine (503 from the endpoint). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
155 lines
5.0 KiB
YAML
155 lines
5.0 KiB
YAML
name: webmail
|
|
services:
|
|
postgres:
|
|
image: postgres:16
|
|
environment:
|
|
POSTGRES_USER: webmail
|
|
POSTGRES_PASSWORD: webmail_dev
|
|
POSTGRES_DB: webmail_dev
|
|
ports: ["127.0.0.1:5433:5432"]
|
|
volumes: [pgdata:/var/lib/postgresql/data]
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U webmail -d webmail_dev"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 10
|
|
|
|
redis:
|
|
image: redis:7
|
|
ports: ["127.0.0.1:6380:6379"]
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 10
|
|
|
|
meilisearch:
|
|
image: getmeili/meilisearch:v1.8
|
|
environment:
|
|
MEILI_MASTER_KEY: dev_master_key_change_me
|
|
MEILI_ENV: development
|
|
ports: ["127.0.0.1:7700:7700"]
|
|
volumes: [meilidata:/meili_data]
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:7700/health | grep -q available"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 10
|
|
|
|
# Sync-engine v2 workers (refactor §1): two roles sharing one image. Scale the
|
|
# sync role with `--scale worker-sync=N` (lease protocol prevents double-owning).
|
|
worker-sync:
|
|
build: { context: ../backend }
|
|
command: ["python", "-m", "workers.run"]
|
|
environment: &worker-env
|
|
WORKER_ROLE: sync
|
|
APP_DOMAIN: dev.webmail.local
|
|
DATABASE_URL: postgresql+asyncpg://webmail:webmail_dev@postgres:5432/webmail_dev
|
|
REDIS_URL: redis://redis:6379/0
|
|
MASTER_KEY: CHANGE_ME_base64_32_bytes
|
|
BLOB_ROOT: /var/blobs
|
|
# Must match the rspamd service below, or the controller 401s every learn.
|
|
RSPAMD_CONTROLLER_PASSWORD: ${RSPAMD_CONTROLLER_PASSWORD:-devpass}
|
|
volumes: [blobs:/var/blobs]
|
|
ulimits: { nofile: 65536 }
|
|
depends_on:
|
|
postgres: { condition: service_healthy }
|
|
redis: { condition: service_healthy }
|
|
|
|
worker-jobs:
|
|
build: { context: ../backend }
|
|
command: ["python", "-m", "workers.run"]
|
|
environment:
|
|
<<: *worker-env
|
|
WORKER_ROLE: jobs
|
|
volumes: [blobs:/var/blobs]
|
|
ulimits: { nofile: 65536 }
|
|
depends_on:
|
|
postgres: { condition: service_healthy }
|
|
redis: { condition: service_healthy }
|
|
|
|
# Anti-spam engine, mirroring the production stack (see ../docker-compose.yml).
|
|
# Dev publishes the ports on loopback so the plugin can be driven from the host
|
|
# (RSPAMD_URL=http://127.0.0.1:11333) as well as from a container.
|
|
rspamd:
|
|
image: rspamd/rspamd:3.9
|
|
# Root only long enough to write the controller password; rspamd then drops
|
|
# to _rspamd itself. /etc/rspamd is root-owned in the image.
|
|
user: root
|
|
entrypoint:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
mkdir -p /etc/rspamd/override.d
|
|
printf 'password = "%s";\nenable_password = "%s";\n' \
|
|
"$$RSPAMD_CONTROLLER_PASSWORD" "$$RSPAMD_CONTROLLER_PASSWORD" \
|
|
> /etc/rspamd/override.d/worker-controller.inc
|
|
exec /usr/bin/rspamd -f -u _rspamd -g _rspamd
|
|
environment:
|
|
RSPAMD_CONTROLLER_PASSWORD: ${RSPAMD_CONTROLLER_PASSWORD:-devpass}
|
|
volumes:
|
|
- ./rspamd/local.d:/etc/rspamd/local.d:ro
|
|
- rspamddata:/var/lib/rspamd
|
|
ports:
|
|
- "127.0.0.1:11333:11333" # scan worker (/checkv2)
|
|
- "127.0.0.1:11334:11334" # controller (/learnspam, /stat)
|
|
healthcheck:
|
|
# rspamc, not wget/curl: the rspamd image ships neither, so the obvious
|
|
# HTTP probe fails 127 forever and the container sits "unhealthy" while
|
|
# the engine is answering perfectly.
|
|
test: ["CMD-SHELL", "rspamc stat > /dev/null 2>&1"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 12
|
|
start_period: 15s
|
|
depends_on:
|
|
redis-rspamd: { condition: service_healthy }
|
|
|
|
# Bayes/fuzzy storage, separate from the app redis on purpose (SPEC §C.2).
|
|
redis-rspamd:
|
|
image: redis:7-alpine
|
|
command: [redis-server, --save, "300", "1", --maxmemory, 128mb, --maxmemory-policy, allkeys-lru]
|
|
volumes:
|
|
- rspamdredis:/data
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 10
|
|
|
|
# Test mail servers (integration tests + manual dev against a throwaway mailbox)
|
|
# Spelling and grammar checking for the compose window (POST /api/spellcheck
|
|
# forwards to it; the browser never talks to it directly). No n-gram data:
|
|
# it would add several GB per language for a modest gain in grammar checks.
|
|
languagetool:
|
|
image: erikvl87/languagetool:6.8
|
|
environment:
|
|
Java_Xms: 256m
|
|
Java_Xmx: 1g
|
|
ports:
|
|
- "127.0.0.1:8010:8010"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8010/v2/languages || exit 1"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 12
|
|
restart: unless-stopped
|
|
|
|
greenmail:
|
|
image: greenmail/standalone:2.0.1
|
|
environment:
|
|
GREENMAIL_OPTS: >-
|
|
-Dgreenmail.setup.test.all -Dgreenmail.hostname=0.0.0.0
|
|
-Dgreenmail.users=dev:devpass@dev.local -Dgreenmail.verbose
|
|
ports:
|
|
- "127.0.0.1:3025:3025" # SMTP
|
|
- "127.0.0.1:3143:3143" # IMAP
|
|
- "127.0.0.1:3993:3993" # IMAPS
|
|
|
|
volumes:
|
|
pgdata:
|
|
meilidata:
|
|
blobs:
|
|
rspamddata:
|
|
rspamdredis:
|