Files
argocd-apps/apps/base/stalwart/resources/config.toml
T
unkinben 0d89a69c18 feat(stalwart): deploy Stalwart mail server with CNPG and S3
- stalwart namespace with Deployment + HPA (2-6 replicas)
- CNPG PostgreSQL cluster (3 instances, 20Gi cephrbd-fast-delete) with PgBouncer pooler
- S3/Ceph-RGW for blob storage (stalwart-maildata bucket, lz4 compressed)
- Secrets from Vault: postgres-credentials, s3-credentials, stalwart-admin
- TLS cert via cert-manager (vault-issuer) for mail.main.unkin.net
- SMTP relay on port 25 (internal ClusterIP, trusted pod CIDRs)
- Submission on port 587, IMAP 143/993, HTTPS 443 via LoadBalancer
- HTTP port 8080 for Traefik reverse proxy (web admin at mail.k8s.syd1.au.unkin.net)
- Outbound mail routed through postfix.mailgateway.svc.cluster.local:25
- Spam filtering offloaded to postfix/rspamd (disabled internally)
2026-05-24 12:44:46 +10:00

224 lines
4.6 KiB
TOML

[server]
hostname = "mail.main.unkin.net"
greeting = "Stalwart ESMTP"
# SMTP relay listener (receives from postfix for local delivery)
[server.listener."smtp-relay"]
bind = ["0.0.0.0:25"]
protocol = "smtp"
greeting = "Stalwart SMTP Relay"
[server.listener."smtp-relay".proxy]
trusted-networks = ["127.0.0.0/8", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
# SMTP submission listener (user-facing, TLS required)
[server.listener."submission"]
bind = ["0.0.0.0:587"]
protocol = "smtp"
greeting = "Stalwart SMTP Submission"
tls.require = true
[server.listener."submission".proxy]
trusted-networks = ["127.0.0.0/8", "::1"]
# IMAP listener
[server.listener."imap"]
bind = ["0.0.0.0:143"]
protocol = "imap"
[server.listener."imap".proxy]
trusted-networks = ["127.0.0.0/8", "::1"]
# IMAPS listener (implicit TLS)
[server.listener."imaps"]
bind = ["0.0.0.0:993"]
protocol = "imap"
tls.implicit = true
[server.listener."imaps".proxy]
trusted-networks = ["127.0.0.0/8", "::1"]
# HTTPS (web admin + JMAP) for external LoadBalancer
[server.listener."https"]
bind = ["0.0.0.0:443"]
protocol = "http"
tls.implicit = true
[server.listener."https".proxy]
trusted-networks = ["127.0.0.0/8", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
# Plain HTTP for Traefik reverse proxy (TLS terminated at Traefik)
[server.listener."http-internal"]
bind = ["0.0.0.0:8080"]
protocol = "http"
[server.listener."http-internal".proxy]
trusted-networks = ["127.0.0.0/8", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
[server.tls]
enable = true
implicit = false
certificate = "default"
[server.http]
use-x-forwarded = true
permissive-cors = false
[webadmin]
path = "/var/lib/stalwart/webadmin"
auto-update = true
resource = "https://github.com/stalwartlabs/webadmin/releases/latest/download/webadmin.zip"
# PostgreSQL store (via CNPG pooler)
[store."postgresql"]
type = "postgresql"
host = "stalwart-postgres-pooler.stalwart.svc.cluster.local"
port = 5432
database = "stalwart"
user = "stalwart"
password = "%{env:POSTGRES_PASSWORD}%"
timeout = "15s"
[store."postgresql".tls]
enable = true
allow-invalid-certs = false
[store."postgresql".pool]
max-connections = 10
[store."postgresql".purge]
frequency = "0 3 *"
# S3/Ceph-RGW store for blobs
[store."s3"]
type = "s3"
bucket = "stalwart-maildata"
region = "syd1"
access-key = "%{env:S3_ACCESS_KEY}%"
secret-key = "%{env:S3_SECRET_KEY}%"
endpoint = "https://radosgw.service.consul"
timeout = "30s"
key-prefix = "stalwart/"
compression = "lz4"
[store."s3".purge]
frequency = "30 5 *"
# Storage assignment
[storage]
data = "postgresql"
fts = "postgresql"
blob = "s3"
lookup = "postgresql"
directory = "internal"
in-memory = "postgresql"
# Directory configuration
[directory.internal]
type = "internal"
store = "postgresql"
# Fallback admin (password hash from Vault)
[authentication.fallback-admin]
user = "admin"
secret = "%{env:ADMIN_PASSWORD_HASH}%"
[authentication]
[authentication.directory]
directories = ["internal"]
[authorization]
directory = "internal"
# JMAP configuration
[jmap]
directory = "internal"
[jmap.protocol]
request-max-size = 10485760
get.max-objects = 500
query.max-results = 5000
changes.max-results = 5000
upload.max-size = 50000000
upload.ttl = "1h"
# IMAP configuration
[imap]
directory = "internal"
[imap.protocol]
max-requests = 64
# Inbound rate limiting
[[queue.limiter.inbound]]
key = ["remote_ip"]
rate = "500/1s"
enable = true
# Outbound routing through postfix relay
[queue]
path = "/var/lib/stalwart/queue"
[queue.schedule]
retry = ["2s", "5s", "1m", "5m", "15m", "30m", "1h", "2h"]
notify = ["1d", "3d"]
expire = "5d"
[session.extensions]
future-release = "7d"
# Route local domain mail locally, everything else through postfix relay
[queue.strategy]
route = [
{ if = "is_local_domain('', rcpt_domain)", then = "'local'" },
{ else = "'relay'" }
]
[queue.route."local"]
type = "local"
[queue.route."relay"]
type = "relay"
address = "postfix.mailgateway.svc.cluster.local"
port = 25
protocol = "smtp"
[queue.route."relay".tls]
implicit = false
allow-invalid-certs = false
# Session configuration
[session.ehlo]
reject-non-fqdn = false
[session.rcpt]
type = "internal"
store = "postgresql"
max-recipients = 25
[session.data]
max-messages = 10
max-message-size = 52428800
# Spam filtering offloaded to postfix/rspamd — disable internal spam filter
[spam-filter]
enable = false
# TLS certificate (cert-manager issues to /etc/stalwart/tls/)
[certificate."default"]
cert = "%{file:/etc/stalwart/tls/tls.crt}%"
private-key = "%{file:/etc/stalwart/tls/tls.key}%"
default = true
# Logging
[tracer]
type = "log"
level = "info"
ansi = false
multiline = true
# Metrics
[metrics]
prometheus.enable = true
prometheus.port = 9090