This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

superconfig / configs / mailserver.nix
12 kB 360 lines
1{ 2 self, 3 config, 4 lib, 5 pkgs, 6 ... 7}: 8let 9 relayHosts = map (name: self.inputs.kartei.hosts.${name}.nets.retiolum.ip6.addr) [ 10 "aergia" 11 "ignavia" 12 "coaxmetal" 13 "green" 14 "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 # neoprism.r: retiolum-internal mail (cron, root@neoprism.r from other 28 # krebs hosts) lands in lass's mailbox via catchAll. 29 domains = [ 30 "lassul.us" 31 "neoprism.r" 32 ]; 33 34 loginAccounts = { 35 "lass@lassul.us" = { 36 hashedPasswordFile = 37 config.clan.core.vars.generators.mailserver-lass.files."lass-mail-password-hash".path; 38 aliases = [ 39 "postmaster@lassul.us" 40 "root@lassul.us" 41 "abuse@lassul.us" 42 "hostmaster@lassul.us" 43 "webmaster@lassul.us" 44 "security@lassul.us" 45 "noc@lassul.us" 46 "mailer-daemon@lassul.us" 47 "nobody@lassul.us" 48 "lassulus@lassul.us" 49 ]; 50 catchAll = [ 51 "lassul.us" 52 "neoprism.r" 53 ]; 54 }; 55 "bot@lassul.us" = { 56 hashedPasswordFile = 57 config.clan.core.vars.generators.mailserver-bot.files."bot-mail-password-hash".path; 58 }; 59 }; 60 61 # TLS via existing ACME cert 62 x509.useACMEHost = "mail.lassul.us"; 63 64 # DKIM — auto-managed by module 65 dkimSigning = true; 66 dkimSelector = "mail"; 67 68 # ManageSieve: lets mail clients manage sieve rules dynamically (port 4190) 69 enableManageSieve = true; 70 71 # Full-text search for IMAP clients 72 fullTextSearch = { 73 enable = true; 74 memoryLimit = 500; 75 }; 76 77 # 5: Sieve scripts now live in each user's Dovecot home (~/sieve) instead of 78 # the old global /var/sieve. Before deploying this bump, run the migration 79 # against /var/sieve on neoprism (see migrations/nixos-mailserver-migration-05.py 80 # in the nixos-mailserver input, or the migration docs). 81 stateVersion = 5; 82 83 # virtual.All mailbox needs more memory to sync all folders 84 imapMemoryLimit = 1024; 85 }; 86 87 # nixos-mailserver mkForces virtualMail without homeMode; match its priority 88 users.users.virtualMail = lib.mkOverride 50 { homeMode = "2770"; }; 89 90 # POSIX ACL default ensures new files inherit lass access (needed for muchsync hardlinks) 91 systemd.services.dovecot.serviceConfig.ExecStartPost = [ 92 "+${pkgs.acl}/bin/setfacl -R -m u:lass:rwX /var/vmail/lassul.us/lass/mail" 93 "+${pkgs.acl}/bin/setfacl -R -d -m u:lass:rwX /var/vmail/lassul.us/lass/mail" 94 ]; 95 96 # Password generation for mail accounts 97 clan.core.vars.generators.mailserver-lass = { 98 files."lass-mail-password" = { }; 99 files."lass-mail-password-hash" = { }; 100 runtimeInputs = with pkgs; [ 101 coreutils 102 mkpasswd 103 pwgen 104 ]; 105 script = '' 106 pwgen -s 32 1 > "$out/lass-mail-password" 107 mkpasswd -sm bcrypt < "$out/lass-mail-password" > "$out/lass-mail-password-hash" 108 ''; 109 }; 110 111 clan.core.vars.generators.mailserver-bot = { 112 files."bot-mail-password" = { }; 113 files."bot-mail-password-hash" = { }; 114 files."mail.env" = { }; 115 runtimeInputs = with pkgs; [ 116 coreutils 117 mkpasswd 118 pwgen 119 ]; 120 script = '' 121 pwgen -s 32 1 > "$out/bot-mail-password" 122 mkpasswd -sm bcrypt < "$out/bot-mail-password" > "$out/bot-mail-password-hash" 123 cat > "$out/mail.env" << EOF 124 IMAP_USER=bot@lassul.us 125 IMAP_PASSWORD=$(cat "$out/bot-mail-password") 126 IMAP_HOST=localhost 127 EOF 128 ''; 129 }; 130 131 # Retiolum relay: allow krebs hosts to relay through postfix 132 services.postfix.settings.main.mynetworks = lib.mkForce ( 133 [ 134 "127.0.0.0/8" 135 "[::1]/128" 136 ] 137 ++ mynetworks 138 ); 139 140 # ACME cert — reuse existing setup 141 security.acme.certs."mail.lassul.us" = { 142 group = "lasscert"; 143 webroot = "/var/lib/acme/acme-challenge"; 144 }; 145 users.groups.lasscert.members = [ 146 "dovecot2" 147 "postfix" 148 "nginx" 149 ]; 150 151 # Firewall: SMTP(25), submission(587), submissions(465), IMAPS(993), ManageSieve(4190) 152 networking.firewall.allowedTCPPorts = [ 153 25 154 587 155 465 156 993 157 4190 158 ]; 159 160 # muchsync: lass user needs access to vmail for notmuch/muchsync 161 users.groups.virtualMail.members = [ "lass" ]; 162 systemd.tmpfiles.rules = [ 163 "L+ /home/lass/Maildir - - - - /var/vmail/lassul.us/lass/mail" 164 "z /var/vmail/lassul.us 2770 virtualMail virtualMail -" 165 "z /var/vmail/lassul.us/lass 2770 virtualMail virtualMail -" 166 "d /var/vmail/lassul.us/lass/mail 2770 virtualMail virtualMail -" 167 ]; 168 169 # Thunderbird autoconfig 170 services.nginx.virtualHosts."autoconfig.lassul.us" = { 171 forceSSL = true; 172 enableACME = true; 173 locations."= /mail/config-v1.1.xml".extraConfig = '' 174 default_type application/xml; 175 return 200 '<?xml version="1.0" encoding="UTF-8"?> 176 <clientConfig version="1.1"> 177 <emailProvider id="lassul.us"> 178 <domain>lassul.us</domain> 179 <displayName>lassul.us Mail</displayName> 180 <displayShortName>lassul.us</displayShortName> 181 <incomingServer type="imap"> 182 <hostname>mail.lassul.us</hostname> 183 <port>993</port> 184 <socketType>SSL</socketType> 185 <authentication>password-cleartext</authentication> 186 <username>%EMAILADDRESS%</username> 187 </incomingServer> 188 <outgoingServer type="smtp"> 189 <hostname>mail.lassul.us</hostname> 190 <port>465</port> 191 <socketType>SSL</socketType> 192 <authentication>password-cleartext</authentication> 193 <username>%EMAILADDRESS%</username> 194 </outgoingServer> 195 </emailProvider> 196 </clientConfig>'; 197 ''; 198 }; 199 200 # Provision virtual mailbox config to a writable directory (nix store is read-only, 201 # and the ACL plugin needs to write dovecot-acl-list next to the virtual config) 202 systemd.tmpfiles.settings."dovecot-virtual" = { 203 "/var/vmail/virtual-config".d = { 204 user = "virtualMail"; 205 group = "virtualMail"; 206 mode = "0750"; 207 }; 208 "/var/vmail/virtual-config/Unread".d = { 209 user = "virtualMail"; 210 group = "virtualMail"; 211 mode = "0750"; 212 }; 213 "/var/vmail/virtual-config/Unread/dovecot-virtual".f = { 214 user = "virtualMail"; 215 group = "virtualMail"; 216 mode = "0640"; 217 argument = "*\n-notmuch\n-fts-flatcurve\n unseen\n"; 218 }; 219 "/var/vmail/virtual-config/All".d = { 220 user = "virtualMail"; 221 group = "virtualMail"; 222 mode = "0750"; 223 }; 224 "/var/vmail/virtual-config/All/dovecot-virtual".f = { 225 user = "virtualMail"; 226 group = "virtualMail"; 227 mode = "0640"; 228 argument = "*\n-notmuch\n-fts-flatcurve\n all\n"; 229 }; 230 }; 231 232 # Dovecot virtual mailbox + ACL-restricted shared namespace for bot 233 services.dovecot2.settings = { 234 mail_plugins = { 235 virtual = true; 236 acl = true; 237 }; 238 239 # ACL plugin (dovecot 2.4): `acl_driver` is top-level; per-identifier 240 # default rights live in `acl <identifier> { acl_rights = ...; }` named 241 # filters where <identifier> is owner/anyone/authenticated/user=.../etc. 242 # `acl_shared_dict` was removed; the bot accesses shared mail via 243 # explicit subscriptions. 244 acl_driver = "vfile"; 245 "acl owner" = { 246 acl_rights = "lrwstipekxa"; 247 }; 248 249 # In dovecot 2.4 the per-namespace `location = "driver:path:opts"` was 250 # replaced by separate mail_driver / mail_path / mail_index_path settings. 251 "namespace shared" = { 252 type = "shared"; 253 separator = "."; 254 prefix = "shared.%{user | username}."; 255 mail_driver = "maildir"; 256 mail_path = "/var/vmail/lassul.us/%{user | username}/mail"; 257 subscriptions = false; 258 list = "children"; 259 }; 260 261 "namespace virtual" = { 262 prefix = "virtual."; 263 separator = "."; 264 mail_driver = "virtual"; 265 mail_path = "/var/vmail/virtual-config"; 266 mail_index_path = "/var/vmail/virtual-indexes/%{user}"; 267 # virtual storage doesn't support maildir++ layout, override the global 268 mailbox_list_layout = "fs"; 269 subscriptions = false; 270 "mailbox Unread".auto = "subscribe"; 271 "mailbox All".auto = "subscribe"; 272 }; 273 }; 274 275 # Write dovecot-acl files to grant bot read-only + flag access to lass's mail 276 systemd.services.dovecot-acl-sync = { 277 description = "Sync dovecot ACL files for bot shared access"; 278 after = [ "dovecot.service" ]; 279 serviceConfig = { 280 Type = "oneshot"; 281 User = "virtualMail"; 282 Group = "virtualMail"; 283 }; 284 # l=lookup, r=read, w=write-flags, s=write-seen (no insert/delete/expunge) 285 script = '' 286 acl_line="user=bot@lassul.us lrws" 287 # Only write ACL in mailbox root dirs, skip cur/new/tmp/notmuch subdirs 288 find /var/vmail/lassul.us/lass/mail -type d \ 289 -not -name cur -not -name new -not -name tmp \ 290 -not -path '*/.notmuch/*' -not -name .notmuch \ 291 -not -path '*/fts-flatcurve/*' -not -name fts-flatcurve \ 292 | while IFS= read -r dir; do 293 acl_file="$dir/dovecot-acl" 294 if [ ! -f "$acl_file" ] || [ "$(cat "$acl_file")" != "$acl_line" ]; then 295 echo "$acl_line" > "$acl_file" 296 fi 297 done 298 ''; 299 }; 300 systemd.timers.dovecot-acl-sync = { 301 wantedBy = [ "timers.target" ]; 302 timerConfig = { 303 OnBootSec = "1min"; 304 OnUnitActiveSec = "1h"; 305 }; 306 }; 307 308 # notmuch + muchsync + msmtp for CLI mail access 309 environment.systemPackages = [ 310 self.packages.${pkgs.system}.notmuch 311 pkgs.muchsync 312 pkgs.msmtp 313 ]; 314 315 # After muchsync writes tags to notmuch DB, the Maildir filenames don't get updated 316 # (muchsync bypasses notmuch's flag sync). This watches the xapian DB for changes and 317 # renames files to add/remove the S (Seen) flag based on the notmuch unread tag. 318 systemd.paths.notmuch-flag-sync = { 319 wantedBy = [ "multi-user.target" ]; 320 pathConfig.PathChanged = "/var/vmail/lassul.us/lass/mail/.notmuch/xapian"; 321 }; 322 systemd.services.notmuch-flag-sync = { 323 description = "Sync notmuch tags to Maildir flags after muchsync"; 324 serviceConfig = { 325 Type = "oneshot"; 326 User = "lass"; 327 Group = "virtualMail"; 328 ExecStart = pkgs.writeShellScript "notmuch-flag-sync" '' 329 export NOTMUCH_CONFIG="${ 330 self.packages.${pkgs.system}.notmuch.passthru.configuration.configFile.path 331 }" 332 # First, pick up any IMAP flag changes into notmuch 333 ${pkgs.notmuch}/bin/notmuch new --quiet 2>/dev/null || true 334 # Add S flag to files notmuch considers read but that lack it 335 ${pkgs.notmuch}/bin/notmuch search --output=files 'NOT tag:unread' \ 336 | ${pkgs.gnugrep}/bin/grep '/cur/' \ 337 | while IFS= read -r f; do 338 [ -f "$f" ] || continue 339 flags="''${f##*:2,}" 340 case "$flags" in *S*) continue ;; esac 341 base="''${f%:2,*}" 342 mv "$f" "''${base}:2,''${flags}S" 343 done 344 # Remove S flag from files notmuch considers unread but that have it 345 ${pkgs.notmuch}/bin/notmuch search --output=files 'tag:unread' \ 346 | ${pkgs.gnugrep}/bin/grep '/cur/' \ 347 | while IFS= read -r f; do 348 [ -f "$f" ] || continue 349 flags="''${f##*:2,}" 350 case "$flags" in *S*) ;; *) continue ;; esac 351 base="''${f%:2,*}" 352 newflags="''${flags//S/}" 353 mv "$f" "''${base}:2,''${newflags}" 354 done 355 # Update notmuch DB with the renames 356 ${pkgs.notmuch}/bin/notmuch new --quiet 2>/dev/null || true 357 ''; 358 }; 359 }; 360}