This repository has no description
7.0 kB
251 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 # Password generation for mail accounts
73 clan.core.vars.generators.mailserver-lass = {
74 files."lass-mail-password" = { };
75 files."lass-mail-password-hash" = { };
76 runtimeInputs = with pkgs; [
77 coreutils
78 mkpasswd
79 pwgen
80 ];
81 script = ''
82 pwgen -s 32 1 > "$out/lass-mail-password"
83 mkpasswd -sm bcrypt < "$out/lass-mail-password" > "$out/lass-mail-password-hash"
84 '';
85 };
86
87 clan.core.vars.generators.mailserver-bot = {
88 files."bot-mail-password" = { };
89 files."bot-mail-password-hash" = { };
90 files."mail.env" = { };
91 runtimeInputs = with pkgs; [
92 coreutils
93 mkpasswd
94 pwgen
95 ];
96 script = ''
97 pwgen -s 32 1 > "$out/bot-mail-password"
98 mkpasswd -sm bcrypt < "$out/bot-mail-password" > "$out/bot-mail-password-hash"
99 cat > "$out/mail.env" << EOF
100 IMAP_USER=bot@lassul.us
101 IMAP_PASSWORD=$(cat "$out/bot-mail-password")
102 IMAP_HOST=localhost
103 EOF
104 '';
105 };
106
107 # Retiolum relay: allow krebs hosts to relay through postfix
108 services.postfix.settings.main.mynetworks = lib.mkForce (
109 [
110 "127.0.0.0/8"
111 "[::1]/128"
112 ]
113 ++ mynetworks
114 );
115
116 # ACME cert — reuse existing setup
117 security.acme.certs."mail.lassul.us" = {
118 group = "lasscert";
119 webroot = "/var/lib/acme/acme-challenge";
120 };
121 users.groups.lasscert.members = [
122 "dovecot2"
123 "postfix"
124 "nginx"
125 ];
126
127 # Firewall: SMTP(25), submission(587), submissions(465), IMAPS(993), ManageSieve(4190)
128 networking.firewall.allowedTCPPorts = [
129 25
130 587
131 465
132 993
133 4190
134 ];
135
136 # muchsync: lass user needs access to vmail for notmuch/muchsync
137 users.groups.virtualMail.members = [ "lass" ];
138 systemd.tmpfiles.rules = [
139 "L+ /home/lass/Maildir - - - - /var/vmail/lassul.us/lass/mail"
140 # dovecot pre-start sets /var/vmail to 02770 already; just fix subdirs
141 "z /var/vmail/lassul.us 2770 virtualMail virtualMail -"
142 "z /var/vmail/lassul.us/lass 2770 virtualMail virtualMail -"
143 "Z /var/vmail/lassul.us/lass/mail 2770 virtualMail virtualMail -"
144 ];
145
146 # Thunderbird autoconfig
147 services.nginx.virtualHosts."autoconfig.lassul.us" = {
148 forceSSL = true;
149 enableACME = true;
150 locations."= /mail/config-v1.1.xml".extraConfig = ''
151 default_type application/xml;
152 return 200 '<?xml version="1.0" encoding="UTF-8"?>
153 <clientConfig version="1.1">
154 <emailProvider id="lassul.us">
155 <domain>lassul.us</domain>
156 <displayName>lassul.us Mail</displayName>
157 <displayShortName>lassul.us</displayShortName>
158 <incomingServer type="imap">
159 <hostname>mail.lassul.us</hostname>
160 <port>993</port>
161 <socketType>SSL</socketType>
162 <authentication>password-cleartext</authentication>
163 <username>%EMAILADDRESS%</username>
164 </incomingServer>
165 <outgoingServer type="smtp">
166 <hostname>mail.lassul.us</hostname>
167 <port>465</port>
168 <socketType>SSL</socketType>
169 <authentication>password-cleartext</authentication>
170 <username>%EMAILADDRESS%</username>
171 </outgoingServer>
172 </emailProvider>
173 </clientConfig>';
174 '';
175 };
176
177 # Dovecot virtual mailbox + ACL-restricted shared namespace for bot
178 services.dovecot2.mailPlugins.globally.enable = [
179 "virtual"
180 "acl"
181 ];
182 services.dovecot2.extraConfig =
183 let
184 virtualDir = pkgs.linkFarm "dovecot-virtual" [
185 {
186 name = "Unread/dovecot-virtual";
187 path = pkgs.writeText "dovecot-virtual-unread" "*\n unseen\n";
188 }
189 ];
190 in
191 ''
192 plugin {
193 acl = vfile
194 acl_shared_dict = file:/var/vmail/shared-mailboxes.db
195 }
196
197 namespace shared {
198 type = shared
199 separator = .
200 prefix = shared.%%n.
201 location = maildir:/var/vmail/lassul.us/%%n/mail:LAYOUT=Maildir++
202 subscriptions = no
203 list = children
204 }
205
206 namespace virtual {
207 prefix = virtual.
208 separator = .
209 location = virtual:${virtualDir}:INDEX=/var/vmail/virtual-indexes/%u
210 subscriptions = no
211 mailbox Unread {
212 auto = subscribe
213 }
214 }
215 '';
216
217 # Write dovecot-acl files to grant bot read-only + flag access to lass's mail
218 systemd.services.dovecot-acl-sync = {
219 description = "Sync dovecot ACL files for bot shared access";
220 after = [ "dovecot.service" ];
221 serviceConfig = {
222 Type = "oneshot";
223 User = "virtualMail";
224 Group = "virtualMail";
225 };
226 # l=lookup, r=read, w=write-flags, s=write-seen (no insert/delete/expunge)
227 script = ''
228 acl_line="user=bot@lassul.us lrws"
229 find /var/vmail/lassul.us/lass/mail -type d | while IFS= read -r dir; do
230 acl_file="$dir/dovecot-acl"
231 if [ ! -f "$acl_file" ] || [ "$(cat "$acl_file")" != "$acl_line" ]; then
232 echo "$acl_line" > "$acl_file"
233 fi
234 done
235 '';
236 };
237 systemd.timers.dovecot-acl-sync = {
238 wantedBy = [ "timers.target" ];
239 timerConfig = {
240 OnBootSec = "1min";
241 OnUnitActiveSec = "1h";
242 };
243 };
244
245 # notmuch + muchsync + msmtp for CLI mail access
246 environment.systemPackages = [
247 self.packages.${pkgs.system}.notmuch
248 pkgs.muchsync
249 pkgs.msmtp
250 ];
251}