{{- /*
Stalwart bootstrap config.toml — applied when RocksDB is empty (first
install). Subsequent runtime config edits via webadmin / stalwart-cli are
persisted in RocksDB and NOT reflected back to this ConfigMap (per the
contabo-mkt apps/stalwart pattern + stalwart_expression_syntax.md memory).

Stalwart expression-language gotcha (incident 2026-04-14): equality
matchers MUST use `==` not `=`. A single `=` is assignment and silently
never matches — see stalwart_expression_syntax.md. Every comparison in
this template uses `==`.
*/}}
{{- $tenantDomain := include "bp-stalwart-tenant.tenantDomain" . -}}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "bp-stalwart-tenant.configMapName" . }}
  labels:
    {{- include "bp-stalwart-tenant.labels" . | nindent 4 }}
data:
  config.toml: |
    [server]
    hostname = {{ include "bp-stalwart-tenant.webmailHost" . | quote }}

    [server.listener.smtp]
    bind = ["0.0.0.0:25"]
    protocol = "smtp"

    [server.listener.submission]
    bind = ["0.0.0.0:587"]
    protocol = "smtp"
    tls.implicit = false

    [server.listener.submissions]
    bind = ["0.0.0.0:465"]
    protocol = "smtp"
    tls.implicit = true

    [server.listener.imap]
    bind = ["0.0.0.0:143"]
    protocol = "imap"
    tls.implicit = false

    [server.listener.imaps]
    bind = ["0.0.0.0:993"]
    protocol = "imap"
    tls.implicit = true

    [server.listener.https]
    bind = ["0.0.0.0:8443"]
    protocol = "http"
    tls.implicit = true

    [server.listener.http]
    bind = ["0.0.0.0:8080"]
    protocol = "http"
    tls.implicit = false

    [certificate.default]
    cert = "%{file:/etc/stalwart/tls/tls.crt}%"
    private-key = "%{file:/etc/stalwart/tls/tls.key}%"

    [storage]
    data = "rocksdb"
    blob = "rocksdb"
    fts = "rocksdb"
    lookup = "rocksdb"
    directory = "internal"

    [store.rocksdb]
    type = "rocksdb"
    path = "/opt/stalwart/data"

    [directory.internal]
    type = "internal"
    store = "rocksdb"

    # ─── OIDC SSO against per-tenant Keycloak (#915) ───────────────────
    # Stalwart 0.16+ stores OIDC directory definitions in its runtime
    # settings KV store (RocksDB), populated via the `/api/settings`
    # admin endpoint — NOT in this bootstrap config.toml. The pre-0.16
    # `[directory.keycloak] type = "oidc"` kebab-case TOML block is
    # silently ignored by the upstream registry parser
    # (verified against crates/registry/src/schema/structs.rs — the
    # OidcDirectory struct serdes camelCase: `@type = "Oidc"`,
    # `issuerUrl`, `claimUsername`, `claimName`, `claimGroups`,
    # `requireScopes`). Stalwart acts as the OIDC RESOURCE SERVER:
    # bearer JWTs minted by the per-tenant Keycloak are validated
    # against the directory's issuer and claim mappings.
    #
    # The post-install setup Job in templates/mailbox-provision-job.yaml
    # POSTs the canonical OIDC directory entry (with the per-tenant
    # Keycloak realm URL the orchestrator emitted into
    # `.Values.keycloak.realmURL`) AND flips `storage.directory` to
    # `keycloak`, making the per-tenant realm the primary
    # authentication backend for SMTP/IMAP/JMAP/HTTP. Until that Job
    # runs, the `internal` directory below keeps Stalwart bootable so
    # the admin user can log in via password.
    #
    # The OIDC client (`stalwart` confidential client registered in
    # the realm by the orchestrator's bp-keycloak realmConfig.tenant
    # block) is consumed by webmail / Thunderbird / Apple Mail to
    # initiate the authorization flow against Keycloak directly; they
    # then present the bearer to Stalwart for IMAP/SMTP/JMAP login.
    # Stalwart itself does NOT mint OAuth2 redirects — the OIDC client
    # secret in `keycloak.clientSecretName` is mounted as the
    # `OIDC_CLIENT_SECRET` env var purely so the setup Job can echo it
    # back to clients via `/api/oauth/.well-known` queries (mailbox
    # provision flow consumed by unified-rbac per ADR-0003 §3).

    [auth.dkim]
    sign = [{if = "is_local_domain('*')", then = true}, {else = false}]

    [signature.dkim]
    domain = {{ $tenantDomain | quote }}
    selector = {{ .Values.dns.dkim.selector | quote }}
    algorithm = {{ .Values.dns.dkim.algorithm | quote }}
    canonicalization = "relaxed/relaxed"
    set-body-length = false
    report = true

    # Per-recipient-domain TLS — default profile only at install time;
    # the operator may add per-domain overrides via webadmin runtime
    # config (NOT this bootstrap configmap). Stalwart expression syntax
    # requires `==` (comparison), not `=` (assignment) — a single `=`
    # silently never matches (incident 2026-04-14, openova.io/huawei.com).
    [tls.default]
    allow-invalid-certs = false
    description = "Default TLS settings"

    [[queue.outbound.tls]]
    else = "'default'"

    # Inbound spam filtering. Per ~/.claude/projects/-home-openova-repos-
    # openova-private/memory/feedback_no_spam_filtering.md and the
    # founder directive on the shared openova.io Stalwart, NEVER reject
    # inbound mail on spam scoring — accept everything, filter at the
    # client. Per-tenant deployments inherit the same posture by default;
    # individual SMEs may opt in via webadmin (NOT this configmap).
    [spam-filter]
    enable = false

    [spam-filter.score]
    spam = 999.0
    reject = 999.0
    discard = 9999.0

    # Local domains — the SME's primary domain (and any aliases the
    # operator adds via webadmin runtime).
    [config.local-domain]
    {{ printf "%q" $tenantDomain }} = true
