feat(stalwart-mail): add mail server

This commit is contained in:
WrenIX 2024-01-10 02:16:55 +01:00
parent 8c589d1516
commit 4b39b5399b
Signed by: wrenix
GPG key ID: 7AFDB012974B1BB5
19 changed files with 2402 additions and 0 deletions

View file

@ -1,6 +1,8 @@
#!/bin/sh
ROOT_DIR="./docs/modules/charts/"
rm "${ROOT_DIR}/pages/"*".adoc"
echo "* charts" > "${ROOT_DIR}/nav.adoc"
for name in * ; do
if \

View file

@ -13,3 +13,4 @@
** xref:monitoring.adoc[monitoring]
** xref:ntfy.adoc[ntfy]
** xref:postgresql.adoc[postgresql]
** xref:stalwart-mail.adoc[stalwart-mail]

View file

@ -0,0 +1 @@
../../../../stalwart-mail/README.adoc

1
stalwart-mail/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
values_test.yaml

23
stalwart-mail/.helmignore Normal file
View file

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

10
stalwart-mail/Chart.yaml Normal file
View file

@ -0,0 +1,10 @@
apiVersion: v2
name: stalwart-mail
description: Helm Chart for Stalwart Mail Server - Secure & Modern All-in-One Mail Server (IMAP, JMAP, SMTP)
icon: https://stalw.art/home/apple-touch-icon.png
type: application
version: 0.0.1
appVersion: "0.5.3"
maintainers:
- name: WrenIX
url: https://wrenix.eu

1105
stalwart-mail/README.adoc Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,14 @@
{{ define "chart.prerequirements" -}}
= Alpha
WARNING
====
We stop working on this Helm-Chart.
There are still many breaking change like:
* https://github.com/stalwartlabs/mail-server/issues/211[storage.fts in toml configuration has two meanings]
We hope that stalward mail-server becomes more stable.
====
{{ end }}

View file

