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