tailwart/config/config.toml

109 lines
3.8 KiB
TOML
Raw Normal View History

# ============================================================================
# Stalwart config — the over-engineered wiring: Postgres + Redis + Garage S3.
# ============================================================================
# THIS IS A STRAWMAN. Stalwart's config schema shifts between releases and a
# lot of runtime config moves into the admin store after first boot. Treat
# every key here as "verify against the version you pinned" — the same pitfall
# the federatedSocial repo learned the hard way. Pin the image tag once it works.
#
# Secrets come in via %{env:NAME}% so this file stays commit-safe.
# ============================================================================
[server]
hostname = "%{env:STALWART_HOSTNAME}%"
# ----------------------------------------------------------------------------
# Listeners — all bind inside the sidecar netns (tailnet only). The public
# edge is caddy/ (layer 4). PROXY protocol carries the real client IP across
# the proxy hop, which mail reputation (SPF/DNSBL/greylisting) depends on.
# ----------------------------------------------------------------------------
[server.listener.smtp]
bind = ["[::]:25"]
protocol = "smtp"
[server.listener.submission]
bind = ["[::]:587"]
protocol = "smtp"
[server.listener.submissions]
bind = ["[::]:465"]
protocol = "smtp"
tls.implicit = true
[server.listener.imap]
bind = ["[::]:143"]
protocol = "imap"
[server.listener.imaptls]
bind = ["[::]:993"]
protocol = "imap"
tls.implicit = true
[server.listener.http]
# JMAP, autoconfig/autodiscover, and the web admin. Fronted by the existing
# layer-7 Caddy vhost (mail.infinidim.net), not the L4 proxy.
bind = ["[::]:8080"]
protocol = "http"
# Trust the PROXY-protocol header only from the tailnet hop (CGNAT range).
[server.proxy]
trusted-networks = ["100.64.0.0/10"]
# ----------------------------------------------------------------------------
# Stores — three backends because we can.
# ----------------------------------------------------------------------------
[store.pg]
type = "postgresql"
host = "%{env:DB_HOST}%"
port = 5432
database = "%{env:STALWART_DB_NAME}%"
user = "%{env:STALWART_DB_USER}%"
password = "%{env:STALWART_DB_PASSWORD}%"
[store.redis]
type = "redis"
urls = ["%{env:REDIS_URL}%"]
[store.s3]
type = "s3"
endpoint = "%{env:S3_ENDPOINT}%"
region = "%{env:S3_REGION}%"
bucket = "%{env:S3_BUCKET}%"
access-key = "%{env:S3_ACCESS_KEY}%"
secret-key = "%{env:S3_SECRET_KEY}%"
# Which store backs which concern.
[storage]
data = "pg" # accounts, mailboxes, metadata, indexes
fts = "pg" # full-text search
blob = "s3" # message bodies + attachments → Garage
lookup = "redis" # rate limits, caches, token revocation (distributed)
directory = "internal"
[directory.internal]
type = "internal"
store = "pg"
# ----------------------------------------------------------------------------
# Outbound — relay via smarthost when the VPS blocks direct :25.
# ----------------------------------------------------------------------------
[queue.outbound]
# If STALWART_SMARTHOST is set, route everything through it; else direct MX.
next-hop = "%{env:STALWART_SMARTHOST}%"
# ----------------------------------------------------------------------------
# TLS — Stalwart terminates mail TLS itself (the L4 proxy is pass-through),
# so it needs the cert. DNS-01 works fine behind the proxy (no inbound needed).
# Swap to a mounted certbot cert by pointing [certificate] at files instead.
# ----------------------------------------------------------------------------
[acme.le]
directory = "https://acme-v02.api.letsencrypt.org/directory"
challenge = "dns-01"
provider = "%{env:STALWART_ACME_PROVIDER}%"
secret = "%{env:STALWART_ACME_TOKEN}%"
domains = ["%{env:STALWART_HOSTNAME}%"]
[authentication.fallback-admin]
user = "admin"
secret = "%{env:STALWART_FALLBACK_ADMIN_SECRET}%"