@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "stalwart-mail.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "stalwart-mail.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "stalwart-mail.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "stalwart-mail.labels" -}}
helm.sh/chart: {{ include "stalwart-mail.chart" . }}
{{ include "stalwart-mail.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "stalwart-mail.selectorLabels" -}}
app.kubernetes.io/name: {{ include "stalwart-mail.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "stalwart-mail.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "stalwart-mail.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,15 @@
{{- with .Values.certificate.certmanager }}
{{- if and .enabled (not $.Values.certificate.secretName) ($.Capabilities.APIVersions.Has "cert-manager.io/v1/Certificate") }}
---
apiVersion: "cert-manager.io/v1"
kind: Certificate
metadata:
name: {{ include "stalwart-mail.fullname" $ }}
spec:
secretName: {{ include "stalwart-mail.fullname" $ }}-cert
issuerRef:
{{- toYaml .issuerRef | nindent 4 }}
dnsNames:
{{- toYaml .dnsNames | nindent 4 }}
{{- end }}{{/* end-if .enabled */}}
{{- end }}{{/* end-with .certificates */}}

View file

@ -0,0 +1,111 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "stalwart-mail.fullname" . }}
labels:
{{- include "stalwart-mail.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "stalwart-mail.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
confighash: {{ toYaml .Values.config | sha256sum | trunc 32 }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "stalwart-mail.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "stalwart-mail.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
{{- with .Values.image}}
image: "{{ coalesce $.Values.global.image.registry .registry }}/{{ .repository }}:{{ .tag | default (printf "v%s" $.Chart.AppVersion) }}"
imagePullPolicy: {{ coalesce $.Values.global.image.pullPolicy .pullPolicy }}
{{- end }}
ports:
{{- range $name, $port := .Values.service.ports }}
- name: {{ $name }}
containerPort: {{ $port }}
protocol: TCP
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: data
mountPath: "/data"
- name: data
mountPath: "/data/blobs"
subPath: "blobs"
- name: data
mountPath: "/data/queue"
subPath: "queue"
- name: data
mountPath: "/data/reports"
subPath: reports
- name: config
mountPath: "/opt/stalwart-mail/etc/config.toml"
subPath: "config.toml"
- name: config
mountPath: "/opt/stalwart-mail/etc/dkim/private.key"
subPath: "dkim.key"
{{- if or .Values.certificate.secretName .Values.certificate.certmanager.enabled }}
- name: certificate
mountPath: "/opt/stalwart-mail/etc/certs"
{{- end }}
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
- name: "config"
secret:
secretName: {{ include "stalwart-mail.fullname" . }}
{{- if or .Values.certificate.secretName .Values.certificate.certmanager.enabled }}
- name: certificate
secret:
secretName: {{ .Values.certificate.secretName | default (printf "%s-cert" (include "stalwart-mail.fullname" .)) }}
{{- end }}
- name: "data"
{{- if .Values.persistence.enabled }}
{{- if .Values.persistence.hostPath }}
hostPath:
type: Directory
path: {{ .Values.persistence.hostPath | quote }}
{{- else }}{{/* else .persistence.hostPath */}}
persistentVolumeClaim:
claimName: {{ coalesce .Values.persistence.existingClaim (include "stalwart-mail.fullname" .) }}
{{- end }}{{/* end-else .persistence.hostPath */}}
{{- else }}{{/* else .persistence.enabled */}}
emptyDir: {}
{{- end }}{{/* end-else .persistence.enabled */}}
{{- with .Values.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View file

@ -0,0 +1,32 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "stalwart-mail.fullname" . }}
labels:
{{- include "stalwart-mail.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "stalwart-mail.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,61 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "stalwart-mail.fullname" . -}}
{{- $svcPort := .Values.service.ports.http -}}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "stalwart-mail.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
pathType: {{ .pathType }}
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- else }}
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,28 @@
{{- with .Values.persistence }}
{{- if and .enabled (not .existingClaim) }}
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ template "stalwart-mail.fullname" $ }}
labels:
{{- include "stalwart-mail.labels" $ | nindent 4 }}
{{- with .annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
accessModes:
- {{ .accessMode | quote }}
resources:
requests:
storage: {{ .size | quote }}
{{- with .storageClass }}
{{- if (eq "-" .) }}
storageClassName: ""
{{- else }}
storageClassName: {{ . | quote }}
{{- end }}
{{- end }}
{{- end }}{{/* end-if .enabled */}}
{{- end }}{{/* end-with .persistence */}}

View file

@ -0,0 +1,20 @@
---
apiVersion: v1
kind: Secret
metadata:
name: {{ include "stalwart-mail.fullname" . }}
labels:
{{- include "stalwart-mail.labels" . | nindent 4 }}
annotations:
confighash: {{ toYaml .Values.config | sha256sum | trunc 32 }}
data:
"config.toml": {{ regexReplaceAll
"trusted-networks = \\[(.*)\\]"
(
toToml .Values.config
| replace ".0\n" "\n"
| replace "fts-table-duplicated-workaround" "fts"
)
"trusted-networks = {${1}}"
| b64enc }}
"dkim.key": {{ genPrivateKey "rsa" | b64enc }}

View file

@ -0,0 +1,26 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "stalwart-mail.fullname" . }}
labels:
{{- include "stalwart-mail.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.service }}
type: {{ .type }}
ipFamilyPolicy: {{ .ipFamilyPolicy }}
ipFamilies:
{{- toYaml .ipFamilies | nindent 4 }}
ports:
{{- range $name, $port := .ports }}
- port: {{ $port }}
targetPort: {{ $name }}
protocol: TCP
name: {{ $name }}
{{- end }}
{{- end }}{{/* end-with .service */}}
selector:
{{- include "stalwart-mail.selectorLabels" . | nindent 4 }}

View file

@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "stalwart-mail.serviceAccountName" . }}
labels:
{{- include "stalwart-mail.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}

View file

@ -0,0 +1,21 @@
{{- if .Values.traefik.enabled }}
{{- range $port,$entryport := .Values.traefik.ports }}
---
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: {{ include "stalwart-mail.fullname" $ }}-{{ $port }}
spec:
entryPoints:
- {{ $entryport }}
routes:
- match: HostSNI(`{{ $.Values.config.macros.host }}`)
services:
- name: {{ include "stalwart-mail.fullname" $ }}
port: {{ $port}}
proxyProtocol:
version: 2
tls:
passthrough: true
{{- end }}
{{- end }}{{/* end-if .enabled */}}

856
stalwart-mail/values.yaml Normal file
View file

@ -0,0 +1,856 @@
# Default values for stalwart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
global:
image:
# -- if set it will overwrite all registry entries
registry:
# -- if set it will overwrite all pullPolicy
pullPolicy:
replicaCount: 1
image:
registry: docker.io
repository: stalwartlabs/mail-server
pullPolicy: IfNotPresent
# -- Overrides the image tag whose default is the chart appVersion.
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
##
# Configuration of stalwart mail-server
# defaults taken from: https://github.com/stalwartlabs/mail-server/tree/6aeadb9cda301ec5f210d8e8390515e6292592fa/resources/config
#
# files import completed:
# - config.toml
# - common/*.toml
# - imap/*.toml
#
##
config:
##
# macros
##
# -- macros (from: config.toml)
macros:
host: "__HOST__"
default_domain: "__DOMAIN__"
default_directory: "memory"
default_store: "sqlite"
##
# global
##
global:
shared-map:
# -- global shared-map capacity (from: common/server.toml)
capacity: 10
# -- global shared-map shard (from: common/server.toml)
shard: 32
# -- global thead-pool (from: common/server.toml)
thread-pool:
# -- global tracing (from: common/tracing.toml)
tracing:
method: "stdout"
level: "info"
##
# server
##
server:
# -- server hostname (from: common/server.toml)
hostname: "%{HOST}%"
security:
# -- server security blocked-networks (from: common/server.toml)
blocked-networks: {}
# -- server security fail2ban (from: common/server.toml)
fail2ban: "100/1d"
run-as:
# -- server run-as user (from: common/server.toml)
user: "stalwart-mail"
# -- server run-as group (from: common/server.toml)
group: "stalwart-mail"
socket:
# -- server socket nodelay (from: common/server.toml)
nodelay: true
# -- server socket reuse-addr (from: common/server.toml)
reuse-addr: true
# -- server socket reuse-port (from: common/server.toml)
reuse-port: false
# -- server socket backlog (from: common/server.toml)
backlog: 1024
# -- server socket ttl (from: common/server.toml)
ttl: 3600
# -- server socket send-buffer-size (from: common/server.toml)
send-buffer-size: 65535
# -- server socket recv-buffer-size (from: common/server.toml)
recv-buffer-size: 65535
# -- server socket linger (from: common/server.toml)
linger: 1
# -- server socket tos (from: common/server.toml)
tos: 1
tls:
# -- server tls enable (from: common/tls.toml)
enable: true
# -- server tls implicit (from: common/tls.toml)
implicit: false
# -- server tls timeout (from: common/tls.toml)
timeout: "1m"
# -- server tls certificate (from: common/tls.toml)
certificate: "default"
# -- server tls acme (from: common/tls.toml)
# example: "letsencrypt"
acme:
# -- server tls sni (from: common/tls.toml)
# example: [{subject: "", certificate: ""}]
sni:
# -- server tls protocols (from: common/tls.toml)
# example: ["TLSv1.2", "TLSv1.3"]
protocols:
# -- server tls #ciphers (from: common/tls.toml)
# example: [ "TLS13_AES_256_GCM_SHA384", "TLS13_AES_128_GCM_SHA256",
# "TLS13_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
# "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
# "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
# "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"]
ciphers:
# -- server tls ignore-client-order (from: common/tls.toml)
ignore-client-order: true
# -- server listener
listener:
smtp:
protocol: "smtp"
bind: ["[::]:25"]
smtp-submission:
protocol: "smtp"
bind: ["[::]:587"]
smtps:
protocol: "smtp"
bind: ["[::]:465"]
tls:
implicit: true
# -- server listener with name imap (from: imap/listener.toml)
imap:
bind: ["[::]:143"]
protocol: "imap"
# -- server listener with name imaps (from: imap/listener.toml)
imaps:
bind: ["[::]:993"]
protocol: "imap"
tls:
implicit: true
# -- server listener with name sieve (from: imap/listener.toml)
sieve:
bind: ["[::]:4190"]
protocol: "managesieve"
tls:
implicit: true
# -- jmap/listener.yaml
http:
protocol: "jmap"
bind: ["[::]:80"]
url: "https://%{HOST}%"
##
# sieve
##
sieve:
untrusted:
# -- sieve untrusted disable-capabilities (from: common/sieve.toml)
disable-capabilities: []
# -- sieve untrusted notification-uris (from: common/sieve.toml)
notification-uris: ["mailto"]
# -- sieve untrusted protected-headers (from: common/sieve.toml)
protected-headers: ["Original-Subject", "Original-From", "Received", "Auto-Submitted"]
limits:
# -- sieve untrusted limit name-length (from: common/sieve.toml)
name-length: 512
# -- sieve untrusted limit max-scripts (from: common/sieve.toml)
max-scripts: 256
# -- sieve untrusted limit script-size (from: common/sieve.toml)
script-size: 102400
# -- sieve untrusted limit string-length (from: common/sieve.toml)
string-length: 4096
# -- sieve untrusted limit variable-name-length (from: common/sieve.toml)
variable-name-length: 32
# -- sieve untrusted limit variable-size (from: common/sieve.toml)
variable-size: 4096
# -- sieve untrusted limit nested-blocks (from: common/sieve.toml)
nested-blocks: 15
# -- sieve untrusted limit nested-tests (from: common/sieve.toml)
nested-tests: 15
# -- sieve untrusted limit nested-foreverypart (from: common/sieve.toml)
nested-foreverypart: 3
# -- sieve untrusted limit match-variables (from: common/sieve.toml)
match-variables: 30
# -- sieve untrusted limit local-variables (from: common/sieve.toml)
local-variables: 128
# -- sieve untrusted limit header-size (from: common/sieve.toml)
header-size: 1024
# -- sieve untrusted limit includes (from: common/sieve.toml)
includes: 3
# -- sieve untrusted limit nested-includes (from: common/sieve.toml)
nested-includes: 3
# -- sieve untrusted limit cpu (from: common/sieve.toml)
cpu: 5000
# -- sieve untrusted limit redirects (from: common/sieve.toml)
redirects: 1
# -- sieve untrusted limit received-headers (from: common/sieve.toml)
received-headers: 10
# -- sieve untrusted limit outgoing-messages (from: common/sieve.toml)
outgoing-messages: 3
vacation:
# -- sieve untrusted vacation default-subject (from: common/sieve.toml)
default-subject: "Automated reply"
# -- sieve untrusted vacation subject-prefix (from: common/sieve.toml)
subject-prefix: "Auto: "
default-expiry:
# -- sieve untrusted default-expiry vacation (from: common/sieve.toml)
vacation: "30d"
# -- sieve untrusted default-expiry duplicate (from: common/sieve.toml)
duplicate: "7d"
trusted:
# -- sieve trusted from-name (from: common/sieve.toml)
from-name: "Automated Message"
# -- sieve trusted from-addr (from: common/sieve.toml)
from-addr: "no-reply@%{DEFAULT_DOMAIN}%"
# -- sieve trusted return-path (from: common/sieve.toml)
return-path: ""
# -- sieve trusted hostname (from: common/sieve.toml)
hostname: "%{HOST}%"
# -- sieve trusted no-capability-check (from: common/sieve.toml)
no-capability-check: true
# -- sieve trusted sign (from: common/sieve.toml)
sign: ["rsa"]
limits:
# -- sieve trusted limits redirects (from: common/sieve.toml)
redirects: 3
# -- sieve trusted limits out-messages (from: common/sieve.toml)
out-messages: 5
# -- sieve trusted limits received-headers (from: common/sieve.toml)
received-headers: 50
# -- sieve trusted limits cpu (from: common/sieve.toml)
cpu: 1048576
# -- sieve trusted limits nested-includes (from: common/sieve.toml)
nested-includes: 5
# -- sieve trusted limits duplicate-expiry (from: common/sieve.toml)
duplicate-expiry: "7d"
scripts:
# -- sieve trusted scripts connect (from: common/sieve.toml)
connect:
# -- sieve trusted scripts ehlo (from: common/sieve.toml)
ehlo:
# -- sieve trusted scripts mail (from: common/sieve.toml)
mail:
##
# storage
##
storage:
# -- storage data (from: common/store.toml)
data: "%{DEFAULT_STORE}%"
# -- storage fts (from: common/store.toml)
# BROKEN / TODO
# see: https://github.com/stalwartlabs/mail-server/issues/211
fts: "%{DEFAULT_STORE}%"
# -- storage blob (from: common/store.toml)
blob: "%{DEFAULT_STORE}%"
# -- storage lookup (from: common/store.toml)
lookup: "%{DEFAULT_STORE}%"
# -- storage directory (from: common/store.toml)
directory: "%{DEFAULT_DIRECTORY}%"
encryption:
# -- storage encryption enable (from: common/store.toml)
enable: true
# -- storage encryption append (from: common/store.toml)
append: false
spam:
# -- storage spam header (from: common/store.toml)
header: "X-Spam-Status: Yes"
# BROKEN / TODO
# should be fts:
# see: https://github.com/stalwartlabs/mail-server/issues/211
fts-table-duplicated-workaround:
# -- storage - fts - default-language (from: common/store.toml)
default-language: "en"
cluster:
# -- storage - cluster - node-id (from: common/store.toml)
node-id:
##
# ACME
##
acme:
# -- acme with name letsencrypt (from: common/tls.toml)
letsencrypt:
# -- acme directory (from: common/tls.toml)
directory: "https://acme-v02.api.letsencrypt.org/directory"
# -- acme contact (from: common/tls.toml)
contact: ["postmaster@%{DEFAULT_DOMAIN}%"]
# -- acme cache (from: common/tls.toml)
cache: "/opt/stalwart-mail/etc/acme"
# -- acme port (from: common/tls.toml)
port: 443
# -- acme renew-before (from: common/tls.toml)
renew-before: "30d"
##
# certificate
##
certificate:
# -- certificate with name default (from: common/tls.toml)
default:
# -- certificate cert (from: common/tls.toml)
cert: "file:///opt/stalwart-mail/etc/certs/tls.crt"
# -- certificate private-key (from: common/tls.toml)
private-key: "file:///opt/stalwart-mail/etc/certs/tls.key"
##
# directory
##
directory:
# -- directory - with name memory (from: directory/internal.yaml)
memory:
type: memory
# -- overwrite me, if not wanted
disable: false
options:
catch-all: true
subaddressing: true
principals:
- type: "admin"
description: "Superuser"
name: "admin"
secret: "changeme"
mail:
- "postmaster@%{DEFAULT_DOMAIN}%"
##
# store
##
store:
# -- store - with name sqlite
sqlite:
type: "sqlite"
# -- overwrite me, if not wanted
disable: false
path: "/data/index.sqlite3"
purge:
frequency: "0 3 *"
query:
name: "SELECT name, type, secret, description, quota FROM accounts WHERE name = ? AND active = true"
members: "SELECT member_of FROM group_members WHERE name = ?"
recipients: "SELECT name FROM emails WHERE address = ?"
emails: "SELECT address FROM emails WHERE name = ? AND type != 'list' ORDER BY type DESC, address ASC"
verify: "SELECT address FROM emails WHERE address LIKE '%' || ? || '%' AND type = 'primary' ORDER BY address LIMIT 5"
expand: "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = ? AND l.type = 'list' ORDER BY p.address LIMIT 50"
domains: "SELECT 1 FROM emails WHERE address LIKE '%@' || ? LIMIT 1"
# -- store - with name fs
fs:
type: "fs"
# -- overwrite me, if not wanted
disable: false
path: "/data/blobs"
depth: 2
purge:
frequency: "0 3 *"
##
# OAuth
##
oauth:
# -- oauth - key
key: "__OAUTH_KEY__"
# -- oauth - auth
auth:
max-attempts: 3
# -- oauth - expiry
expiry:
user-code: "30m"
auth-code: "10m"
token: "1h"
refresh-token: "30d"
refresh-token-renew: "4d"
# -- oauth - cache
cache:
size: 128
##
# SMTP configuration (smtp/*.yaml)
##
##
# query (from: smtp/queue.yaml)
##
queue:
# -- queue-path
path: "/data/queue"
# -- queue-hash
hash: 64
# -- queue-schedule
schedule:
retry: ["2m", "5m", "10m", "15m", "30m", "1h", "2h"]
notify: ["1d", "3d"]
expire: "5d"
# -- queue-outbound
outbound:
# hostname: "%{HOST}%"
next-hop:
- if: "rcpt-domain"
in-list: "%{DEFAULT_DIRECTORY}%/domains"
then: "local"
- else: false
ip-strategy: "ipv4-then-ipv6"
tls:
dane: "optional"
mta-sts: "optional"
starttls: "require"
allow-invalid-certs: false
limits:
mx: 7
multihomed: 2
timeouts:
connect: "3m"
greeting: "3m"
tls: "2m"
ehlo: "3m"
mail-from: "3m"
rcpt-to: "3m"
data: "10m"
mta-sts: "2m"
##
# Report (from: smtp/report.yaml)
##
report:
# -- report-path
path: "/data/reports"
# -- report-hash
hash: 64
# submitter: "%{HOST}%"
# -- report-analysis
analysis:
addresses: ["dmarc@*", "abuse@*", "postmaster@*"]
forward: true
# store: "/data/incoming"
# -- report-dsn
dsn:
from-name: "Mail Delivery Subsystem"
from-address: "MAILER-DAEMON@%{DEFAULT_DOMAIN}%"
sign: ["rsa"]
# -- report-dkim
dkim:
from-name: "Report Subsystem"
from-address: "noreply-dkim@%{DEFAULT_DOMAIN}%"
subject: "DKIM Authentication Failure Report"
sign: ["rsa"]
send: "1/1d"
# -- report-spf
spf:
from-name: "Report Subsystem"
from-address: "noreply-spf@%{DEFAULT_DOMAIN}%"
subject: "SPF Authentication Failure Report"
send: "1/1d"
sign: ["rsa"]
# -- report-dmarc
dmarc:
from-name: "Report Subsystem"
from-address: "noreply-dmarc@%{DEFAULT_DOMAIN}%"
subject: "DMARC Authentication Failure Report"
send: "1/1d"
sign: ["rsa"]
aggregate:
from-name: "DMARC Report"
from-address: "noreply-dmarc@%{DEFAULT_DOMAIN}%"
org-name: "%{DEFAULT_DOMAIN}%"
# contact-info: ""
send: "daily"
# -- default: 25 mb
max-size: 26214400
sign: ["rsa"]
# -- report-tls
tls:
aggregate:
from-name: "TLS Report"
from-address: "noreply-tls@%{DEFAULT_DOMAIN}%"
org-name: "%{DEFAULT_DOMAIN}%"
# contact-info: ""
send: "daily"
# -- default: 25 mb
max-size: 26214400
sign: ["rsa"]
##
# resolver (from: smtp/resolver.yaml)
##
resolver:
# -- resolver-type
type: "system"
# -- resolver-preserve-intermediates
preserve-intermediates: true
# -- resolver-concurrency
concurrency: 2
# -- resolver-timeout
timeout: "5s"
# -- resolver-attempts
attempts: 2
# -- resolver-try-tcp-on-error
try-tcp-on-error: true
# -- resolver-public-suffix
public-suffix:
- "https://publicsuffix.org/list/public_suffix_list.dat"
- "file:///opt/stalwart-mail/etc/spamfilter/maps/suffix_list.dat.gz"
# -- resolver-cache
cache:
txt: 2048
mx: 1024
ipv4: 1024
ipv6: 1024
ptr: 1024
tlsa: 1024
mta-sts: 1024
##
# signature (from: smtp/signature.yaml)
##
signature:
# -- signature-rsa
rsa:
# public-key: "file://opt/stalwart-mail/etc/dkim/%{DEFAULT_DOMAIN}%.cert"
private-key: "file://opt/stalwart-mail/etc/dkim/private.key"
domain: "%{DEFAULT_DOMAIN}%"
selector: "stalwart"
headers: ["From", "To", "Date", "Subject", "Message-ID"]
algorithm: "rsa-sha256"
canonicalization: "relaxed/relaxed"
# expire: "10d"
# third-party: ""
# third-party-algo: ""
# auid: ""
set-body-length: false
report: true
##
# IMAP
##
imap:
request:
# -- imap request max-size (from: imap/settings.toml)
max-size: 52428800
auth:
# -- imap auth max-failures(from: imap/settings.toml)
max-failures: 3
# -- imap auth allow-plain-text (from: imap/settings.toml)
allow-plain-text: false
folders:
name:
# -- imap folders name shared (from: imap/settings.toml)
shared: "Shared Folders"
timeout:
# -- imap timeout authenticated (from: imap/settings.toml)
authenticated: "30m"
# -- imap timeout anonymous (from: imap/settings.toml)
anonymous: "1m"
# -- imap timeout idle (from: imap/settings.toml)
idle: "30m"
rate-limit:
# -- imap rate-limit requests (from: imap/settings.toml)
requests: "2000/1m"
# -- imap rate-limit concurrent (from: imap/settings.toml)
concurrent: 6
protocol:
# -- imap protocol uidplus (from: imap/settings.toml)
uidplus: false
##
# JMAP
##
jmap:
# -- jmap-directory (from: jmap/auth.yaml)
directory: "%{DEFAULT_DIRECTORY}%"
# -- jmap-session (from: jmap/auth.yaml)
session:
cache:
ttl: "1h"
size: 100
purge:
frequency: "0 3 *"
# -- jmap-protocol (from: jmap/protocol.yaml)
protocol:
get:
max-objects: 500
set:
max-objects: 500
request:
max-concurrent: 4
max-size: 10000000
max-calls: 16
query:
max-results: 5000
upload:
max-size: 50000000
max-concurrent: 4
ttl: "1h"
quota:
files: 1000
size: 50000000
changes:
max-results: 5000
# -- jmap-mailbox
mailbox:
max-depth: 10
max-name-length: 255
# -- jmap-email
email:
max-attachment-size: 50000000
max-size: 75000000
parse:
max-items: 10
# -- jmap-principal
principal:
allow-lookups: true
# -- jmap-push (from: jmap/push.yaml)
push:
max-total: 100
throttle: "1ms"
attempts:
interval: "1m"
max: 3
retry:
interval: "1s"
timeout:
request: "10s"
verify: "1s"
# -- jmap-event-source
event-source:
throttle: "1s"
# -- jmap-rate-limit (from: jmap/ratelimit.yaml)
rate-limit:
account: "1000/1m"
authentication: "10/1m"
anonymous: "100/1m"
use-forwarded: true
cache:
size: 1024
# -- jmap-web-sockets (from: jmap/websocket.yaml)
web-sockets:
throttle: "1s"
timeout: "10m"
heartbeat: "1m"
serviceAccount:
# Specifies whether a service account should be created
create: false
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
ipFamilies: ["IPv4"]
# -- other option is RequireDualStack
ipFamilyPolicy: "SingleStack"
annotations: {}
ports:
smtp: 25
smtp-submission: 587
smtps: 465
imap: 143
imaps: 993
sieve: 4190
http: 80
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
traefik:
enabled: false
ports:
http: websecure
imaps: imaps
smtps: smtps
certificate:
# -- not needed if certmanager is used
secretName:
certmanager:
enabled: true
issuerRef:
group: cert-manager.io
kind: ClusterIssuer
name: letsencrypt-prod
dnsNames:
- "chart-example.local"
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
affinity: {}
persistence:
# -- Enable persistence using Persistent Volume Claims
# ref: http://kubernetes.io/docs/user-guide/persistent-volumes/
enabled: true
annotations: {}
# -- Persistent Volume Storage Class
# If defined, storageClassName: <storageClass>
# If set to "-", storageClassName: "", which disables dynamic provisioning
# If undefined (the default) or set to null, no storageClassName spec is
# set, choosing the default provisioner. (gp2 on AWS, standard on
# GKE, AWS & OpenStack)
storageClass:
# -- A manually managed Persistent Volume and Claim
# Requires persistence.enabled: true
# If defined, PVC must be created manually before volume will be bound
existingClaim:
# -- Do not create an PVC, direct use hostPath in Pod
hostPath:
# -- accessMode
accessMode: ReadWriteOnce
# -- size
size: 10Gi