continuous-integration/drone/pr Build is passing
- /api/oauth/start → JAMBOTRON_URL/oauth/authorize (client mail, PKCE S256); /api/oauth/callback: код меняется на сервере у Jambotron /api/oauth/token (Bearer SERVICE_TOKEN) → рабочий ящик сотрудника → сессия, ADMIN Jambotron — ADMIN. - Вход по паролю в веб — только встроенный администратор (когда вход через Jambotron включён). - Пароли приложений (V9 app_passwords): User panel → APP PASSWORDS, только SMTP/IMAP. - Системные ящики (V9 users.is_system): без входа в веб, открывают встроенные администраторы, не в USERS; Admin panel → MAILBOXES → кнопка SYSTEM. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
5.8 KiB
Properties
109 lines
5.8 KiB
Properties
spring.application.name=mail-server
|
|
server.port=8080
|
|
# behind a proxy (Jambotron Caddy): take scheme/host from X-Forwarded-*, otherwise the browser's own
|
|
# domain looks like another origin to CORS and every API call with an Origin header gets 403
|
|
server.forward-headers-strategy=framework
|
|
|
|
# Secrets: environment variables, or files in a config tree (file name = variable name, e.g. /run/mail-secrets/DB_PASSWORD).
|
|
# No defaults — the server does not start without them (dev profile: application-dev.properties).
|
|
spring.config.import=optional:configtree:${MAILSERVER_SECRETS_DIR:/run/mail-secrets}/
|
|
|
|
# PostgreSQL: R2DBC for the app, JDBC for Flyway migrations
|
|
spring.r2dbc.url=r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:mailserver}
|
|
spring.r2dbc.username=${DB_USER:mail}
|
|
spring.r2dbc.password=${DB_PASSWORD}
|
|
spring.flyway.enabled=true
|
|
spring.flyway.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:mailserver}
|
|
spring.flyway.user=${DB_USER:mail}
|
|
spring.flyway.password=${DB_PASSWORD}
|
|
spring.flyway.locations=classpath:db/migration
|
|
spring.flyway.baseline-on-migrate=true
|
|
|
|
jwt.secret=${JWT_SECRET}
|
|
|
|
# Mailboxes of these domains live here; catch-all: mail to any address without its own mailbox here goes into this one mailbox (mail trap), nothing goes outside
|
|
mailserver.domains=${MAIL_DOMAINS:localhost}
|
|
mailserver.catch-all=${MAIL_CATCH_ALL:}
|
|
mailserver.registration.enabled=${MAIL_REGISTRATION:false}
|
|
mailserver.admin.email=${MAIL_ADMIN_EMAIL:}
|
|
mailserver.admin.password=${MAIL_ADMIN_PASSWORD:}
|
|
# Who manages mailboxes (Settings → Mailboxes), comma separated; empty — the first mailbox (admin.email or admin@<first domain>)
|
|
mailserver.admins=${MAIL_ADMINS:}
|
|
# Generated passwords of created mailboxes (first admin, catch-all) are appended here (mode 600); empty — printed to the log once
|
|
mailserver.passwords-file=${MAIL_PASSWORDS_FILE:}
|
|
# Service API for Jambotron (/api/service/**, staff mailboxes and lists); empty — the API is off.
|
|
# In the compose files: generated by secrets-init into the secrets volume (file SERVICE_TOKEN).
|
|
mailserver.service-token=${SERVICE_TOKEN:}
|
|
# Sign in with Jambotron (OAuth 2.0 code + PKCE): https://jambotron.com; empty — password login for everyone
|
|
mailserver.jambotron.url=${JAMBOTRON_URL:}
|
|
# address of this web client for the OAuth callback; empty — https://MAIL_HOSTNAME
|
|
mailserver.public-url=${MAIL_PUBLIC_URL:}
|
|
mailserver.cors.origins=${MAIL_CORS_ORIGINS:}
|
|
|
|
# Attachments
|
|
mailserver.attachments.dir=${MAIL_ATTACHMENTS_DIR:./attachments}
|
|
spring.webflux.multipart.max-file-size=10MB
|
|
spring.webflux.multipart.max-request-size=10MB
|
|
|
|
# SMTP server (incoming mail and submission). AUTH without TLS only in a closed network (docker network, VM)
|
|
mailserver.smtp.port=${SMTP_PORT:3025}
|
|
mailserver.smtp.hostname=${MAIL_HOSTNAME:localhost}
|
|
mailserver.smtp.auth-without-tls=${SMTP_AUTH_WITHOUT_TLS:false}
|
|
mailserver.smtp.max-message-bytes=26214400
|
|
# TLS (75.1): STARTTLS on the SMTP port; implicit TLS on SMTPS_PORT (465) if set. Certificate: MAIL_TLS_CERT + MAIL_TLS_KEY
|
|
# (PEM, PKCS#8 key), otherwise a self-signed one created once in MAIL_TLS_DIR. MAIL_TLS=false — no TLS
|
|
mailserver.tls.enabled=${MAIL_TLS:true}
|
|
mailserver.tls.cert-file=${MAIL_TLS_CERT:}
|
|
mailserver.tls.key-file=${MAIL_TLS_KEY:}
|
|
mailserver.tls.dir=${MAIL_TLS_DIR:./tls}
|
|
# Web client + API over HTTPS with the same certificate (public compose: 8443 → 443); 0 — only plain HTTP (server.port)
|
|
mailserver.https.port=${HTTPS_PORT:0}
|
|
mailserver.smtps.port=${SMTPS_PORT:0}
|
|
|
|
# Outbound: off | mx (straight to recipients' MX, port 25) | smarthost (via a provider, e.g. Resend)
|
|
mailserver.outbound.mode=${OUTBOUND_MODE:off}
|
|
mailserver.outbound.host=${OUTBOUND_HOST:}
|
|
mailserver.outbound.port=${OUTBOUND_PORT:587}
|
|
mailserver.outbound.username=${OUTBOUND_USERNAME:}
|
|
mailserver.outbound.password=${OUTBOUND_PASSWORD:}
|
|
mailserver.outbound.tls=${OUTBOUND_TLS:starttls}
|
|
|
|
# IMAP (75.2): STARTTLS on IMAP_PORT, implicit TLS on IMAPS_PORT (993) if set; same certificate as SMTP.
|
|
# LOGIN only inside TLS unless IMAP_AUTH_WITHOUT_TLS=true (a closed network only)
|
|
mailserver.imap.enabled=${IMAP_ENABLED:true}
|
|
mailserver.imap.port=${IMAP_PORT:3143}
|
|
mailserver.imaps.port=${IMAPS_PORT:0}
|
|
mailserver.imap.auth-without-tls=${IMAP_AUTH_WITHOUT_TLS:false}
|
|
|
|
# DKIM — signing of our mail; DNS record printed to the log at start
|
|
mailserver.dkim.enabled=${DKIM_ENABLED:true}
|
|
mailserver.dkim.domain=${DKIM_DOMAIN:localhost}
|
|
mailserver.dkim.selector=${DKIM_SELECTOR:mail}
|
|
mailserver.dkim.keys-dir=${DKIM_KEYS_DIR:./dkim-keys}
|
|
|
|
# SPF / DMARC for incoming mail
|
|
mailserver.spf.enabled=${SPF_ENABLED:true}
|
|
mailserver.spf.local-bypass=true
|
|
mailserver.dmarc.enabled=${DMARC_ENABLED:true}
|
|
mailserver.dmarc.local-bypass=true
|
|
|
|
# error responses carry the reason ("No such mailbox", "Sending outside is disabled") for the web client
|
|
server.error.include-message=always
|
|
|
|
management.endpoints.web.exposure.include=health
|
|
management.endpoint.health.show-details=never
|
|
|
|
logging.level.com.mailserver=INFO
|
|
|
|
# Brute-force protection for SMTP AUTH, IMAP login, web login and password change (one counter for all of them):
|
|
# MAX_PER_IP wrong passwords from one IP within WINDOW → the IP is blocked for BLOCK (each next block twice as long,
|
|
# up to MAX_BLOCK); MAX_PER_ACCOUNT wrong passwords to one mailbox from any IPs → the mailbox is blocked for BLOCK,
|
|
# except for IPs that logged in to it before. TRUSTED_IPS (comma-separated) are never blocked by IP
|
|
mailserver.login-guard.enabled=${LOGIN_GUARD:true}
|
|
mailserver.login-guard.max-per-ip=${LOGIN_GUARD_MAX_PER_IP:10}
|
|
mailserver.login-guard.max-per-account=${LOGIN_GUARD_MAX_PER_ACCOUNT:20}
|
|
mailserver.login-guard.window=${LOGIN_GUARD_WINDOW:15m}
|
|
mailserver.login-guard.block=${LOGIN_GUARD_BLOCK:15m}
|
|
mailserver.login-guard.max-block=${LOGIN_GUARD_MAX_BLOCK:24h}
|
|
mailserver.login-guard.trusted-ips=${LOGIN_GUARD_TRUSTED_IPS:}
|