149eb0b020
ci / typecheck (map[dir:apps/booking name:booking]) (push) Has been cancelled
ci / typecheck (map[dir:apps/portal name:portal]) (push) Has been cancelled
ci / typecheck (map[dir:apps/website name:website]) (push) Has been cancelled
ci / typecheck (map[dir:services/platform-api name:platform-api]) (push) Has been cancelled
ci / test (push) Has been cancelled
- install.sh: default repo stalwartlabs/mail-server -> stalwartlabs/stalwart
(renamed), and select the exact /stalwart-<target>.tar.gz asset excluding the
foundationdb build (head -n1 could grab the wrong one).
- config.toml: $env{...} -> %{env:...}% (correct Stalwart macro syntax).
KNOWN ISSUE: Stalwart v0.16 removed TOML config (single config.json datastore +
everything else in the DB via CLI/UI), so this config.toml does not load on
0.16.8 ("Failed to parse data store settings"). Needs either a pinned pre-0.16
version or a migration to the v0.16 config model. Binary is installed; the
service is stopped pending that decision.
103 lines
4.3 KiB
TOML
103 lines
4.3 KiB
TOML
# Stalwart Mail Server — Dezky PRODUCTION (bare-metal host, outside k3s)
|
|
#
|
|
# Topology (see host/README.md):
|
|
# - Mail protocol ports bind directly on the host's public IP.
|
|
# - Web/JMAP is served plaintext on 127-reachable :8080 and fronted by
|
|
# Traefik (k3s) for mail.dezky.eu:443. Stalwart does NOT bind 80/443 —
|
|
# those belong to Traefik.
|
|
# - TLS for the mail-protocol ports uses a cert ISSUED BY cert-manager
|
|
# (mail.dezky.eu) and copied here by stalwart/cert-sync.sh. Stalwart runs
|
|
# no ACME of its own (80/443 are Traefik's).
|
|
# - Storage is RocksDB on local disk — intentionally independent of the
|
|
# in-cluster Postgres so mail keeps flowing regardless of cluster state.
|
|
#
|
|
# Reference: https://stalw.art/docs
|
|
|
|
[server]
|
|
hostname = "mail.dezky.eu" # MUST match the IP's PTR/rDNS record
|
|
|
|
# ── Listeners ──────────────────────────────────────────────────────────────
|
|
# Mail protocols on the public IP; management/JMAP on internal 8080 only
|
|
# (firewall blocks 8080 from the world, allows the k3s pod CIDR + Traefik).
|
|
[server.listener]
|
|
"smtp" = { bind = "[::]:25", protocol = "smtp" }
|
|
"submission" = { bind = "[::]:587", protocol = "smtp", tls.implicit = false }
|
|
"submissions" = { bind = "[::]:465", protocol = "smtp", tls.implicit = true }
|
|
"imap" = { bind = "[::]:143", protocol = "imap", tls.implicit = false }
|
|
"imaps" = { bind = "[::]:993", protocol = "imap", tls.implicit = true }
|
|
"sieve" = { bind = "[::]:4190", protocol = "managesieve" }
|
|
# Internal HTTP: JMAP + WebAdmin + management API. Traefik terminates TLS for
|
|
# the public hostname and proxies here; platform-api (pod) calls it directly.
|
|
"http" = { bind = "0.0.0.0:8080", protocol = "http" }
|
|
|
|
# ── Storage — RocksDB on local disk (host-isolated from the cluster) ────────
|
|
[store."rocksdb"]
|
|
type = "rocksdb"
|
|
path = "/opt/stalwart/data"
|
|
compression = "lz4"
|
|
|
|
[storage]
|
|
data = "rocksdb"
|
|
fts = "rocksdb"
|
|
blob = "rocksdb"
|
|
lookup = "rocksdb"
|
|
directory = "internal"
|
|
|
|
[directory."internal"]
|
|
type = "internal"
|
|
store = "rocksdb"
|
|
|
|
# ── TLS — cert issued by cert-manager, synced here by cert-sync.sh ──────────
|
|
# Until the first sync runs, install.sh drops a self-signed bootstrap cert so
|
|
# the TLS listeners can start. cert-sync replaces it with the real LE cert.
|
|
[certificate."default"]
|
|
cert = "%{file:/opt/stalwart/etc/tls/cert.pem}%"
|
|
private-key = "%{file:/opt/stalwart/etc/tls/key.pem}%"
|
|
default = true
|
|
|
|
# ── Authentication ─────────────────────────────────────────────────────────
|
|
# Fallback admin is what platform-api uses for Basic auth on the JMAP
|
|
# management API (STALWART_ADMIN_USER/PASSWORD on the platform-api side).
|
|
[authentication]
|
|
fallback-admin.user = "admin"
|
|
fallback-admin.secret = "%{env:STALWART_ADMIN_PASSWORD}%"
|
|
|
|
# ── Resolver ───────────────────────────────────────────────────────────────
|
|
# DNSSEC-aware system resolver. Mail deliverability depends on clean DNS.
|
|
[resolver]
|
|
type = "system"
|
|
preserve-intermediates = true
|
|
concurrency = 4
|
|
|
|
# ── Spam filtering — built-in filter ON in production ──────────────────────
|
|
[spam-filter]
|
|
enable = true
|
|
|
|
# ── Logging — journald captures stdout ─────────────────────────────────────
|
|
[tracer."stdout"]
|
|
type = "stdout"
|
|
level = "info"
|
|
ansi = false
|
|
enable = true
|
|
|
|
# ── Audit webhook → platform-api (via the public api ingress) ──────────────
|
|
# Stalwart on the host reaches platform-api through Traefik on the public
|
|
# hostname; HMAC-signed so a public endpoint is safe.
|
|
[webhook."audit-ingest"]
|
|
url = "https://api.dezky.eu/ingest/stalwart/webhook"
|
|
signature-key = "%{env:STALWART_WEBHOOK_SECRET}%"
|
|
events = [
|
|
"auth.success",
|
|
"auth.failure",
|
|
"auth.banned",
|
|
"account.created",
|
|
"account.deleted",
|
|
"account.password-changed",
|
|
"message.rejected",
|
|
"policy.rejection",
|
|
"dkim.failure",
|
|
"dmarc.failure",
|
|
"spam.detected",
|
|
]
|
|
throttle = "1s"
|