anydrop/k8s/maddy.yml
ordinarthur 3f87debcf8
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 37s
fix(maddy): correct global hostname + run subcommand + dkim syntax
- add top-level `hostname $(hostname)` directive (required by maddy 0.8)
- invoke as `maddy -config … run` (global flag before subcommand)
- fix dkim syntax: `dkim DOMAIN SELECTOR` (no key-size positional arg —
  that was being parsed as a second selector, generating bogus keys)
- use bounce block on local_queue target instead of (local_routing) macro
2026-04-20 10:18:58 +02:00

201 lines
5.3 KiB
YAML

# ---------------------------------------------------------------------------
# Maddy — self-hosted SMTP server for AnyDrop magic-link emails.
#
# BEFORE FIRST DEPLOYMENT, the following DNS records must exist for
# anydrop.arthurbarre.fr (OVH):
# - A mail.anydrop.arthurbarre.fr → <cluster public IP>
# - MX 10 anydrop.arthurbarre.fr → mail.anydrop.arthurbarre.fr
# - TXT anydrop.arthurbarre.fr → "v=spf1 a mx ~all"
# - TXT _dmarc.anydrop.arthurbarre.fr → "v=DMARC1; p=none; rua=mailto:arthurbarre.js@gmail.com"
#
# The DKIM public key is generated by maddy on first start and stored in the
# PVC. Extract it once with:
# kubectl -n anydrop exec -it deploy/maddy -- cat /data/dkim_keys/anydrop.arthurbarre.fr_default.dns
# Then publish it as:
# TXT default._domainkey.anydrop.arthurbarre.fr → <public key record>
#
# Also make sure PTR (reverse DNS) for the public IP points to
# mail.anydrop.arthurbarre.fr — configure at the Proxmox / ISP level.
# ---------------------------------------------------------------------------
apiVersion: v1
kind: ConfigMap
metadata:
name: maddy-config
namespace: anydrop
data:
maddy.conf: |
# Global directives — both the top-level `hostname`/`tls` statements AND
# the macros of the same name are required. Macros are substituted into
# module blocks below; the bare directives configure the process.
$(hostname) = mail.anydrop.arthurbarre.fr
$(primary_domain) = anydrop.arthurbarre.fr
$(local_domains) = $(primary_domain)
hostname $(hostname)
tls off
# Outbound delivery pipeline ----------------------------------------------
target.queue local_queue {
target &remote_delivery
autogenerated_msg_domain $(primary_domain)
bounce {
destination postmaster $(local_domains) {
reject 550 5.0.0 "Bounces ignored — outbound only"
}
default_destination {
reject 550 5.0.0 "Bounces ignored — outbound only"
}
}
}
target.remote remote_delivery {
limits {
destination rate 20 1s
destination concurrency 10
}
mx_auth {
dane
mtasts {
cache fs
fs_dir mtasts_cache/
}
local_policy {
min_tls_level none
min_mx_level none
}
}
}
# SMTP submission listener — internal ClusterIP service only.
# No auth enforced: the service is not reachable outside the cluster
# network. If you ever expose this externally, add an `auth` block.
smtp tcp://0.0.0.0:587 {
hostname $(hostname)
tls off
limits {
all rate 100 1s
all concurrency 50
}
source $(local_domains) {
destination postmaster $(local_domains) {
reject 550 5.1.1 "Local delivery disabled"
}
default_destination {
modify {
dkim $(primary_domain) default
}
deliver_to &local_queue
}
}
default_source {
reject 501 5.1.8 "Non-local sender refused"
}
}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: maddy-data
namespace: anydrop
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: maddy
namespace: anydrop
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: maddy
template:
metadata:
labels:
app: maddy
spec:
containers:
- name: maddy
image: foxcpp/maddy:0.8
args: ["-config", "/etc/maddy/maddy.conf", "run"]
ports:
- containerPort: 587
name: submission
- containerPort: 25
name: smtp
volumeMounts:
- name: config
mountPath: /etc/maddy
- name: data
mountPath: /data
workingDir: /data
readinessProbe:
tcpSocket:
port: 587
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 587
initialDelaySeconds: 30
periodSeconds: 30
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "200m"
volumes:
- name: config
configMap:
name: maddy-config
- name: data
persistentVolumeClaim:
claimName: maddy-data
---
apiVersion: v1
kind: Service
metadata:
name: maddy
namespace: anydrop
spec:
type: ClusterIP
selector:
app: maddy
ports:
- name: submission
port: 587
targetPort: 587
---
# Optional: expose port 25 on a NodePort if you want maddy to also receive
# inbound mail (bounces, replies). For Phase 1 outbound-only, this can stay
# commented out — direct-to-MX delivery does not require inbound.
#
# apiVersion: v1
# kind: Service
# metadata:
# name: maddy-smtp
# namespace: anydrop
# spec:
# type: NodePort
# selector:
# app: maddy
# ports:
# - name: smtp
# port: 25
# targetPort: 25
# nodePort: 30025