mirror of
https://github.com/suitenumerique/messages.git
synced 2026-10-01 06:04:57 +02:00
Ship the existing SPA as native iOS/Android apps without forking the codebase: Capacitor wraps the web build, and every mobile-specific behavior is gated behind isNativePlatform() so the web app is untouched. The native shells route fetch/cookies through the native HTTP layer (CapacitorHttp) — the WebView cookie jar is unreliable for cross-origin sessions — which is why login runs in the system browser (cross-app SSO via the shared IdP cookie) and finishes through the backend session handoff, with the deep-link scheme pinned by sso-invariants tests. Downloads/share go through the Filesystem/Share plugins since WebView navigation would lose the session.
515 lines
14 KiB
YAML
515 lines
14 KiB
YAML
name: st-messages
|
|
|
|
services:
|
|
postgresql:
|
|
image: postgres:16.6
|
|
ports:
|
|
- "8912:5432"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
|
interval: 1s
|
|
timeout: 2s
|
|
retries: 300
|
|
env_file:
|
|
- env.d/development/postgresql.defaults
|
|
- env.d/development/postgresql.local
|
|
|
|
redis:
|
|
image: redis:5
|
|
ports:
|
|
- "8913:6379"
|
|
|
|
opensearch:
|
|
image: opensearchproject/opensearch:2.19.2
|
|
environment:
|
|
- discovery.type=single-node
|
|
- bootstrap.memory_lock=true
|
|
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
|
|
- "DISABLE_INSTALL_DEMO_CONFIG=true"
|
|
- "DISABLE_SECURITY_PLUGIN=true"
|
|
# - http.cors.enabled=true
|
|
# - "http.cors.allow-origin=/.*/"
|
|
ports:
|
|
- "8914:9200" # REST API
|
|
- "8915:9600" # Performance Analyzer
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:9200"]
|
|
interval: 1s
|
|
timeout: 5s
|
|
retries: 60
|
|
ulimits:
|
|
memlock:
|
|
soft: -1 # Set memlock to unlimited (no soft or hard limit)
|
|
hard: -1
|
|
nofile:
|
|
soft: 65536 # Maximum number of open files for the opensearch user - set to at least 65536
|
|
hard: 65536
|
|
|
|
mailcatcher:
|
|
image: maildev/maildev:2.2.1
|
|
ports:
|
|
- "8904:1080"
|
|
- "8917:1025"
|
|
|
|
objectstorage:
|
|
# user: ${DOCKER_USER:-1000}
|
|
image: rustfs/rustfs:1.0.0-alpha.83
|
|
environment:
|
|
- RUSTFS_ACCESS_KEY=st-messages
|
|
- RUSTFS_SECRET_KEY=password
|
|
- RUSTFS_CONSOLE_ENABLE=true
|
|
- RUSTFS_ADDRESS=0.0.0.0:9000
|
|
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
|
|
- RUSTFS_CORS_ALLOWED_ORIGINS=*
|
|
- RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=*
|
|
ports:
|
|
- "8906:9000"
|
|
- "8907:9001"
|
|
healthcheck:
|
|
test:
|
|
[
|
|
"CMD",
|
|
"sh",
|
|
"-c",
|
|
"curl -f http://127.0.0.1:9000/health && curl -f http://127.0.0.1:9001/rustfs/console/health",
|
|
]
|
|
interval: 1s
|
|
timeout: 5s
|
|
retries: 60
|
|
start_period: 0s
|
|
volumes:
|
|
- objectstorage-data:/data
|
|
|
|
backend-base:
|
|
build:
|
|
context: src/backend
|
|
target: runtime-dev
|
|
args:
|
|
DOCKER_USER: ${DOCKER_USER:-1000}
|
|
user: ${DOCKER_USER:-1000}
|
|
volumes:
|
|
- ./src/backend:/app
|
|
- ./data/static:/data/static
|
|
# Dev-only override: live-mount the jmap-email working tree over the
|
|
# package installed from PyPI so local source edits propagate without a
|
|
# rebuild. The wheel installed at
|
|
# ``/venv/lib/${PYTHON_VERSION}/site-packages/jmap_email`` is overlaid
|
|
# with the working-tree source. Override ``PYTHON_VERSION`` in the
|
|
# environment when the backend's Python floor moves. Comment this mount
|
|
# out to run exactly what CI/prod install from PyPI.
|
|
- ./src/jmap-email/jmap_email:/venv/lib/${PYTHON_VERSION:-python3.14}/site-packages/jmap_email
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import urllib.request as u; u.urlopen('http://localhost:8000/__heartbeat__/', timeout=1)"]
|
|
interval: 3s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
backend-dev:
|
|
extends: backend-base
|
|
environment:
|
|
- PYLINTHOME=/app/.pylint.d
|
|
- DJANGO_CONFIGURATION=Development
|
|
env_file:
|
|
- env.d/development/backend.defaults
|
|
- env.d/development/backend.local
|
|
ports:
|
|
- "8901:8000"
|
|
depends_on:
|
|
postgresql:
|
|
condition: service_healthy
|
|
# restart: true
|
|
objectstorage:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_started
|
|
opensearch:
|
|
condition: service_healthy
|
|
keycloak:
|
|
condition: service_started
|
|
mailcatcher:
|
|
condition: service_started
|
|
|
|
backend-db:
|
|
extends: backend-base
|
|
profiles:
|
|
- tools
|
|
environment:
|
|
- DJANGO_CONFIGURATION=DevelopmentMinimal
|
|
env_file:
|
|
- env.d/development/backend.defaults
|
|
- env.d/development/backend.local
|
|
ports:
|
|
- "8901:8000"
|
|
depends_on:
|
|
postgresql:
|
|
condition: service_healthy
|
|
# restart: true
|
|
|
|
backend-uv:
|
|
profiles:
|
|
- tools
|
|
volumes:
|
|
- ./src/backend:/app
|
|
build:
|
|
context: src/backend
|
|
target: uv
|
|
pull_policy: build
|
|
|
|
worker-dev:
|
|
build:
|
|
context: src/backend
|
|
target: runtime-dev
|
|
args:
|
|
DOCKER_USER: ${DOCKER_USER:-1000}
|
|
user: ${DOCKER_USER:-1000}
|
|
command: ["python", "worker.py", "--loglevel=DEBUG"]
|
|
environment:
|
|
- DJANGO_CONFIGURATION=Development
|
|
env_file:
|
|
- env.d/development/backend.defaults
|
|
- env.d/development/backend.local
|
|
volumes:
|
|
- ./src/backend:/app
|
|
- ./data/static:/data/static
|
|
mem_limit: 2G
|
|
depends_on:
|
|
- backend-dev
|
|
|
|
worker-ui:
|
|
build:
|
|
context: src/backend
|
|
target: runtime-dev
|
|
args:
|
|
DOCKER_USER: ${DOCKER_USER:-1000}
|
|
user: ${DOCKER_USER:-1000}
|
|
depends_on:
|
|
- redis
|
|
environment:
|
|
- FLOWER_UNAUTHENTICATED_API=true
|
|
- DJANGO_CONFIGURATION=Development
|
|
env_file:
|
|
- env.d/development/backend.defaults
|
|
- env.d/development/backend.local
|
|
volumes:
|
|
- ./src/backend:/app
|
|
ports:
|
|
- "8903:8803"
|
|
command: celery -A messages.celery_app flower --port=8803
|
|
|
|
frontend-base:
|
|
user: "${DOCKER_USER:-1000}"
|
|
build:
|
|
context: ./src/frontend
|
|
dockerfile: Dockerfile
|
|
target: frontend-deps
|
|
args:
|
|
DOCKER_USER: ${DOCKER_USER:-1000}
|
|
|
|
frontend-dev:
|
|
extends: frontend-base
|
|
env_file:
|
|
- env.d/development/frontend.defaults
|
|
- env.d/development/frontend.local
|
|
command: ["npm", "run", "dev"]
|
|
volumes:
|
|
- ./src/frontend/:/home/frontend/
|
|
ports:
|
|
- "8900:3000"
|
|
|
|
frontend-tools:
|
|
extends: frontend-base
|
|
profiles:
|
|
- frontend-tools
|
|
volumes:
|
|
- ./src/backend/core/api/openapi.json:/home/backend/core/api/openapi.json
|
|
- ./src/frontend/:/home/frontend/
|
|
|
|
frontend-tools-amd64:
|
|
extends: frontend-tools
|
|
platform: linux/amd64
|
|
|
|
# Mobile (Capacitor) web build: like frontend-tools but with the env_file, so
|
|
# the NEXT_PUBLIC_* vars Vite inlines at build time are present (frontend-tools
|
|
# has none). The native projects (android/, ios/) are written back to the host
|
|
# through the mount; the native compile (gradle/xcode) stays a host step.
|
|
frontend-mobile:
|
|
extends: frontend-base
|
|
profiles:
|
|
- frontend-tools
|
|
# Run in the root group and register the (arbitrary) host uid in /etc/passwd
|
|
# before exec'ing the command, so the Capacitor CLI's os.userInfo() call does
|
|
# not throw ENOENT under musl. See the chmod in src/frontend/Dockerfile.
|
|
# Use DOCKER_UID (uid only) and not DOCKER_USER (uid:gid): appending ":0" to
|
|
# the latter yields a malformed "uid:gid:0" spec that drops the root group,
|
|
# leaving /etc/passwd read-only and breaking the registration above.
|
|
user: "${DOCKER_UID:-1000}:0"
|
|
entrypoint:
|
|
- /bin/sh
|
|
- -c
|
|
- 'grep -q ":x:$$(id -u):" /etc/passwd || echo "builder:x:$$(id -u):0::/home/frontend:/bin/sh" >> /etc/passwd; exec "$$@"'
|
|
- --
|
|
# The Capacitor CLI writes its config under $HOME/.config. HOME is unset for
|
|
# the arbitrary uid, so it falls back to "/" and fails with EACCES on
|
|
# mkdir /.config. Point it at a writable, non-mounted path (keeps the CLI's
|
|
# throwaway config out of the bind-mounted repo).
|
|
environment:
|
|
HOME: /tmp
|
|
env_file:
|
|
- env.d/development/frontend.defaults
|
|
- env.d/development/frontend.local
|
|
volumes:
|
|
- ./src/frontend/:/home/frontend/
|
|
|
|
crowdin:
|
|
image: crowdin/cli:4.11.0
|
|
volumes:
|
|
- ".:/app"
|
|
env_file:
|
|
- env.d/development/crowdin.defaults
|
|
- env.d/development/crowdin.local
|
|
user: "${DOCKER_USER:-1000}"
|
|
working_dir: /app
|
|
|
|
mta-in:
|
|
build:
|
|
context: src/mta-in
|
|
target: runtime-prod
|
|
env_file:
|
|
- env.d/development/mta-in.defaults
|
|
- env.d/development/mta-in.local
|
|
ports:
|
|
- "8910:25"
|
|
depends_on:
|
|
- backend-dev
|
|
|
|
mta-in-test:
|
|
profiles:
|
|
- tools
|
|
build:
|
|
context: src/mta-in
|
|
target: runtime-dev
|
|
env_file:
|
|
- env.d/development/mta-in.defaults
|
|
- env.d/development/mta-in.local
|
|
environment:
|
|
- EXEC_CMD=true
|
|
- MDA_API_BASE_URL=http://localhost:8000/api/mail/
|
|
- MTA_HOST=localhost
|
|
- MTA_PORT=25
|
|
- MTA_IMPL=postfix
|
|
command: pytest -vvs tests/
|
|
volumes:
|
|
- ./src/mta-in:/app
|
|
|
|
mta-in-uv:
|
|
profiles:
|
|
- tools
|
|
volumes:
|
|
- ./src/mta-in:/app
|
|
build:
|
|
context: src/mta-in
|
|
target: uv
|
|
pull_policy: build
|
|
|
|
# ---- Pure-Python (aiosmtpd) inbound MTA --------------------------------
|
|
# Runs side-by-side with the Postfix-based `mta-in` service on a different
|
|
# host port (8920 vs 8910). Both implementations share the same MDA
|
|
# contract, env vars, and test suite. Toggle which one is the public-facing
|
|
# MTA at the edge by switching the upstream pool.
|
|
|
|
mta-in-py:
|
|
build:
|
|
context: src/mta-in
|
|
dockerfile: Dockerfile.pymta
|
|
target: runtime-distroless-prod
|
|
args:
|
|
DOCKER_USER: ${DOCKER_USER:-65532}
|
|
user: ${DOCKER_USER:-65532}
|
|
env_file:
|
|
- env.d/development/mta-in.defaults
|
|
- env.d/development/mta-in.local
|
|
- env.d/development/mta-in-py.defaults
|
|
- env.d/development/mta-in-py.local
|
|
ports:
|
|
- "8920:25"
|
|
- "9120:9100" # Prometheus metrics
|
|
# Defence-in-depth: pymta needs no on-disk writes at runtime. Read-only
|
|
# rootfs + dropped capabilities + no-new-privileges mirror the posture
|
|
# a production k8s pod-spec should run with.
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=16m
|
|
depends_on:
|
|
- backend-dev
|
|
|
|
mta-in-py-test:
|
|
profiles:
|
|
- tools
|
|
build:
|
|
context: src/mta-in
|
|
dockerfile: Dockerfile.pymta
|
|
target: runtime-dev
|
|
args:
|
|
DOCKER_USER: ${DOCKER_USER:-65532}
|
|
user: ${DOCKER_USER:-65532}
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
env_file:
|
|
- env.d/development/mta-in.defaults
|
|
- env.d/development/mta-in.local
|
|
- env.d/development/mta-in-py.defaults
|
|
- env.d/development/mta-in-py.local
|
|
environment:
|
|
- EXEC_CMD=true
|
|
- MDA_API_BASE_URL=http://localhost:8000/api/mail/
|
|
- MTA_HOST=localhost
|
|
- MTA_PORT=25
|
|
- MTA_IMPL=pymta
|
|
- MTA_METRICS_URL=http://localhost:9100/metrics
|
|
command: pytest -vvs tests/
|
|
volumes:
|
|
- ./src/mta-in:/app
|
|
|
|
mta-out:
|
|
build:
|
|
context: src/mta-out
|
|
target: runtime-prod
|
|
env_file:
|
|
- env.d/development/mta-out.defaults
|
|
- env.d/development/mta-out.local
|
|
ports:
|
|
- "8911:587"
|
|
depends_on:
|
|
mailcatcher:
|
|
condition: service_started
|
|
|
|
mta-out-test:
|
|
profiles:
|
|
- tools
|
|
build:
|
|
context: src/mta-out
|
|
target: runtime-dev
|
|
env_file:
|
|
- env.d/development/mta-out.defaults
|
|
- env.d/development/mta-out.local
|
|
environment:
|
|
- EXEC_CMD=true
|
|
- MTA_OUT_SMTP_HOST=localhost:587
|
|
- MTA_OUT_SMTP_USERNAME=user
|
|
- MTA_OUT_SMTP_PASSWORD=pass
|
|
- SMTP_RELAY_HOST=localhost:2525
|
|
command: pytest -vvs tests/
|
|
volumes:
|
|
- ./src/mta-out:/app
|
|
|
|
socks-proxy:
|
|
build:
|
|
context: src/socks-proxy
|
|
target: runtime
|
|
env_file:
|
|
- env.d/development/socks-proxy.defaults
|
|
- env.d/development/socks-proxy.local
|
|
ports:
|
|
- "8916:1080"
|
|
|
|
socks-proxy-test:
|
|
profiles:
|
|
- tools
|
|
build:
|
|
context: src/socks-proxy/tests
|
|
environment:
|
|
- SOCKS_PROXY1=user1:pwd1@socks-proxy:1080
|
|
- SOCKS_PROXY2=user2:pwd2@socks-proxy:1080
|
|
depends_on:
|
|
socks-proxy:
|
|
condition: service_started
|
|
|
|
mta-out-uv:
|
|
profiles:
|
|
- tools
|
|
volumes:
|
|
- ./src/mta-out:/app
|
|
build:
|
|
context: src/mta-out
|
|
target: uv
|
|
pull_policy: build
|
|
|
|
keycloak:
|
|
image: quay.io/keycloak/keycloak:26.6.4
|
|
volumes:
|
|
- ./src/keycloak/realm.json:/opt/keycloak/data/import/realm.json:ro
|
|
- ./src/keycloak/themes/dsfr-2.3.4.jar:/opt/keycloak/providers/keycloak-theme.jar:ro
|
|
- ./src/keycloak/bulk-role-membership/bulk-role-membership.jar:/opt/keycloak/providers/bulk-role-membership.jar:ro
|
|
environment:
|
|
- HOST=http://localhost:8902
|
|
- ADMIN_HOST=http://localhost:8902
|
|
command:
|
|
- start-dev
|
|
- --features=preview
|
|
- --import-realm
|
|
- --proxy-headers=xforwarded
|
|
- --http-enabled=true
|
|
- --hostname=$${HOST}
|
|
- --hostname-admin=$${ADMIN_HOST}
|
|
- --http-port=8802
|
|
env_file:
|
|
- env.d/development/keycloak.defaults
|
|
- env.d/development/keycloak.local
|
|
ports:
|
|
- "8902:8802"
|
|
depends_on:
|
|
- postgresql
|
|
|
|
mpa:
|
|
build:
|
|
context: src/mpa/rspamd
|
|
environment:
|
|
- RSPAMD_password=password
|
|
- PORT=8010
|
|
ports:
|
|
- "8918:8010"
|
|
depends_on:
|
|
redis:
|
|
condition: service_started
|
|
|
|
mpa-test:
|
|
profiles:
|
|
- tools
|
|
build:
|
|
context: src/mpa/tests
|
|
environment:
|
|
- RSPAMD_URL=http://mpa:8010/_api
|
|
- RSPAMD_AUTH=Bearer password
|
|
command: pytest -vvs tests/
|
|
volumes:
|
|
- ./src/mpa/tests:/app/tests
|
|
depends_on:
|
|
mpa:
|
|
condition: service_started
|
|
redis:
|
|
condition: service_started
|
|
|
|
# Self-contained jmap-email package tests. Zero infrastructure
|
|
# dependencies (no DB, no opensearch, no redis) — the library has
|
|
# no runtime deps. Source is mounted for instant feedback during
|
|
# development.
|
|
jmap-email-test:
|
|
profiles:
|
|
- tools
|
|
build:
|
|
context: src/jmap-email
|
|
command: pytest -q tests/
|
|
volumes:
|
|
- ./src/jmap-email/jmap_email:/app/jmap_email
|
|
- ./src/jmap-email/tests:/app/tests
|
|
- ./src/jmap-email/pyproject.toml:/app/pyproject.toml
|
|
|
|
volumes:
|
|
objectstorage-data:
|