All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 37s
- 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
201 lines
5.3 KiB
YAML
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
|