This repository has no description
0

Configure Feed

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

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