1{
2 config,
3 lib,
4 self,
5 ...
6}:
7let
8 d = self.lib.data.mail;
9 cfg = config.services.stalwart-mail;
10 sec = config.age.secrets;
11 creds = config.services.stalwart-mail.credentials;
12 credsDir = "/run/credentials/stalwart-mail.service";
13 certDir = config.security.acme.certs."pyroxdev-mail".directory;
14 isAuthenticated = d: {
15 "if" = "!is_empty(authenticated_as)";
16 "then" = d;
17 };
18 otherwise = d: {
19 "else" = d;
20 };
21 ifThen = f: d: {
22 "if" = f;
23 "then" = d;
24 };
25 smSecret = {
26 owner = "stalwart-mail";
27 group = "stalwart-mail";
28 };
29in
30{
31 services.stalwart-mail = {
32 credentials = {
33 cert = "${certDir}/cert.pem";
34 key = "${certDir}/key.pem";
35 };
36 enable = true;
37 dataDir = "/var/lib/stalwart";
38 settings = {
39 tracer.stdout.level = "info";
40 authentication.fallback-admin = {
41 user = "fallback";
42 secret = "%{file:${sec.stalwart-fallback-admin-pw.path}}%";
43 };
44 config = {
45 local-keys = [
46 "asn.*"
47 "auth.*"
48 "authentication.*"
49 "auto-ban.*"
50 "calendar.*"
51 "certificate.*"
52 "changes.*"
53 "cluster.*"
54 "config.*"
55 "contacts.*"
56 "directory.*"
57 "http.*"
58 "imap.*"
59 "jmap.*"
60 "queue.*"
61 "report.*"
62 "resolver.*"
63 "server.*"
64 "session.*"
65 "signature.*"
66 "storage.*"
67 "store.*"
68 "tracer.*"
69 "webadmin.*"
70 "form.*"
71 "email.*"
72 "spam-filter.*"
73 ];
74 };
75 certificate = {
76 default = {
77 default = true;
78 cert = "%{file:${credsDir}/cert}%";
79 private-key = "%{file:${credsDir}/key}%";
80 subjects = [
81 "dav.pyrox.dev"
82 "mail.pyrox.dev"
83 "mta-sts.pyrox.dev"
84 "autoconfig.pyrox.dev"
85 "autodiscover.pyrox.dev"
86 ];
87 };
88 };
89 server = import ./server.nix { inherit d; };
90 # Use NixOS-generated certs now, since stalwart can't do it on its own
91 # (DeSec API Errors abound)
92 # acme = import ./acme.nix { inherit cfg sec; };
93 # HTTP Configuration
94 # https://stalw.art/docs/http/overview
95 http = {
96 url = "'https://${d.extUrl}'";
97 hsts = true;
98 rate-limit = {
99 account = "10000/1m";
100 };
101 };
102 # Disable HTTP Forms submission
103 # https://stalw.art/docs/http/form-submission
104 form.enable = false;
105 # DKIM Signatures
106 signature = import ./signature.nix { inherit sec; };
107 # Storage Settings
108 # https://stalw.art/docs/storage/overview
109 store = {
110 data = {
111 type = "rocksdb";
112 path = "${cfg.dataDir}/db";
113 purge.frequency = "0 3 *";
114 };
115 blob = {
116 type = "fs";
117 path = "${cfg.dataDir}/blobs";
118 depth = 2;
119 compression = "lz4";
120 purge.frequency = "0 4 *";
121 };
122 db.path = "${cfg.dataDir}/db2";
123 };
124 storage = {
125 data = "data";
126 blob = "blob";
127 fts = "data";
128 lookup = "data";
129 directory = "default";
130 };
131 directory = {
132 default = {
133 type = "internal";
134 store = "data";
135 };
136 };
137 # ASN/GeoIP Lookups
138 # https://stalw.art/docs/server/asn
139 asn = {
140 type = "dns";
141 separator = "|";
142 zone.ipv4 = "origin.asn.cymru.com";
143 zone.ipv6 = "origin6.asn.cymru.com";
144 index.asn = 0;
145 index.asn-name = 1;
146 index.country = 2;
147 };
148 auto-ban = import ./auto-ban.nix;
149 # JMAP Settings
150 # https://stalw.art/docs/email/jmap
151 jmap = {
152 mailbox.max-depth = 10;
153 mailbox.max-name-length = 255;
154 # 50 MB
155 email.max-attachment-size = 50 * 1000 * 1000;
156 # 75 MB
157 email.max-size = 75 * 1000 * 1000;
158 email.parse.max-items = 10;
159 };
160 imap = import ./imap.nix;
161 # Maintainance
162 # https://stalw.art/docs/email/maintenance
163 email.auto-expunge = "180d";
164 changes.max-history = 10000;
165 session = import ./session.nix { inherit isAuthenticated otherwise ifThen; };
166 queue = import ./queue.nix { inherit d ifThen otherwise; };
167 # DNS Settings
168 # https://stalw.art/docs/mta/outbound/dns
169 resolver = {
170 custom = [
171 "tls://dns11.quad9.net"
172 "tcp://1.1.1.1"
173 ];
174 concurrency = 2;
175 preserve-intermediates = true;
176 timeout = "5s";
177 attempts = 3;
178 edns = true;
179 };
180 report = import ./report.nix { inherit d; };
181 calendar = import ./calendar.nix;
182 # Authentication
183 auth = import ./auth.nix { inherit ifThen otherwise; };
184 # Contacts
185 # https://stalw.art/docs/collaboration/contact
186 contacts = {
187 # 512 KiB
188 max-size = 524288;
189 default.href-name = "default";
190 default.display-name = "Contacts";
191 };
192 # Spam Filtering
193 # https://stalw.art/docs/spamfilter/overview
194 spam-filter = {
195 card-is-ham = true;
196 };
197 };
198 };
199 systemd.services.stalwart-mail.serviceConfig = {
200 Restart = lib.mkForce "always";
201 RestartSec = lib.mkForce 1;
202 };
203 age.secrets = {
204 stalwart-secret-rsa = smSecret // {
205 file = ../../secrets/stalwart-secret-rsa.age;
206 };
207 stalwart-secret-ed25519 = smSecret // {
208 file = ../../secrets/stalwart-secret-ed25519.age;
209 };
210 stalwart-desec-token = smSecret // {
211 file = ../../secrets/stalwart-desec-token.age;
212 };
213 stalwart-fallback-admin-pw = smSecret // {
214 file = ../../secrets/stalwart-fallback-admin-pw.age;
215 };
216 };
217}