This repository has no description
8.1 kB
276 lines
1{
2 self,
3 config,
4 lib,
5 pkgs,
6 ...
7}:
8let
9 relayHosts = map (host: host.nets.retiolum.ip6.addr) [
10 config.krebs.hosts.aergia
11 config.krebs.hosts.ignavia
12 config.krebs.hosts.coaxmetal
13 config.krebs.hosts.green
14 config.krebs.hosts.mors
15 ];
16
17 mynetworks = map (ip: "[${ip}]") relayHosts;
18in
19{
20 imports = [
21 self.inputs.nixos-mailserver.nixosModules.default
22 ];
23
24 mailserver = {
25 enable = true;
26 fqdn = "mail.lassul.us";
27 domains = [ "lassul.us" ];
28
29 loginAccounts = {
30 "lass@lassul.us" = {
31 hashedPasswordFile =
32 config.clan.core.vars.generators.mailserver-lass.files."lass-mail-password-hash".path;
33 aliases = [
34 "postmaster@lassul.us"
35 "root@lassul.us"
36 "abuse@lassul.us"
37 "hostmaster@lassul.us"
38 "webmaster@lassul.us"
39 "security@lassul.us"
40 "noc@lassul.us"
41 "mailer-daemon@lassul.us"
42 "nobody@lassul.us"
43 "lassulus@lassul.us"
44 ];
45 catchAll = [ "lassul.us" ];
46 };
47 "bot@lassul.us" = {
48 hashedPasswordFile =
49 config.clan.core.vars.generators.mailserver-bot.files."bot-mail-password-hash".path;
50 };
51 };
52
53 # TLS via existing ACME cert
54 x509.useACMEHost = "mail.lassul.us";
55
56 # DKIM — auto-managed by module
57 dkimSigning = true;
58 dkimSelector = "mail";
59
60 # ManageSieve: lets mail clients manage sieve rules dynamically (port 4190)
61 enableManageSieve = true;
62
63 # Full-text search for IMAP clients
64 fullTextSearch = {
65 enable = true;
66 memoryLimit = 500;
67 };
68
69 stateVersion = 3;
70 };
71
72 # nixos-mailserver mkForces virtualMail without homeMode; match its priority
73 users.users.virtualMail = lib.mkOverride 50 { homeMode = "2770"; };
74
75 # POSIX ACL default ensures new files inherit lass access (needed for muchsync hardlinks)
76 systemd.services.dovecot.serviceConfig.ExecStartPost = [
77 "+${pkgs.acl}/bin/setfacl -R -m u:lass:rwX /var/vmail/lassul.us/lass/mail"
78 "+${pkgs.acl}/bin/setfacl -R -d -m u:lass:rwX /var/vmail/lassul.us/lass/mail"
79 ];
80
81 # Password generation for mail accounts
82 clan.core.vars.generators.mailserver-lass = {
83 files."lass-mail-password" = { };
84 files."lass-mail-password-hash" = { };
85 runtimeInputs = with pkgs; [
86 coreutils
87 mkpasswd
88 pwgen
89 ];
90 script = ''
91 pwgen -s 32 1 > "$out/lass-mail-password"
92 mkpasswd -sm bcrypt < "$out/lass-mail-password" > "$out/lass-mail-password-hash"
93 '';
94 };
95
96 clan.core.vars.generators.mailserver-bot = {
97 files."bot-mail-password" = { };
98 files."bot-mail-password-hash" = { };
99 files."mail.env" = { };
100 runtimeInputs = with pkgs; [
101 coreutils
102 mkpasswd
103 pwgen
104 ];
105 script = ''
106 pwgen -s 32 1 > "$out/bot-mail-password"
107 mkpasswd -sm bcrypt < "$out/bot-mail-password" > "$out/bot-mail-password-hash"
108 cat > "$out/mail.env" << EOF
109 IMAP_USER=bot@lassul.us
110 IMAP_PASSWORD=$(cat "$out/bot-mail-password")
111 IMAP_HOST=localhost
112 EOF
113 '';
114 };
115
116 # Retiolum relay: allow krebs hosts to relay through postfix
117 services.postfix.settings.main.mynetworks = lib.mkForce (
118 [
119 "127.0.0.0/8"
120 "[::1]/128"
121 ]
122 ++ mynetworks
123 );
124
125 # ACME cert — reuse existing setup
126 security.acme.certs."mail.lassul.us" = {
127 group = "lasscert";
128 webroot = "/var/lib/acme/acme-challenge";
129 };
130 users.groups.lasscert.members = [
131 "dovecot2"
132 "postfix"
133 "nginx"
134 ];
135
136 # Firewall: SMTP(25), submission(587), submissions(465), IMAPS(993), ManageSieve(4190)
137 networking.firewall.allowedTCPPorts = [
138 25
139 587
140 465
141 993
142 4190
143 ];
144
145 # muchsync: lass user needs access to vmail for notmuch/muchsync
146 users.groups.virtualMail.members = [ "lass" ];
147 systemd.tmpfiles.rules = [
148 "L+ /home/lass/Maildir - - - - /var/vmail/lassul.us/lass/mail"
149 "z /var/vmail/lassul.us 2770 virtualMail virtualMail -"
150 "z /var/vmail/lassul.us/lass 2770 virtualMail virtualMail -"
151 "d /var/vmail/lassul.us/lass/mail 2770 virtualMail virtualMail -"
152 ];
153
154 # Thunderbird autoconfig
155 services.nginx.virtualHosts."autoconfig.lassul.us" = {
156 forceSSL = true;
157 enableACME = true;
158 locations."= /mail/config-v1.1.xml".extraConfig = ''
159 default_type application/xml;
160 return 200 '<?xml version="1.0" encoding="UTF-8"?>
161 <clientConfig version="1.1">
162 <emailProvider id="lassul.us">
163 <domain>lassul.us</domain>
164 <displayName>lassul.us Mail</displayName>
165 <displayShortName>lassul.us</displayShortName>
166 <incomingServer type="imap">
167 <hostname>mail.lassul.us</hostname>
168 <port>993</port>
169 <socketType>SSL</socketType>
170 <authentication>password-cleartext</authentication>
171 <username>%EMAILADDRESS%</username>
172 </incomingServer>
173 <outgoingServer type="smtp">
174 <hostname>mail.lassul.us</hostname>
175 <port>465</port>
176 <socketType>SSL</socketType>
177 <authentication>password-cleartext</authentication>
178 <username>%EMAILADDRESS%</username>
179 </outgoingServer>
180 </emailProvider>
181 </clientConfig>';
182 '';
183 };
184
185 # Dovecot virtual mailbox + ACL-restricted shared namespace for bot
186 services.dovecot2.mailPlugins.globally.enable = [
187 "virtual"
188 "acl"
189 ];
190 # Provision virtual mailbox config to a writable directory (nix store is read-only,
191 # and the ACL plugin needs to write dovecot-acl-list next to the virtual config)
192 systemd.tmpfiles.settings."dovecot-virtual" = {
193 "/var/vmail/virtual-config".d = {
194 user = "virtualMail";
195 group = "virtualMail";
196 mode = "0750";
197 };
198 "/var/vmail/virtual-config/Unread".d = {
199 user = "virtualMail";
200 group = "virtualMail";
201 mode = "0750";
202 };
203 "/var/vmail/virtual-config/Unread/dovecot-virtual".f = {
204 user = "virtualMail";
205 group = "virtualMail";
206 mode = "0640";
207 argument = "*\n-notmuch\n-fts-flatcurve\n unseen\n";
208 };
209 };
210
211 services.dovecot2.extraConfig = ''
212 plugin {
213 acl = vfile
214 acl_shared_dict = file:/var/vmail/shared-mailboxes.db
215 }
216
217 namespace shared {
218 type = shared
219 separator = .
220 prefix = shared.%%n.
221 location = maildir:/var/vmail/lassul.us/%%n/mail:LAYOUT=Maildir++
222 subscriptions = no
223 list = children
224 }
225
226 namespace virtual {
227 prefix = virtual.
228 separator = .
229 location = virtual:/var/vmail/virtual-config:INDEX=/var/vmail/virtual-indexes/%u
230 subscriptions = no
231 mailbox Unread {
232 auto = subscribe
233 }
234 }
235 '';
236
237 # Write dovecot-acl files to grant bot read-only + flag access to lass's mail
238 systemd.services.dovecot-acl-sync = {
239 description = "Sync dovecot ACL files for bot shared access";
240 after = [ "dovecot.service" ];
241 serviceConfig = {
242 Type = "oneshot";
243 User = "virtualMail";
244 Group = "virtualMail";
245 };
246 # l=lookup, r=read, w=write-flags, s=write-seen (no insert/delete/expunge)
247 script = ''
248 acl_line="user=bot@lassul.us lrws"
249 # Only write ACL in mailbox root dirs, skip cur/new/tmp/notmuch subdirs
250 find /var/vmail/lassul.us/lass/mail -type d \
251 -not -name cur -not -name new -not -name tmp \
252 -not -path '*/.notmuch/*' -not -name .notmuch \
253 -not -path '*/fts-flatcurve/*' -not -name fts-flatcurve \
254 | while IFS= read -r dir; do
255 acl_file="$dir/dovecot-acl"
256 if [ ! -f "$acl_file" ] || [ "$(cat "$acl_file")" != "$acl_line" ]; then
257 echo "$acl_line" > "$acl_file"
258 fi
259 done
260 '';
261 };
262 systemd.timers.dovecot-acl-sync = {
263 wantedBy = [ "timers.target" ];
264 timerConfig = {
265 OnBootSec = "1min";
266 OnUnitActiveSec = "1h";
267 };
268 };
269
270 # notmuch + muchsync + msmtp for CLI mail access
271 environment.systemPackages = [
272 self.packages.${pkgs.system}.notmuch
273 pkgs.muchsync
274 pkgs.msmtp
275 ];
276}