Personal-use NixOS configuration
1{
2 domain,
3 email,
4 ssl,
5}:
6
7let
8 subdomain = "mx.${domain}";
9
10 tlsModule = import ./mta-sts.nix {
11 inherit domain ssl;
12 };
13
14 autoconfigModule = import ./autoconfig.nix {
15 inherit domain;
16
17 hosts = [
18 {
19 name = "autoconfig.${domain}";
20
21 inherit ssl;
22 }
23 ];
24 };
25in
26{
27 imports = [
28 ../databases/postgresql.nix
29 ./rspamd.nix
30
31 tlsModule
32 autoconfigModule
33 ];
34
35 services.maddy = {
36 enable = true;
37
38 primaryDomain = subdomain;
39
40 localDomains = [
41 "$(hostname)"
42 ];
43
44 tls = {
45 loader = "acme";
46 extraConfig = ''
47 email ${email}
48 agreed
49
50 hostname ${subdomain}
51 challenge dns-01
52
53 dns cloudflare {
54 api_token "{env:CF_API_TOKEN}"
55 }
56 '';
57 };
58
59 config = ''
60 auth.pass_table local_authdb {
61 table sql_table {
62 driver postgres
63 dsn "host=/run/postgresql dbname=maddy user=maddy"
64 table_name passwords
65 }
66 }
67
68 storage.imapsql local_mailboxes {
69 driver postgres
70 dsn "host=/run/postgresql dbname=maddy user=maddy"
71 }
72
73 table.chain local_rewrites {
74 optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
75
76 optional_step static {
77 entry postmaster postmaster@$(primary_domain)
78 }
79
80 optional_step file /etc/maddy/aliases
81 }
82
83 msgpipeline local_routing {
84 check {
85 rspamd {
86 api_path unix:/run/rspamd/rspamd.sock
87 }
88 }
89
90 destination postmaster $(local_domains) {
91 modify {
92 replace_rcpt &local_rewrites
93 }
94
95 deliver_to &local_mailboxes
96 }
97
98 default_destination {
99 reject 550 5.1.1 "User doesn't exist"
100 }
101 }
102
103 smtp tcp://0.0.0.0:25 {
104 limits {
105 all rate 25 1s
106 all concurrency 10
107 }
108
109 dmarc yes
110 max_message_size 25M
111 check {
112 require_mx_record
113 dkim
114 spf
115 }
116
117 source $(local_domains) {
118 reject 501 5.1.8 "Use Submission for outgoing SMTP"
119 }
120
121 default_source {
122 destination postmaster $(local_domains) {
123 deliver_to &local_routing
124 }
125
126 default_destination {
127 reject 550 5.1.1 "User doesn't exist"
128 }
129 }
130 }
131
132 submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
133 limits {
134 all rate 25 1s
135 }
136
137 auth &local_authdb
138
139 source $(local_domains) {
140 check {
141 authorize_sender {
142 prepare_email &local_rewrites
143 user_to_email identity
144 }
145 }
146
147 destination postmaster $(local_domains) {
148 deliver_to &local_routing
149 }
150
151 default_destination {
152 modify {
153 dkim $(primary_domain) $(local_domains) default
154 }
155
156 deliver_to &remote_queue
157 }
158 }
159
160 default_source {
161 reject 501 5.1.8 "Non-local sender domain"
162 }
163 }
164
165 imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
166 auth &local_authdb
167 storage &local_mailboxes
168 }
169
170 target.remote outbound_delivery {
171 limits {
172 destination rate 25 1s
173 destination concurrency 10
174 }
175
176 mx_auth {
177 dane
178 mtasts {
179 cache ram
180 }
181 local_policy {
182 min_tls_level encrypted
183 min_mx_level none
184 }
185 }
186 }
187
188 target.queue remote_queue {
189 target &outbound_delivery
190
191 autogenerated_msg_domain $(primary_domain)
192
193 bounce {
194 destination postmaster $(local_domains) {
195 deliver_to &local_routing
196 }
197
198 default_destination {
199 reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
200 }
201 }
202 }
203 '';
204 };
205
206 networking.firewall.allowedTCPPorts = [
207 25
208 587
209 465
210 143
211 993
212 ];
213
214 # Ensure creation of PostgreSQL database
215 services.postgresql = {
216 ensureUsers = [
217 {
218 name = "maddy";
219 ensureDBOwnership = true;
220 }
221 ];
222
223 ensureDatabases = [ "maddy" ];
224 };
225
226 # Configure rspamd
227 services.rspamd = {
228 locals."dkim_signing.conf".text = ''
229 selector = "default";
230 domain = "${subdomain}";
231 path = "/var/lib/maddy/dkim_keys/$domain_$selector.key";
232 '';
233 };
234
235 systemd.services.rspamd.serviceConfig.SupplementaryGroups = [ "maddy" ];
236}