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