titan-iac/services/mailu/helmrelease.yaml

779 lines
30 KiB
YAML

# services/mailu/helmrelease.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: mailu
namespace: mailu-mailserver
spec:
interval: 30m
chart:
spec:
chart: mailu
version: 2.1.2
sourceRef:
kind: HelmRepository
name: mailu
namespace: flux-system
install:
remediation: { retries: 3 }
timeout: 10m
upgrade:
remediation:
retries: 3
remediateLastFailure: true
cleanupOnFail: true
timeout: 10m
values:
mailuVersion: "2024.06"
domain: bstein.dev
hostnames: [mail.bstein.dev]
domains:
- name: bstein.dev
enabled: true
dkim:
enabled: true
externalRelay:
host: "[smtp.postmarkapp.com]:587"
existingSecret: mailu-postmark-relay
usernameKey: relay-password
passwordKey: relay-password
timezone: Etc/UTC
subnet: 10.42.0.0/16
existingSecret: mailu-secret
tls:
outboundLevel: encrypt
externalDatabase:
enabled: true
type: postgresql
host: postgres-service.postgres.svc.cluster.local
port: 5432
database: mailu
username: mailu
existingSecret: mailu-db-secret
existingSecretUsernameKey: username
existingSecretPasswordKey: password
existingSecretDatabaseKey: database
initialAccount:
enabled: true
username: test
domain: bstein.dev
existingSecret: mailu-initial-account-secret
existingSecretPasswordKey: password
persistence:
accessModes: [ReadWriteMany]
size: 100Gi
storageClass: astreae
single_pvc: true
front:
hostnames: [mail.bstein.dev]
proxied: true
hostPort:
enabled: false
https:
enabled: false
external: false
forceHttps: false
externalService:
enabled: true
type: LoadBalancer
externalTrafficPolicy: Cluster
ports:
submission: true
nodePorts:
pop3: 30010
pop3s: 30011
imap: 30143
imaps: 30993
manageSieve: 30419
smtp: 30025
smtps: 30465
submission: 30587
logLevel: DEBUG
nodeSelector:
hardware: rpi4
admin:
logLevel: DEBUG
nodeSelector:
hardware: rpi4
podLivenessProbe:
enabled: true
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1
podReadinessProbe:
enabled: true
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1
extraEnvVars:
- name: FLASK_DEBUG
value: "1"
- name: ACCESSLOG
value: /dev/stdout
- name: ERRORLOG
value: /dev/stderr
- name: WEBROOT_REDIRECT
value: ""
- name: FORWARDED_ALLOW_IPS
value: 127.0.0.1,10.42.0.0/16
- name: DNS_RESOLVERS
value: 1.1.1.1,9.9.9.9
extraVolumes:
- name: unbound-config
configMap:
name: mailu-unbound
- name: unbound-run
emptyDir: {}
extraVolumeMounts:
- name: unbound-run
mountPath: /var/lib/unbound
extraContainers:
- name: unbound
image: docker.io/alpine:3.20
command: ["/bin/sh", "-c"]
args:
- |
while :; do
printf "nameserver 10.43.0.10\n" > /etc/resolv.conf
if apk add --no-cache unbound bind-tools; then
break
fi
echo "apk failed, retrying" >&2
sleep 10
done
cat >/etc/resolv.conf <<'EOF'
search mailu-mailserver.svc.cluster.local svc.cluster.local cluster.local
nameserver 127.0.0.1
EOF
unbound-anchor -a /var/lib/unbound/root.key || true
exec unbound -d -c /opt/unbound/etc/unbound/unbound.conf
ports:
- containerPort: 53
protocol: UDP
- containerPort: 53
protocol: TCP
volumeMounts:
- name: unbound-config
mountPath: /opt/unbound/etc/unbound
- name: unbound-run
mountPath: /var/lib/unbound
dnsPolicy: None
dnsConfig:
nameservers:
- 127.0.0.1
searches:
- mailu-mailserver.svc.cluster.local
- svc.cluster.local
- cluster.local
clamav:
image:
repository: clamav/clamav-debian
tag: "1.4"
logLevel: DEBUG
nodeSelector:
hardware: rpi5
resources:
requests:
cpu: 200m
memory: 1Gi
limits:
cpu: 500m
memory: 3Gi
livenessProbe:
enabled: false
initialDelaySeconds: 300
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1
startupProbe:
enabled: false
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 20
successThreshold: 1
readinessProbe:
enabled: false
initialDelaySeconds: 300
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1
dovecot:
logLevel: DEBUG
nodeSelector:
hardware: rpi4
oletools:
logLevel: DEBUG
nodeSelector:
hardware: rpi4
postfix:
logLevel: DEBUG
nodeSelector:
hardware: rpi4
overrides:
postfix.cf: |
mynetworks = 127.0.0.0/8 [::1]/128 10.42.0.0/16 10.43.0.0/16 192.168.22.0/24
recipient_canonical_maps = regexp:/overrides/recipient_canonical, ${podop}recipientmap
recipient_canonical_classes = envelope_recipient,header_recipient
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtpd_helo_restrictions = reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_security_options = noanonymous
smtpd_sasl_tls_security_options = noanonymous
smtpd_client_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_pipelining, reject_unknown_client_hostname
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, reject_non_fqdn_recipient, reject_unknown_recipient_domain
smtpd_relay_restrictions = permit_sasl_authenticated, reject_unauth_destination
smtpd_sender_restrictions = reject_non_fqdn_sender, reject_unknown_sender_domain, reject_sender_login_mismatch, reject_authenticated_sender_login_mismatch
smtpd_tls_auth_only = yes
smtpd_forbid_unauth_pipelining = yes
smtpd_client_connection_count_limit = 20
smtpd_client_connection_rate_limit = 30
smtpd_client_message_rate_limit = 100
smtpd_client_recipient_rate_limit = 200
smtpd_recipient_limit = 100
recipient_canonical: |
/^double-bounce@mail\.bstein\.dev$/ double-bounce@bstein.dev
podAnnotations:
bstein.dev/restarted-at: "2026-01-06T00:00:00Z"
redis:
enabled: true
architecture: standalone
logLevel: DEBUG
image:
repository: bitnamilegacy/redis
tag: 8.0.3-debian-12-r3
master:
nodeSelector:
hardware: rpi4
persistence:
enabled: true
accessModes: [ReadWriteMany]
size: 8Gi
storageClass: astreae
rspamd:
logLevel: DEBUG
nodeSelector:
hardware: rpi4
persistence:
accessModes: [ReadWriteOnce]
size: 8Gi
storageClass: astreae
tika:
logLevel: DEBUG
nodeSelector:
hardware: rpi4
global:
logLevel: DEBUG
storageClass: astreae
webmail:
enabled: false
nodeSelector:
hardware: rpi4
ingress:
enabled: false
ingressClassName: traefik
tls: true
existingSecret: mailu-certificates
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/service.serversscheme: https
traefik.ingress.kubernetes.io/service.serverstransport: mailu-transport@kubernetescrd
extraRules:
- host: mail.bstein.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: mailu-front
port:
number: 443
service:
ports:
smtp:
port: 25
targetPort: 25
smtps:
port: 465
targetPort: 465
submission:
port: 587
targetPort: 587
postRenderers:
- kustomize:
patches:
- target:
kind: Deployment
name: mailu-admin
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-admin
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "mailu-mailserver"
vault.hashicorp.com/agent-inject-secret-mailu-env.sh: "kv/data/atlas/mailu/mailu-secret"
vault.hashicorp.com/agent-inject-template-mailu-env.sh: |
{{ with secret "kv/data/atlas/mailu/mailu-secret" }}
export SECRET_KEY="{{ index .Data.data "secret-key" }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-db-secret" }}
export DB_PW="{{ .Data.data.password }}"
export ROUNDCUBE_DB_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-initial-account-secret" }}
export INITIAL_ADMIN_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/shared/postmark-relay" }}
{{- $access := index .Data.data "accesskey" -}}
{{- $secret := index .Data.data "secretkey" -}}
{{- if and $access $secret }}
export RELAYUSER="{{ $access }}"
export RELAYPASSWORD="{{ $secret }}"
{{- else }}
export RELAYUSER="{{ index .Data.data "apikey" }}"
export RELAYPASSWORD="{{ index .Data.data "apikey" }}"
{{- end }}
{{ end }}
spec:
serviceAccountName: mailu-vault-sync
automountServiceAccountToken: true
containers:
- name: admin
command:
- /entrypoint.sh
args:
- python3
- /start.py
env:
- name: SECRET_KEY
$patch: delete
- name: INITIAL_ADMIN_PW
$patch: delete
- name: DB_PW
$patch: delete
- name: RELAYUSER
$patch: delete
- name: RELAYPASSWORD
$patch: delete
- name: VAULT_ENV_FILE
value: /vault/secrets/mailu-env.sh
volumeMounts:
- name: mailu-vault-entrypoint
mountPath: /entrypoint.sh
subPath: vault-entrypoint.sh
volumes:
- name: mailu-vault-entrypoint
configMap:
name: mailu-vault-entrypoint
defaultMode: 493
- target:
kind: Deployment
name: mailu-front
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-front
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "mailu-mailserver"
vault.hashicorp.com/agent-inject-secret-mailu-env.sh: "kv/data/atlas/mailu/mailu-secret"
vault.hashicorp.com/agent-inject-template-mailu-env.sh: |
{{ with secret "kv/data/atlas/mailu/mailu-secret" }}
export SECRET_KEY="{{ index .Data.data "secret-key" }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-db-secret" }}
export DB_PW="{{ .Data.data.password }}"
export ROUNDCUBE_DB_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-initial-account-secret" }}
export INITIAL_ADMIN_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/shared/postmark-relay" }}
{{- $access := index .Data.data "accesskey" -}}
{{- $secret := index .Data.data "secretkey" -}}
{{- if and $access $secret }}
export RELAYUSER="{{ $access }}"
export RELAYPASSWORD="{{ $secret }}"
{{- else }}
export RELAYUSER="{{ index .Data.data "apikey" }}"
export RELAYPASSWORD="{{ index .Data.data "apikey" }}"
{{- end }}
{{ end }}
spec:
serviceAccountName: mailu-vault-sync
automountServiceAccountToken: true
containers:
- name: front
command:
- /entrypoint.sh
args:
- python3
- /start.py
env:
- name: SECRET_KEY
$patch: delete
- name: INITIAL_ADMIN_PW
$patch: delete
- name: DB_PW
$patch: delete
- name: RELAYUSER
$patch: delete
- name: RELAYPASSWORD
$patch: delete
- name: VAULT_ENV_FILE
value: /vault/secrets/mailu-env.sh
volumeMounts:
- name: mailu-vault-entrypoint
mountPath: /entrypoint.sh
subPath: vault-entrypoint.sh
volumes:
- name: mailu-vault-entrypoint
configMap:
name: mailu-vault-entrypoint
defaultMode: 493
- target:
kind: Deployment
name: mailu-postfix
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-postfix
spec:
strategy:
type: Recreate
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "mailu-mailserver"
vault.hashicorp.com/agent-inject-secret-mailu-env.sh: "kv/data/atlas/mailu/mailu-secret"
vault.hashicorp.com/agent-inject-template-mailu-env.sh: |
{{ with secret "kv/data/atlas/mailu/mailu-secret" }}
export SECRET_KEY="{{ index .Data.data "secret-key" }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-db-secret" }}
export DB_PW="{{ .Data.data.password }}"
export ROUNDCUBE_DB_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-initial-account-secret" }}
export INITIAL_ADMIN_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/shared/postmark-relay" }}
{{- $access := index .Data.data "accesskey" -}}
{{- $secret := index .Data.data "secretkey" -}}
{{- if and $access $secret }}
export RELAYUSER="{{ $access }}"
export RELAYPASSWORD="{{ $secret }}"
{{- else }}
export RELAYUSER="{{ index .Data.data "apikey" }}"
export RELAYPASSWORD="{{ index .Data.data "apikey" }}"
{{- end }}
{{ end }}
spec:
serviceAccountName: mailu-vault-sync
automountServiceAccountToken: true
containers:
- name: postfix
command:
- /entrypoint.sh
args:
- python3
- /start.py
env:
- name: SECRET_KEY
$patch: delete
- name: INITIAL_ADMIN_PW
$patch: delete
- name: DB_PW
$patch: delete
- name: RELAYUSER
$patch: delete
- name: RELAYPASSWORD
$patch: delete
- name: VAULT_ENV_FILE
value: /vault/secrets/mailu-env.sh
volumeMounts:
- name: mailu-vault-entrypoint
mountPath: /entrypoint.sh
subPath: vault-entrypoint.sh
volumes:
- name: mailu-vault-entrypoint
configMap:
name: mailu-vault-entrypoint
defaultMode: 493
- target:
kind: Deployment
name: mailu-dovecot
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-dovecot
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "mailu-mailserver"
vault.hashicorp.com/agent-inject-secret-mailu-env.sh: "kv/data/atlas/mailu/mailu-secret"
vault.hashicorp.com/agent-inject-template-mailu-env.sh: |
{{ with secret "kv/data/atlas/mailu/mailu-secret" }}
export SECRET_KEY="{{ index .Data.data "secret-key" }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-db-secret" }}
export DB_PW="{{ .Data.data.password }}"
export ROUNDCUBE_DB_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-initial-account-secret" }}
export INITIAL_ADMIN_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/shared/postmark-relay" }}
{{- $access := index .Data.data "accesskey" -}}
{{- $secret := index .Data.data "secretkey" -}}
{{- if and $access $secret }}
export RELAYUSER="{{ $access }}"
export RELAYPASSWORD="{{ $secret }}"
{{- else }}
export RELAYUSER="{{ index .Data.data "apikey" }}"
export RELAYPASSWORD="{{ index .Data.data "apikey" }}"
{{- end }}
{{ end }}
spec:
serviceAccountName: mailu-vault-sync
automountServiceAccountToken: true
containers:
- name: dovecot
command:
- /entrypoint.sh
args:
- python3
- /start.py
env:
- name: SECRET_KEY
$patch: delete
- name: INITIAL_ADMIN_PW
$patch: delete
- name: DB_PW
$patch: delete
- name: RELAYUSER
$patch: delete
- name: RELAYPASSWORD
$patch: delete
- name: VAULT_ENV_FILE
value: /vault/secrets/mailu-env.sh
volumeMounts:
- name: mailu-vault-entrypoint
mountPath: /entrypoint.sh
subPath: vault-entrypoint.sh
volumes:
- name: mailu-vault-entrypoint
configMap:
name: mailu-vault-entrypoint
defaultMode: 493
- target:
kind: Deployment
name: mailu-rspamd
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-rspamd
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "mailu-mailserver"
vault.hashicorp.com/agent-inject-secret-mailu-env.sh: "kv/data/atlas/mailu/mailu-secret"
vault.hashicorp.com/agent-inject-template-mailu-env.sh: |
{{ with secret "kv/data/atlas/mailu/mailu-secret" }}
export SECRET_KEY="{{ index .Data.data "secret-key" }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-db-secret" }}
export DB_PW="{{ .Data.data.password }}"
export ROUNDCUBE_DB_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-initial-account-secret" }}
export INITIAL_ADMIN_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/shared/postmark-relay" }}
{{- $access := index .Data.data "accesskey" -}}
{{- $secret := index .Data.data "secretkey" -}}
{{- if and $access $secret }}
export RELAYUSER="{{ $access }}"
export RELAYPASSWORD="{{ $secret }}"
{{- else }}
export RELAYUSER="{{ index .Data.data "apikey" }}"
export RELAYPASSWORD="{{ index .Data.data "apikey" }}"
{{- end }}
{{ end }}
spec:
serviceAccountName: mailu-vault-sync
automountServiceAccountToken: true
containers:
- name: rspamd
command:
- /entrypoint.sh
args:
- python3
- /start.py
env:
- name: SECRET_KEY
$patch: delete
- name: INITIAL_ADMIN_PW
$patch: delete
- name: DB_PW
$patch: delete
- name: RELAYUSER
$patch: delete
- name: RELAYPASSWORD
$patch: delete
- name: VAULT_ENV_FILE
value: /vault/secrets/mailu-env.sh
volumeMounts:
- name: mailu-vault-entrypoint
mountPath: /entrypoint.sh
subPath: vault-entrypoint.sh
volumes:
- name: mailu-vault-entrypoint
configMap:
name: mailu-vault-entrypoint
defaultMode: 493
- target:
kind: Deployment
name: mailu-oletools
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-oletools
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "mailu-mailserver"
vault.hashicorp.com/agent-inject-secret-mailu-env.sh: "kv/data/atlas/mailu/mailu-secret"
vault.hashicorp.com/agent-inject-template-mailu-env.sh: |
{{ with secret "kv/data/atlas/mailu/mailu-secret" }}
export SECRET_KEY="{{ index .Data.data "secret-key" }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-db-secret" }}
export DB_PW="{{ .Data.data.password }}"
export ROUNDCUBE_DB_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/mailu/mailu-initial-account-secret" }}
export INITIAL_ADMIN_PW="{{ .Data.data.password }}"
{{ end }}
{{ with secret "kv/data/atlas/shared/postmark-relay" }}
{{- $access := index .Data.data "accesskey" -}}
{{- $secret := index .Data.data "secretkey" -}}
{{- if and $access $secret }}
export RELAYUSER="{{ $access }}"
export RELAYPASSWORD="{{ $secret }}"
{{- else }}
export RELAYUSER="{{ index .Data.data "apikey" }}"
export RELAYPASSWORD="{{ index .Data.data "apikey" }}"
{{- end }}
{{ end }}
spec:
serviceAccountName: mailu-vault-sync
automountServiceAccountToken: true
containers:
- name: oletools
command:
- /entrypoint.sh
args:
- python3
- /start.py
env:
- name: SECRET_KEY
$patch: delete
- name: INITIAL_ADMIN_PW
$patch: delete
- name: DB_PW
$patch: delete
- name: RELAYUSER
$patch: delete
- name: RELAYPASSWORD
$patch: delete
- name: VAULT_ENV_FILE
value: /vault/secrets/mailu-env.sh
volumeMounts:
- name: mailu-vault-entrypoint
mountPath: /entrypoint.sh
subPath: vault-entrypoint.sh
volumes:
- name: mailu-vault-entrypoint
configMap:
name: mailu-vault-entrypoint
defaultMode: 493
- target:
kind: StatefulSet
name: mailu-clamav
patch: |-
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mailu-clamav
spec:
template:
spec:
containers:
- name: clamav
env:
- name: SECRET_KEY
$patch: delete
- name: INITIAL_ADMIN_PW
$patch: delete
- name: DB_PW
$patch: delete
- name: RELAYUSER
$patch: delete
- name: RELAYPASSWORD
$patch: delete
- target:
kind: Deployment
name: mailu-tika
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-tika
spec:
template:
spec:
containers:
- name: tika
env:
- name: SECRET_KEY
$patch: delete
- name: INITIAL_ADMIN_PW
$patch: delete
- name: DB_PW
$patch: delete
- name: RELAYUSER
$patch: delete
- name: RELAYPASSWORD
$patch: delete