mirror of
https://github.com/vhaudiquet/homeprod.git
synced 2026-07-21 21:17:08 +00:00
stalwart: deploy to kube
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
# TLS certificate for the Stalwart mail listeners (SMTP/IMAP/POP3 STARTTLS + implicit TLS)
|
||||||
|
# and the management WebUI hostname.
|
||||||
|
#
|
||||||
|
# Issued by cert-manager via the letsencrypt-production ClusterIssuer (Cloudflare DNS-01).
|
||||||
|
# The resulting Secret (mail-vhaudiquet-fr-tls) is consumed in two places:
|
||||||
|
# 1. The Stalwart HelmRelease mailTls block -> mounted into the pod + pushed to
|
||||||
|
# Stalwart by the tls-sync sidecar (JMAP) so SMTP/IMAP/POP3 present this cert.
|
||||||
|
# 2. The chart ingress (TLS termination is handled by Caddy in this cluster, so the
|
||||||
|
# ingress is TLS-less and only routes HTTP to the mgmt port).
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: mail-vhaudiquet-fr
|
||||||
|
namespace: stalwart
|
||||||
|
spec:
|
||||||
|
secretName: mail-vhaudiquet-fr-tls
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt-production
|
||||||
|
kind: ClusterIssuer
|
||||||
|
commonName: mail.vhaudiquet.fr
|
||||||
|
dnsNames:
|
||||||
|
- mail.vhaudiquet.fr
|
||||||
|
duration: 2160h # 90 days
|
||||||
|
renewBefore: 360h # 15 days before expiry
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: stalwart
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- repository.yaml
|
||||||
|
- certificate.yaml
|
||||||
|
- recovery-admin-secret.yaml
|
||||||
|
- release.yaml
|
||||||
|
secretGenerator:
|
||||||
|
- name: stalwart-values
|
||||||
|
files:
|
||||||
|
- values.yaml=values.yaml
|
||||||
|
configurations:
|
||||||
|
- kustomizeconfig.yaml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
nameReference:
|
||||||
|
- kind: Secret
|
||||||
|
version: v1
|
||||||
|
fieldSpecs:
|
||||||
|
- path: spec/valuesFrom/name
|
||||||
|
kind: HelmRelease
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: stalwart
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Recovery / bootstrap administrator credential for Stalwart.
|
||||||
|
#
|
||||||
|
# This Secret is referenced by:
|
||||||
|
# - recoveryAdmin.existingSecret in the HelmRelease, which injects it into the
|
||||||
|
# Stalwart pod (and the tls-sync sidecar) so the cert-manager certificate can
|
||||||
|
# be pushed to Stalwart via JMAP. Keep it present for the lifetime of the
|
||||||
|
# deployment; the tls-sync sidecar needs it on every cert renewal.
|
||||||
|
#
|
||||||
|
# The username/password here are ONLY the recovery account.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: stalwart-recovery-admin
|
||||||
|
namespace: stalwart
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: stalwart
|
||||||
|
app.kubernetes.io/component: recovery-admin
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
username: ENC[AES256_GCM,data:CYm0+Kw=,iv:UdgReqdb8GdoOfv1ZEW/7BAvY3vV88httj3W976eV0Y=,tag:PPrDQY/mo0RcJiz/cmxFSA==,type:str]
|
||||||
|
password: ENC[AES256_GCM,data:r0mPJ4qKIhAWDw==,iv:L5+OKTznQgDUgqttHwdW0X66vz+JPTqWdwYPGklCuj0=,tag:WNOj7ZjC6Lx76UlPsgSDhg==,type:str]
|
||||||
|
sops:
|
||||||
|
lastmodified: "2026-07-02T20:57:39Z"
|
||||||
|
mac: ENC[AES256_GCM,data:lIVOaMsFV9F1/yLvykA3LH5ukn9PIba6N8cE9RGdcFepM56tBYlY6fbkcKTtojyAkoYov6OCjgTsOkMtUzRdGES4Zti6zHB3cTPal8bJCf5cAfYnGFRcQaASIkVjNZkQSJ2pDRP4rZqioCHuj2/a6uJZ4hhtSpxrECYbpzD2Gk8=,iv:31fXHsWH1ZP00Od5DSxgYPuw87NqQNfWl4vFtoouIHk=,tag:u5cJn231VT2ZTmlcdpBz0g==,type:str]
|
||||||
|
pgp:
|
||||||
|
- created_at: "2026-07-02T20:57:39Z"
|
||||||
|
enc: |-
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
hQIMA7uy4qQr71wiAQ//fYBIypexVfQJn5BxVCRgZKRBcYw78XSoPjFYWFyk5F7I
|
||||||
|
UFRQKtdZOVVhgNHtFJOVyBvl1sAmmzAt3xq3L+Z5bvtrUfZ33tTp7ty+MmSmuutS
|
||||||
|
wquSh4NOHUYENGvo5pWsgVwQDj5bI2qUq4L92aedpB2XJycb7/Df7BWFKNMNgWdt
|
||||||
|
l+Hj9HEJWxmFjrz1MQJEL2r5WOx9/hK7uatbDTXVFPqyyW1aCM5XOkKErTuc3Avn
|
||||||
|
8tNNlvP1aqhI/2dHfOSZ1l0/PHhdi3RBmGa3BfAZUXEseBEesIK/EaJZXMjQYhF2
|
||||||
|
5tAPQ1irqg8LLv9mhrUD2mWaUigcuZ1EeyfZFvNtY/noF8fyZny1jra0sDe4CmQP
|
||||||
|
7uyP6CNRWPCikutUw45oxtw+/Us5Ld8xzJxhDEm0e+8FFiAK/SFSOx57JbjNzoCj
|
||||||
|
dqHU3UIzyp9VVZohqW7xdHOW64fgDI41BDKBsEiKraLnZUoXL2Lx0lnjxayJqVsD
|
||||||
|
2pX9D5YXzmtwFvUU5ZpcCzUWFu9MVxSfp6HdKvocGdtpSz7J23PQvHtkv20GxJYd
|
||||||
|
J45RYGgekuJ2EXc9kPaCuSex/0BKtYybOk5ANRv2yGeUZlc6EoW8/GZ4sHXPY6um
|
||||||
|
tt1DdIsMNMjAU68EgRkhmQnFAkZixJVdDR3u4MNXFUr0WdVIqzjFBavyrkcK6HjS
|
||||||
|
XAGhFNWdk5DNZ1ChGrMKEnYCZEB6XNkofa7S2xAomEoxyuCq7X2vbfBfqDQ/Rtcf
|
||||||
|
kSx0TpIOSHxefSFMduPz90rDqdZgUW0D3GND0PD5XRFFiJxxdrRYPux9MDMd
|
||||||
|
=Ef8e
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
fp: DC6910268E657FF70BA7EC289974494E76938DDC
|
||||||
|
encrypted_regex: ^(data|stringData|.*.key|.*.crt)$
|
||||||
|
version: 3.10.2
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: stalwart
|
||||||
|
namespace: stalwart
|
||||||
|
annotations:
|
||||||
|
homeprod.io/essential: "true"
|
||||||
|
spec:
|
||||||
|
interval: 1m
|
||||||
|
chart:
|
||||||
|
spec:
|
||||||
|
sourceRef:
|
||||||
|
kind: HelmRepository
|
||||||
|
name: stalwart-helm
|
||||||
|
namespace: stalwart
|
||||||
|
chart: stalwart
|
||||||
|
version: "0.7.5"
|
||||||
|
interval: 1m
|
||||||
|
valuesFrom:
|
||||||
|
- kind: Secret
|
||||||
|
name: stalwart-values
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: source.toolkit.fluxcd.io/v1
|
||||||
|
kind: HelmRepository
|
||||||
|
metadata:
|
||||||
|
name: stalwart-helm
|
||||||
|
namespace: stalwart
|
||||||
|
spec:
|
||||||
|
interval: 1m
|
||||||
|
url: https://kgrubb.github.io/stalwart-helm-chart
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
# Stalwart Mail Server - Helm values
|
||||||
|
#
|
||||||
|
# TLS for the mail listeners is sourced from cert-manager: the
|
||||||
|
# mail-vhaudiquet-fr-tls Secret (see certificate.yaml) is mounted into the pod
|
||||||
|
# and pushed to Stalwart by the chart's tls-sync sidecar over JMAP, which also
|
||||||
|
# calls ReloadTlsCertificates on every cert-manager renewal.
|
||||||
|
# Container image
|
||||||
|
image:
|
||||||
|
repository: stalwartlabs/stalwart
|
||||||
|
tag: v0.16.11
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
# Single-node RocksDB deployment.
|
||||||
|
replicaCount: 1
|
||||||
|
role: ""
|
||||||
|
pushShard: ""
|
||||||
|
# Recovery / bootstrap administrator.
|
||||||
|
# The Secret (stalwart-recovery-admin) is required for the lifetime of the
|
||||||
|
# deployment because the tls-sync sidecar authenticates to Stalwart's JMAP API
|
||||||
|
# with these credentials on every cert-manager renewal.
|
||||||
|
recoveryAdmin:
|
||||||
|
enabled: true
|
||||||
|
username: ENC[AES256_GCM,data:N8MwV7g=,iv:ipHgAWJ6J1C7TrugLLeJrV/IVk9xfC8DvCMBfIDyJMk=,tag:OwbcThuQhS/aBLH0tyewnA==,type:str]
|
||||||
|
password: ""
|
||||||
|
existingSecret: ENC[AES256_GCM,data:tGvczUq1+EDNR8CAFgLTMd+/N3v7Vig=,iv:XYrfBXYGexxUXskRJDSYx02D+nIYUmNtSCScplBenZM=,tag:/MZ5H6yuJlD7D6TN8CwVuQ==,type:str]
|
||||||
|
usernameKey: username
|
||||||
|
passwordKey: password
|
||||||
|
# Recovery mode suspends mail services and exposes only the management listener.
|
||||||
|
# Ship production values with this OFF.
|
||||||
|
recoveryMode:
|
||||||
|
enabled: true
|
||||||
|
port: 8080
|
||||||
|
logLevel: info
|
||||||
|
extraEnv: {}
|
||||||
|
extraSecretEnv: {}
|
||||||
|
# The bootstrap hook Job provisions a *fresh* domain/accounts/OIDC directory.
|
||||||
|
bootstrap:
|
||||||
|
enabled: false
|
||||||
|
# cert-manager TLS integration for the mail (SMTP/IMAP/POP3 STARTTLS + implicit
|
||||||
|
# TLS) listeners. The Secret below is produced by the Certificate in
|
||||||
|
# certificate.yaml (letsencrypt-production, Cloudflare DNS-01).
|
||||||
|
mailTls:
|
||||||
|
enabled: true
|
||||||
|
existingSecret: ENC[AES256_GCM,data:HkvfPP3KuHm5O81LrSA6Slq0iH88rg==,iv:nHnYsJ9uaRa+rKE20bVuxyruM7kepPX21Hhbl2uvuwo=,tag:YUxgSPhFyecT3ls2sEqEVA==,type:str]
|
||||||
|
certKey: tls.crt
|
||||||
|
privateKeyKey: tls.key
|
||||||
|
mountPath: /etc/stalwart/tls/ingress
|
||||||
|
hostname: mail.vhaudiquet.fr
|
||||||
|
domain: vhaudiquet.fr
|
||||||
|
# How often the sidecar re-checks the mounted cert for changes.
|
||||||
|
reloadIntervalSeconds: 300
|
||||||
|
# config.json contents - ONLY the DataStore object. All other settings
|
||||||
|
# (listeners, storage backends, domains, accounts, certs) live in the database
|
||||||
|
# and are managed via JMAP/WebUI.
|
||||||
|
config:
|
||||||
|
'@type': RocksDb
|
||||||
|
path: /var/lib/stalwart
|
||||||
|
# Main Service: ClusterIP. Used by the Ingress for the management WebUI (mgmt)
|
||||||
|
# and for in-cluster access. Mail L4 ports are exposed separately via mailService.
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
smtp: 25
|
||||||
|
smtps: 465
|
||||||
|
submission: 587
|
||||||
|
imap: 143
|
||||||
|
imaps: 993
|
||||||
|
pop3: 110
|
||||||
|
pop3s: 995
|
||||||
|
sieve: 4190
|
||||||
|
http: 80
|
||||||
|
https: 443
|
||||||
|
mgmt: 8080
|
||||||
|
# Dedicated LoadBalancer for SMTP/IMAP/POP3/Sieve (L4). HTTP/HTTPS/mgmt are NOT
|
||||||
|
# exposed here - the WebUI is reached via the Ingress (Traefik) below.
|
||||||
|
# The WAN router NAT for 25/465/587/993/995/4190 must be repointed to this IP.
|
||||||
|
mailService:
|
||||||
|
enabled: true
|
||||||
|
name: stalwart-mail
|
||||||
|
type: LoadBalancer
|
||||||
|
loadBalancerIP: 10.1.2.143
|
||||||
|
annotations: {}
|
||||||
|
# WebUI ingress. TLS is terminated by Caddy at the edge (wildcard cert), so the
|
||||||
|
# path is: public -> Caddy (TLS) -> Traefik (HTTP) -> this Ingress -> mgmt:8080.
|
||||||
|
# No TLS block here.
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: traefik
|
||||||
|
annotations: {}
|
||||||
|
hosts:
|
||||||
|
- host: mail.vhaudiquet.fr
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
portName: mgmt
|
||||||
|
tls: []
|
||||||
|
# Persistent volume for the RocksDB data directory.
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
storageClass: ""
|
||||||
|
size: 50Gi
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 1Gi
|
||||||
|
# Inherit the chart's secure defaults (UID/GID 2000, non-root, RuntimeDefault
|
||||||
|
# seccomp, drop ALL caps + add NET_BIND_SERVICE for privileged mail ports).
|
||||||
|
podSecurityContext: {}
|
||||||
|
containerSecurityContext: {}
|
||||||
|
sops:
|
||||||
|
lastmodified: "2026-07-02T20:57:39Z"
|
||||||
|
mac: ENC[AES256_GCM,data:8xnZW0A6hsysCDpfhfXnQ+NMc+VTSRwkMj4u2p2ERG8lj132dg5ErYG106jkQLXB3IkzxiAY9uyoquTpssVCOMemLRP9HgZmzqadI6kfpE2HZBHktgeY6MysF14RE4ep3naci0wVvpFiKv+sgr9QIbBmMnnN0pYwx5tASVFSgJc=,iv:KccW9I3LmxEmVfAnXjKz66FO1LgIO86zCFqp2/ulp4Q=,tag:xCNcgbHlttioSCvoZamwWw==,type:str]
|
||||||
|
pgp:
|
||||||
|
- created_at: "2026-07-02T20:57:39Z"
|
||||||
|
enc: |-
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
hQIMA7uy4qQr71wiAQ//fdCucpqT55/e76AABnVcBvwM3s34HK33n+6bvEwE2jTr
|
||||||
|
IPU6UXEZZzMCuGnSWSSy07hSX/0rhw6ETLIJmEPQkqOnxk6T3rcz9+8fLPapfLjI
|
||||||
|
sxFayU+Dw/s2rycErhrPTxgwLp3FBlnyEf/Zk22emUYiXnxZPoh4oXA8bjrMzSXj
|
||||||
|
tD5SVhvbHAT4WqTgG1cYgZ6XaDJ1eODqPXExJf6EekPlLAC/ohzgtPV44WTFj2YU
|
||||||
|
nVKouOGsc6ZUXkEwOm0HZcfJ5PuEOtCQsvD7BSKuTMbP5CUA9iuuBIhC8PmbCqxa
|
||||||
|
630fP2rDyVyaRucrjUgO7/N0AfaymYysSoRlqGPR/fgt0iJ/jeOu8POQMvK51IQL
|
||||||
|
Gtvp663bJpooCh3ALfvQsGmkpsdQX6QttNYUFyO8yRNDhbYKfT5Ha27a/xUdOEFA
|
||||||
|
Z/imx8fjjH/Anj1n30cy5w/9sZY4lmmxmf/zQMhJMGsTy5efWxbg8DYVOan/bgP3
|
||||||
|
1ZMLYNwLxg0CPRPrJuZbI2Iz2d7GertI22acyAF7OCGljwg4lL/SYPIs02cKYuKO
|
||||||
|
x2M0fpTo7kWlb5CWwDkEfwU14ZmLw8buAvYeG7KblG11kZtgvbWB1LqoXU5TMv4y
|
||||||
|
gZA82VdTEUepOYavRMOaPScnyhCImdC/1xLH6gQCrk5qWZYVAy7GEvLRM19pWtHS
|
||||||
|
XgHxDIdiNeTVD5OuVsxpke02osFrfT8q4ybGJF8KH0ykebuegGtKZuCdncTc5Hnk
|
||||||
|
wnJjZaHHqWGei7pGHWeN6FPQ0jSj8nMosQEpxs1X1UZhUG03t141nCgduTiQZhE=
|
||||||
|
=63dW
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
fp: DC6910268E657FF70BA7EC289974494E76938DDC
|
||||||
|
encrypted_regex: ^(password|value|ssh-key|api-key|user|username|privateKey|clientSecret|clientId|apiKey|extraArgs.*|.*Secret.*|extraEnvVars|.*SECRET.*|.*secret.*|key|.*Password|.*\.ya?ml)$
|
||||||
|
version: 3.10.2
|
||||||
@@ -22,6 +22,7 @@ resources:
|
|||||||
- home/zigbee2mqtt
|
- home/zigbee2mqtt
|
||||||
# Infrastructure
|
# Infrastructure
|
||||||
- infrastructure/authentik
|
- infrastructure/authentik
|
||||||
|
- infrastructure/mail/stalwart
|
||||||
# Personal
|
# Personal
|
||||||
- personal/linkwarden
|
- personal/linkwarden
|
||||||
- personal/notesnook
|
- personal/notesnook
|
||||||
|
|||||||
@@ -62,11 +62,10 @@ data:
|
|||||||
reverse_proxy traefik.traefik.svc.cluster.local.:80
|
reverse_proxy traefik.traefik.svc.cluster.local.:80
|
||||||
}
|
}
|
||||||
|
|
||||||
# Docker VM services (via Traefik)
|
# Docker VM services (via Docker Traefik)
|
||||||
@alexscript host alexscript.vhaudiquet.fr
|
@alexscript host alexscript.vhaudiquet.fr
|
||||||
@clips host clips.vhaudiquet.fr
|
@clips host clips.vhaudiquet.fr
|
||||||
@jellyfin host flix.vhaudiquet.fr
|
@jellyfin host flix.vhaudiquet.fr
|
||||||
@mail host mail.vhaudiquet.fr
|
|
||||||
|
|
||||||
handle @alexscript {
|
handle @alexscript {
|
||||||
reverse_proxy 10.1.2.212:80
|
reverse_proxy 10.1.2.212:80
|
||||||
@@ -77,8 +76,11 @@ data:
|
|||||||
handle @jellyfin {
|
handle @jellyfin {
|
||||||
reverse_proxy 10.1.2.212:80
|
reverse_proxy 10.1.2.212:80
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Stalwart WebUI (now in Kubernetes, via k8s Traefik)
|
||||||
|
@mail host mail.vhaudiquet.fr
|
||||||
handle @mail {
|
handle @mail {
|
||||||
reverse_proxy 10.1.2.212:80
|
reverse_proxy traefik.traefik.svc.cluster.local.:80
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user