feat(infra): deploy Stalwart mail server (internal-only, Tailnet access)
Add Stalwart all-in-one mail server: SMTP (cluster-internal relay for Mattermost), IMAP + HTTPS admin via Tailscale proxy. RocksDB storage, self-signed TLS (stalwart-tls secret). No public MX, no outbound SMTP. SCW TEM relay noted in NetworkPolicy comments for future use. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,20 @@ spec:
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
- name: imap-proxy
|
||||
image: alpine/socat:latest
|
||||
args:
|
||||
- "TCP-LISTEN:993,fork,reuseaddr"
|
||||
- "TCP:stalwart.foxhunt.svc.cluster.local:993"
|
||||
ports:
|
||||
- containerPort: 993
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 16Mi
|
||||
limits:
|
||||
cpu: 50m
|
||||
memory: 32Mi
|
||||
- name: tailscale
|
||||
image: ghcr.io/tailscale/tailscale:latest
|
||||
env:
|
||||
@@ -257,6 +271,26 @@ data:
|
||||
}
|
||||
}
|
||||
|
||||
# Stalwart Admin — mail.fxhnt.ai
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name mail.fxhnt.ai;
|
||||
|
||||
ssl_certificate /etc/nginx/certs/tls.crt;
|
||||
ssl_certificate_key /etc/nginx/certs/tls.key;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
location / {
|
||||
proxy_pass https://stalwart.foxhunt.svc.cluster.local:443;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_http_version 1.1;
|
||||
proxy_ssl_verify off;
|
||||
}
|
||||
}
|
||||
|
||||
# Mattermost — chat.fxhnt.ai
|
||||
server {
|
||||
listen 443 ssl;
|
||||
|
||||
45
infra/k8s/network-policies/stalwart.yaml
Normal file
45
infra/k8s/network-policies/stalwart.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: stalwart
|
||||
namespace: foxhunt
|
||||
labels:
|
||||
app.kubernetes.io/part-of: foxhunt
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: stalwart
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
# SMTP from cluster (Mattermost internal relay)
|
||||
- ports:
|
||||
- port: 25
|
||||
protocol: TCP
|
||||
from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: mattermost
|
||||
# IMAPS from Tailscale proxy
|
||||
- ports:
|
||||
- port: 993
|
||||
protocol: TCP
|
||||
# HTTPS admin panel from Tailscale proxy
|
||||
- ports:
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
egress:
|
||||
# DNS
|
||||
- ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
# No outbound SMTP — internal-only for now.
|
||||
# To add SCW TEM relay later:
|
||||
# - ports:
|
||||
# - port: 465
|
||||
# protocol: TCP
|
||||
# - port: 587
|
||||
# protocol: TCP
|
||||
153
infra/k8s/stalwart/stalwart.yaml
Normal file
153
infra/k8s/stalwart/stalwart.yaml
Normal file
@@ -0,0 +1,153 @@
|
||||
# Internal-only mail server. No public MX, no LoadBalancer.
|
||||
# SMTP: cluster-internal (Mattermost relay)
|
||||
# IMAP: via Tailscale proxy (port 993)
|
||||
# HTTPS admin: via Tailscale proxy (port 443 → mail.fxhnt.ai)
|
||||
# Outbound: not configured (add SCW TEM relay later if needed)
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: stalwart-data
|
||||
namespace: foxhunt
|
||||
labels:
|
||||
app.kubernetes.io/name: stalwart
|
||||
app.kubernetes.io/part-of: foxhunt
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: scw-bssd
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: stalwart-config
|
||||
namespace: foxhunt
|
||||
labels:
|
||||
app.kubernetes.io/name: stalwart
|
||||
app.kubernetes.io/part-of: foxhunt
|
||||
data:
|
||||
config.toml: |
|
||||
[server]
|
||||
hostname = "mail.fxhnt.ai"
|
||||
|
||||
[server.listener.smtp]
|
||||
bind = "[::]:25"
|
||||
protocol = "smtp"
|
||||
|
||||
[server.listener.imaptls]
|
||||
bind = "[::]:993"
|
||||
protocol = "imap"
|
||||
tls.implicit = true
|
||||
|
||||
[server.listener.https]
|
||||
bind = "[::]:443"
|
||||
protocol = "http"
|
||||
tls.implicit = true
|
||||
|
||||
[storage]
|
||||
data = "rocksdb"
|
||||
blob = "rocksdb"
|
||||
fts = "rocksdb"
|
||||
lookup = "rocksdb"
|
||||
directory = "internal"
|
||||
|
||||
[store.rocksdb]
|
||||
type = "rocksdb"
|
||||
path = "/opt/stalwart-mail/data"
|
||||
|
||||
[directory.internal]
|
||||
type = "internal"
|
||||
store = "rocksdb"
|
||||
|
||||
[certificate.default]
|
||||
cert = "/opt/stalwart-mail/tls/tls.crt"
|
||||
private-key = "/opt/stalwart-mail/tls/tls.key"
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: stalwart
|
||||
namespace: foxhunt
|
||||
labels:
|
||||
app.kubernetes.io/name: stalwart
|
||||
app.kubernetes.io/part-of: foxhunt
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: stalwart
|
||||
strategy:
|
||||
type: Recreate # RWO PVC
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: stalwart
|
||||
app.kubernetes.io/part-of: foxhunt
|
||||
spec:
|
||||
nodeSelector:
|
||||
k8s.scaleway.com/pool-name: platform
|
||||
containers:
|
||||
- name: stalwart
|
||||
image: stalwartlabs/mail-server:latest
|
||||
ports:
|
||||
- containerPort: 25
|
||||
name: smtp
|
||||
- containerPort: 993
|
||||
name: imaps
|
||||
- containerPort: 443
|
||||
name: https
|
||||
volumeMounts:
|
||||
- name: stalwart-data
|
||||
mountPath: /opt/stalwart-mail/data
|
||||
- name: stalwart-config
|
||||
mountPath: /opt/stalwart-mail/etc/config.toml
|
||||
subPath: config.toml
|
||||
- name: stalwart-tls
|
||||
mountPath: /opt/stalwart-mail/tls
|
||||
readOnly: true
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 25
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
volumes:
|
||||
- name: stalwart-data
|
||||
persistentVolumeClaim:
|
||||
claimName: stalwart-data
|
||||
- name: stalwart-config
|
||||
configMap:
|
||||
name: stalwart-config
|
||||
- name: stalwart-tls
|
||||
secret:
|
||||
secretName: stalwart-tls
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: stalwart
|
||||
namespace: foxhunt
|
||||
labels:
|
||||
app.kubernetes.io/name: stalwart
|
||||
app.kubernetes.io/part-of: foxhunt
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: stalwart
|
||||
ports:
|
||||
- port: 25
|
||||
targetPort: 25
|
||||
name: smtp
|
||||
- port: 993
|
||||
targetPort: 993
|
||||
name: imaps
|
||||
- port: 443
|
||||
targetPort: 443
|
||||
name: https
|
||||
Reference in New Issue
Block a user