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