My Nix Configuration
2
fork

Configure Feed

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

[systems.prefect] enable stalwart-mail

dish ab166eb3 92053af4

+768 -50
+1
lib/data/default.nix
··· 1 1 { 2 2 data.hosts = builtins.fromTOML (builtins.readFile ./hosts.toml); 3 3 data.services = builtins.fromTOML (builtins.readFile ./services.toml); 4 + data.mail = builtins.fromTOML (builtins.readFile ./mail.toml); 4 5 data.tsNet = "coelacanth-dragon.ts.net"; 5 6 }
+10
lib/data/mail.toml
··· 1 + extUrl = "mail.pyrox.dev" 2 + extIPs = ["5.161.140.5", "2a01:4ff:f0:98bf:0:0:0:1"] 3 + # internal port is 40k+real mail port 4 + intSMTP = 40587 5 + intSMTPS = 40465 6 + intIMAP = 40143 7 + intIMAPS = 40993 8 + intManageSieve = 44190 9 + intHTTPS = 40443 10 + intHTTP = 40080
+1
systems/x86_64-linux/prefect/default.nix
··· 19 19 ./services/fail2ban.nix 20 20 # ./services/headscale.nix 21 21 ./services/mailserver 22 + ./services/mailserver/stalwart 22 23 # ./services/netdata.nix 23 24 ./services/nginx 24 25 ./services/prometheus.nix
+2
systems/x86_64-linux/prefect/firewall.nix
··· 14 14 636 15 15 993 16 16 4130 17 + 4190 17 18 6900 18 19 8000 19 20 ]; 20 21 allowedUDPPorts = [ 22 + 80 21 23 636 22 24 4367 23 25 6900
+10
systems/x86_64-linux/prefect/secrets/secrets.nix
··· 32 32 yubi-main 33 33 yubi-back 34 34 ]; 35 + "stalwart-secret-rsa.age".publicKeys = [ 36 + prefect 37 + yubi-main 38 + yubi-back 39 + ]; 40 + "stalwart-secret-ed25519.age".publicKeys = [ 41 + prefect 42 + yubi-main 43 + yubi-back 44 + ]; 35 45 }
+19
systems/x86_64-linux/prefect/secrets/stalwart-secret-ed25519.age
··· 1 + age-encryption.org/v1 2 + -> ssh-ed25519 LcWOqQ iqNMs1zctbP+bDdN4c3RGhckhCvrTPJbcTHUQnu88ko 3 + x5T5SxnBpMRgAHbYY1YU0Yy7XVlGRZ2WnM7ICDFmjbE 4 + -> ssh-ed25519 ihSg8g g0enzjgGoNx7kHNuq7lSys3G3MeQeusWuRZ0oIrP0UE 5 + V6mXoy2LFKubv4f1Fwa0Wx15D7OI3FWeBovhDcuGaMQ 6 + -> ssh-rsa fFaiTA 7 + b9TNMiCUmYELGyGF5YzGR6KOA1w6dUteVArMlg1ZPn1HW2W/wrhhAEmTN/hAnEPR 8 + 9AFTDJupQ/6aacyIUnQlkXjI7QZeVbKXJ39VHva1rTXCamAKAAjVBdzcLtEbl4Z5 9 + iZuv6bEDcggZ1/4PmvmMtH5ljkGZWmbRSXwSqn0xF2P9VY4QMVK86WWpobk+St1X 10 + EQqEDiGrPtgwHMCOfL1wb7lZD6t2wwMlRfnTee/5qDWTSDme6QYPsAFmJOpQGCYT 11 + BOQOlZegH/KCl7qai8qBOKwEWTVMbyblihCp4vaVTgpkOiHO6PM1HS4JrXMZJeDr 12 + 30m8oeGT7/PoO14tWZvU2jCWGO/M6tiiif5I+cOymFnGCxmHPpuO/+GPOQ+f4bKb 13 + zw5hh6Bqpj9bp6QoTLfzzKnrpSk/n26e89nejha0iLDwP+QQyZZos9vss3B6Fkmu 14 + bZUuifW7eSuI7Y7l4RkuvCOEIJf+vkW7bF0e1BDpd6n5B9Jp/c5ghvWzgoyCyDud 15 + 1Xf0UxiFM90AUR8ml3QtclaRewfMr/b69qgR2olwBVrctjC/CSXAJC3bih6LZLdA 16 + nKHWyqWjWfsxCPWZUmFHMcu1ogFVsnlEdr+UoYCN1oXMwZfgxQnkfLFfcLZFW3+a 17 + JeXgm10wb2+DpmltSi7fLgQOB5462VQ+SvnFFwWFRN8 18 + --- xB0nATcSCPeSRGpbNMeSuUCJ3rxQhruXmqczOcTvMj0 19 + �EՊ�[�7]COII�IU [1�:?�wfq��;m�+ %9�������d}ՈQ!y �����IVz;`�᭹�33L ;�Z� �����J�kҕW<W,f2������"V�������G<�q'0*���~!�7��&��-��i� e�e�'�^
systems/x86_64-linux/prefect/secrets/stalwart-secret-rsa.age

This is a binary file and will not be displayed.

+101 -50
systems/x86_64-linux/prefect/services/caddy.nix
··· 1 1 { pkgs, lib, ... }: 2 2 let 3 3 pns = lib.py.data.services; 4 - # mail = lib.py.data.mail; 4 + mail = lib.py.data.mail; 5 5 marvin = "http://${lib.py.data.hosts.marvin.ts.ip4}"; 6 6 marvinIP = lib.py.data.hosts.marvin.ts.ip4; 7 7 tsNet = lib.py.data.tsNet; ··· 115 115 handle /metrics* { 116 116 respond @blocked "Access Denied" 403 117 117 } 118 - ''; 119 - }; 120 - 121 - # MTA-STS Setup for mailserver 122 - "mta-sts.pyrox.dev" = { 123 - extraConfig = '' 124 - header Content-Type text/plain; charset=utf-8 125 - respond /.well-known/mta-sts.txt <<END 126 - version: STSv1 127 - mode: enforce 128 - mx: mail.pyrox.dev 129 - mx:mail2.pyrox.dev 130 - max_age: 2419200 131 - END 200 132 118 ''; 133 119 }; 134 120 ··· 271 257 reverse_proxy ${marvin}:${toString pns.pinchflat.port} 272 258 ''; 273 259 }; 274 - # "mail.pyrox.dev:80" = { 275 - # extraConfig = '' 276 - # reverse_proxy ${marvin}:${mail.intHTTP} 277 - # ''; 278 - # }; 260 + 261 + "http://mail.pyrox.dev" = { 262 + serverAliases = [ 263 + "http://mta-sts.pyrox.dev" 264 + "http://autodiscover.pyrox.dev" 265 + "http://autoconfig.pyrox.dev" 266 + ]; 267 + extraConfig = '' 268 + reverse_proxy 127.0.0.1:${toString mail.intHTTP} { 269 + transport http { 270 + proxy_protocol v2 271 + } 272 + } 273 + 274 + ''; 275 + }; 279 276 }; 280 277 # Mail Config 281 278 globalConfig = '' ··· 283 280 ref refs/heads/pages 284 281 refresh_period 10m 285 282 } 283 + servers :80 { 284 + listener_wrappers { 285 + layer4 { 286 + @maildomains http host mail.pyrox.dev mta-sts.pyrox.dev autoconfig.pyrox.dev autodiscover.pyrox.dev 287 + route @maildomains { 288 + subroute { 289 + @a http 290 + route @a { 291 + proxy { 292 + proxy_protocol v2 293 + upstream 127.0.0.1:${toString mail.intHTTP} 294 + } 295 + } 296 + } 297 + } 298 + } 299 + http_redirect 300 + } 301 + } 302 + servers :443 { 303 + listener_wrappers { 304 + layer4 { 305 + @maildomains tls sni mail.pyrox.dev mta-sts.pyrox.dev autoconfig.pyrox.dev autodiscover.pyrox.dev 306 + route @maildomains { 307 + proxy { 308 + proxy_protocol v2 309 + upstream 127.0.0.1:${toString mail.intHTTPS} 310 + } 311 + } 312 + } 313 + tls 314 + } 315 + } 286 316 layer4 { 287 317 :22 { 288 318 @a ssh 289 319 route @a { 290 - proxy ${marvinIP}:2222 320 + proxy { 321 + upstream ${marvinIP}:2222 322 + } 323 + } 324 + } 325 + :2025 { 326 + route { 327 + proxy { 328 + proxy_protocol v2 329 + upstream 127.0.0.1:40025 330 + } 331 + } 332 + } 333 + :2143 { 334 + route { 335 + proxy { 336 + proxy_protocol v2 337 + upstream 127.0.0.1:${toString mail.intIMAP} 338 + } 339 + } 340 + } 341 + :2465 { 342 + route { 343 + proxy { 344 + proxy_protocol v2 345 + upstream 127.0.0.1:${toString mail.intSMTPS} 346 + } 347 + } 348 + } 349 + :2587 { 350 + route { 351 + proxy { 352 + proxy_protocol v2 353 + upstream 127.0.0.1:${toString mail.intSMTP} 354 + } 355 + } 356 + } 357 + :2993 { 358 + route { 359 + proxy { 360 + proxy_protocol v2 361 + upstream 127.0.0.1:${toString mail.intIMAPS} 362 + } 363 + } 364 + } 365 + :24190 { 366 + route { 367 + proxy { 368 + proxy_protocol v2 369 + upstream 127.0.0.1:${toString mail.intManageSieve} 370 + } 291 371 } 292 372 } 293 373 } 294 374 ''; 295 - # TODO: Move the below section to global options once stalwart is working 296 - # extraConfig = '' 297 - # layer4 { 298 - # 0.0.0.0:465 { 299 - # route { 300 - # proxy { 301 - # proxy_protocol v2 302 - # upstream ${marvinIP}:mail.intSMTPS} 303 - # } 304 - # } 305 - # } 306 - # 0.0.0.0:993 { 307 - # route { 308 - # proxy { 309 - # proxy_protocol v2 310 - # upstream ${marvinIP}:mail.intIMAPS} 311 - # } 312 - # } 313 - # } 314 - # 0.0.0.0:4190 { 315 - # route { 316 - # proxy { 317 - # proxy_protocol v2 318 - # upstream ${marvinIP}:mail.intManageSieve} 319 - # } 320 - # } 321 - # } 322 - # } 323 - # ''; 324 375 }; 325 376 systemd.services.caddy.serviceConfig.CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; 326 377 systemd.services.caddy.serviceConfig.AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+17
systems/x86_64-linux/prefect/services/mailserver/stalwart/acme.nix
··· 1 + { cfg }: 2 + { 3 + letsencrypt = { 4 + directory = "https://acme-staging-v02.api.letsencrypt.org/directory"; 5 + challenge = "http-01"; 6 + contact = [ "pyrox@pyrox.dev" ]; 7 + domains = [ 8 + "mail.pyrox.dev" 9 + "mta-sts.pyrox.dev" 10 + "autoconfig.pyrox.dev" 11 + "autodiscover.pyrox.dev" 12 + ]; 13 + cache = "${cfg.dataDir}/acme/certs"; 14 + renew-before = "30d"; 15 + default = true; 16 + }; 17 + }
+22
systems/x86_64-linux/prefect/services/mailserver/stalwart/auth.nix
··· 1 + { ifThen, otherwise }: 2 + let 3 + relVer = [ 4 + (ifThen "listener = 'smtp'" "relaxed") 5 + (otherwise "disable") 6 + ]; 7 + in 8 + { 9 + dkim = { 10 + sign = [ 11 + (ifThen "sender_domain = 'pyrox.dev'" "'rsa'") 12 + (ifThen "sender_domain = 'pyrox.dev'" "'ed25519'") 13 + (otherwise false) 14 + ]; 15 + }; 16 + spf.verify.ehlo = relVer; 17 + spf.verify.mail-from = relVer; 18 + dmarc.verify = relVer; 19 + iprev.verify = relVer; 20 + arc.seal = "'ed25519'"; 21 + arc.verify = "relaxed"; 22 + }
+25
systems/x86_64-linux/prefect/services/mailserver/stalwart/auto-ban.nix
··· 1 + # Strict Auto-ban 2 + # https://stalw.art/docs/server/auto-ban 3 + { 4 + auth.rate = "15/1d"; 5 + abuse.rate = "15/1d"; 6 + loiter.rate = "15/1d"; 7 + scan = { 8 + rate = "20/1d"; 9 + paths = [ 10 + "*.php*" 11 + "*.cgi*" 12 + "*.asp*" 13 + "*/wp-*" 14 + "*/php*" 15 + "*/cgi-bin*" 16 + "*xmlrpc*" 17 + "*../*" 18 + "*/..*" 19 + "*joomla*" 20 + "*wordpress*" 21 + "*drupal*" 22 + "/.git*" 23 + ]; 24 + }; 25 + }
+25
systems/x86_64-linux/prefect/services/mailserver/stalwart/calendar.nix
··· 1 + # Calendar settings 2 + # https://stalw.art/docs/collaboration/calendar 3 + { 4 + max-recurrence-expansions = 2048; 5 + # 512 KiB 6 + max-size = 524288; 7 + max-attendees-per-instance = 20; 8 + default.href-name = "default"; 9 + default.display-name = "Personal"; 10 + # Scheduling 11 + # https://stalw.art/docs/collaboration/scheduling 12 + scheduling.enable = true; 13 + # 1 MiB 14 + scheduling.inbound.max-size = 1048576; 15 + scheduling.outbound.max-recipients = 100; 16 + scheduling.inbox.auto-expunge = "30d"; 17 + scheduling.http-rsvp.enable = true; 18 + scheduling.http-rsvp.expiration = "7d"; 19 + # Notifications 20 + # https://stalw.art/docs/collaboration/notifications 21 + alarms.enable = true; 22 + alarms.minimum-interval = "1h"; 23 + alarms.from.name = "PyroNet Calendars"; 24 + alarms.from.email = "calendar-notifs@pyrox.dev"; 25 + }
+181
systems/x86_64-linux/prefect/services/mailserver/stalwart/default.nix
··· 1 + { 2 + config, 3 + lib, 4 + ... 5 + }: 6 + let 7 + d = lib.py.data.mail; 8 + cfg = config.services.stalwart-mail; 9 + sec = config.age.secrets; 10 + creds = config.services.stalwart-mail.credentials; 11 + isAuthenticated = d: { 12 + "if" = "!is_empty(authenticated_as)"; 13 + "then" = d; 14 + }; 15 + otherwise = d: { 16 + "else" = d; 17 + }; 18 + ifThen = f: d: { 19 + "if" = f; 20 + "then" = d; 21 + }; 22 + smSecret = { 23 + owner = "stalwart-mail"; 24 + group = "stalwart-mail"; 25 + }; 26 + in 27 + { 28 + services.stalwart-mail = { 29 + enable = true; 30 + dataDir = "/var/lib/stalwart"; 31 + credentials = { 32 + rsa_private_key = sec.stalwart-secret-rsa.path; 33 + ed25519_private_key = sec.stalwart-secret-ed25519.path; 34 + }; 35 + settings = { 36 + tracer.stdout.level = "debug"; 37 + config = { 38 + local-keys = [ 39 + "acme.*" 40 + "asn.*" 41 + "auth.*" 42 + "authentication.*" 43 + "auto-ban.*" 44 + "calendar.*" 45 + "certificate.*" 46 + "changes.*" 47 + "cluster.*" 48 + "config.*" 49 + "contacts.*" 50 + "directory.*" 51 + "http.*" 52 + "imap.*" 53 + "jmap.*" 54 + "queue.*" 55 + "report.*" 56 + "resolver.*" 57 + "server.*" 58 + "session.*" 59 + "storage.*" 60 + "store.*" 61 + "tracer.*" 62 + "webadmin.*" 63 + "form.*" 64 + "email.*" 65 + "spam-filter.*" 66 + ]; 67 + }; 68 + server = import ./server.nix { inherit d; }; 69 + # ACME for certs, using TLS-ALPN-01 Challenges(one fewer ports open) 70 + # https://stalw.art/docs/server/tls/acme/configuration 71 + acme = import ./acme.nix { inherit cfg; }; 72 + # HTTP Configuration 73 + # https://stalw.art/docs/http/overview 74 + http = { 75 + url = "'https://${d.extUrl}'"; 76 + hsts = true; 77 + }; 78 + # Disable HTTP Forms submission 79 + # https://stalw.art/docs/http/form-submission 80 + form.enable = false; 81 + # Storage Settings 82 + # https://stalw.art/docs/storage/overview 83 + store = { 84 + data = { 85 + type = "rocksdb"; 86 + path = "${cfg.dataDir}/db"; 87 + purge.frequency = "0 3 *"; 88 + }; 89 + blob = { 90 + type = "fs"; 91 + path = "${cfg.dataDir}/blobs"; 92 + depth = 2; 93 + compression = "lz4"; 94 + purge.frequency = "0 4 *"; 95 + }; 96 + db.path = "${cfg.dataDir}/db2"; 97 + }; 98 + storage = { 99 + data = "data"; 100 + blob = "blob"; 101 + fts = "data"; 102 + lookup = "data"; 103 + directory = "default"; 104 + }; 105 + directory = { 106 + default = { 107 + type = "internal"; 108 + store = "data"; 109 + }; 110 + }; 111 + # ASN/GeoIP Lookups 112 + # https://stalw.art/docs/server/asn 113 + asn = { 114 + type = "dns"; 115 + separator = "|"; 116 + zone.ipv4 = "origin.asn.cymru.com"; 117 + zone.ipv6 = "origin6.asn.cymru.com"; 118 + index.asn = 0; 119 + index.asn-name = 1; 120 + index.country = 2; 121 + }; 122 + auto-ban = import ./auto-ban.nix; 123 + # JMAP Settings 124 + # https://stalw.art/docs/email/jmap 125 + jmap = { 126 + mailbox.max-depth = 10; 127 + mailbox.max-name-length = 255; 128 + # 50 MB 129 + email.max-attachment-size = 50 * 1000 * 1000; 130 + # 75 MB 131 + email.max-size = 75 * 1000 * 1000; 132 + email.parse.max-items = 10; 133 + }; 134 + imap = import ./imap.nix; 135 + # Maintainance 136 + # https://stalw.art/docs/email/maintenance 137 + email.auto-expunge = "180d"; 138 + changes.max-history = 10000; 139 + session = import ./session.nix { inherit isAuthenticated otherwise; }; 140 + queue = import ./queue.nix { inherit d ifThen otherwise; }; 141 + # DNS Settings 142 + # https://stalw.art/docs/mta/outbound/dns 143 + resolver = { 144 + custom = [ 145 + "tls://dns11.quad9.net" 146 + "tcp://1.1.1.1" 147 + ]; 148 + concurrency = 2; 149 + preserve-intermediates = true; 150 + timeout = "5s"; 151 + attempts = 3; 152 + edns = true; 153 + }; 154 + report = import ./report.nix { inherit d; }; 155 + calendar = import ./calendar.nix; 156 + # Authentication 157 + auth = import ./auth.nix { inherit ifThen otherwise; }; 158 + # Contacts 159 + # https://stalw.art/docs/collaboration/contact 160 + contacts = { 161 + # 512 KiB 162 + max-size = 524288; 163 + default.href-name = "default"; 164 + default.display-name = "Contacts"; 165 + }; 166 + # Spam Filtering 167 + # https://stalw.art/docs/spamfilter/overview 168 + spam-filter = { 169 + card-is-ham = true; 170 + }; 171 + }; 172 + }; 173 + age.secrets = { 174 + stalwart-secret-rsa = smSecret // { 175 + file = ../../../secrets/stalwart-secret-rsa.age; 176 + }; 177 + stalwart-secret-ed25519 = smSecret // { 178 + file = ../../../secrets/stalwart-secret-ed25519.age; 179 + }; 180 + }; 181 + }
+42
systems/x86_64-linux/prefect/services/mailserver/stalwart/imap.nix
··· 1 + # https://stalw.art/docs/email/imap 2 + { 3 + # 50 MiB 4 + request.max-size = 52428800; 5 + auth.max-failures = 3; 6 + auth.allow-plain-text = false; 7 + folders = 8 + let 9 + folder = { 10 + create = true; 11 + subscribe = true; 12 + }; 13 + in 14 + { 15 + inbox = folder // { 16 + name = "Inbox"; 17 + }; 18 + drafts = folder // { 19 + name = "Drafts"; 20 + }; 21 + sent = folder // { 22 + name = "Sent"; 23 + }; 24 + trash = folder // { 25 + name = "Trash"; 26 + }; 27 + archive = folder // { 28 + name = "Archive"; 29 + }; 30 + junk = folder // { 31 + name = "Junk"; 32 + }; 33 + shared = { 34 + name = "Shared Folders"; 35 + create = true; 36 + subscribe = false; 37 + }; 38 + }; 39 + timeout.authenticated = "30m"; 40 + timeout.anonymous = "1m"; 41 + timeout.idle = "30m"; 42 + }
+97
systems/x86_64-linux/prefect/services/mailserver/stalwart/queue.nix
··· 1 + { 2 + d, 3 + ifThen, 4 + otherwise, 5 + }: 6 + # Queue Management 7 + # https://stalw.art/docs/mta/outbound/overview 8 + { 9 + # Virtual Queues 10 + # https://stalw.art/docs/mta/outbound/queue 11 + virtual.default.threads-per-node = 100; 12 + virtual.admin.threads-per-node = 10; 13 + virtual.local.threads-per-node = 100; 14 + # Schedules 15 + # https://stalw.art/docs/mta/outbound/schedule 16 + schedule = 17 + let 18 + queue = { 19 + retry = [ 20 + "1m" 21 + "2m" 22 + "5m" 23 + "10m" 24 + "15m" 25 + "30m" 26 + "1h" 27 + "2h" 28 + ]; 29 + notify = [ 30 + "1d" 31 + "3d" 32 + ]; 33 + max-attempts = 15; 34 + }; 35 + in 36 + { 37 + default = queue // { 38 + queue-name = "default"; 39 + }; 40 + admin = queue // { 41 + queue-name = "admin"; 42 + }; 43 + local = queue // { 44 + queue-name = "local"; 45 + }; 46 + }; 47 + # Routes 48 + # https://stalw.art/docs/mta/outbound/routing 49 + route = { 50 + local.type = "local"; 51 + remote = { 52 + type = "mx"; 53 + ip-lookup = "ipv6_then_ipv4"; 54 + tls.implicit = false; 55 + tls.allow-invalid-certs = false; 56 + }; 57 + }; 58 + # Strategies 59 + # https://stalw.art/docs/mta/outbound/strategy 60 + strategy = { 61 + schedule = [ 62 + (ifThen "is_local_domain('', rcpt_domain)" "'local'") 63 + (ifThen "source = 'dsn'" "'admin'") 64 + (ifThen "source = 'report'" "'admin'") 65 + (ifThen "source = 'autogenerated'" "'admin'") 66 + (otherwise "'default'") 67 + ]; 68 + route = [ 69 + (ifThen "is_local_domain('', rcpt_domain)" "'local'") 70 + (otherwise "'remote'") 71 + ]; 72 + connection = "'default'"; 73 + tls = "'default'"; 74 + }; 75 + # Remote Connection 76 + # https://stalw.art/docs/mta/outbound/connection 77 + connection.default = { 78 + ehlo-hostname = d.extUrl; 79 + source-ips = d.extIPs; 80 + timeout = { 81 + connect = "3m"; 82 + greeting = "3m"; 83 + ehlo = "3m"; 84 + mail-from = "3m"; 85 + rcpt-to = "3m"; 86 + data = "10m"; 87 + }; 88 + }; 89 + tls.default = { 90 + dane = "optional"; 91 + mta-sts = "optional"; 92 + starttls = "optional"; 93 + allow-invalid-certs = false; 94 + timeout.tls = "3m"; 95 + timeout.mta-sts = "3m"; 96 + }; 97 + }
+64
systems/x86_64-linux/prefect/services/mailserver/stalwart/report.nix
··· 1 + { d }: 2 + # Reports 3 + # https://stalw.art/docs/mta/reports/overview 4 + { 5 + domain = "pyrox.dev"; 6 + submitter = "'${d.extUrl}'"; 7 + analysis = { 8 + addresses = [ 9 + "dmarc@" 10 + "reports@" 11 + "spf@" 12 + "dkim@" 13 + "abuse@" 14 + ]; 15 + forward = true; 16 + store = "30d"; 17 + }; 18 + dsn = { 19 + from-name = "'PyroNet Mail'"; 20 + from-address = "'mail@pyrox.dev'"; 21 + sign = "['rsa', 'ed25519']"; 22 + }; 23 + dkim = { 24 + from-name = "'PyroNet Mail Reports'"; 25 + from-address = "'noreply-dkim@pyrox.dev'"; 26 + subject = "'DKIM Authentication Failure Report'"; 27 + sign = "['rsa', 'ed25519']"; 28 + send = "1/1d"; 29 + }; 30 + spf = { 31 + from-name = "'PyroNet Mail Reports'"; 32 + from-address = "'noreply-spf@pyrox.dev'"; 33 + subject = "'SPF Authentication Failure Report'"; 34 + sign = "['rsa', 'ed25519']"; 35 + send = "1/1d"; 36 + }; 37 + dmarc = { 38 + from-name = "'PyroNet Mail Reports'"; 39 + from-address = "'noreply-dmarc@pyrox.dev'"; 40 + subject = "'DMARC Authentication Failure Report'"; 41 + sign = "['rsa', 'ed25519']"; 42 + send = "1/1d"; 43 + aggregate = { 44 + from-name = "'DMARC Report'"; 45 + from-address = "'noreply-dmarc@pyrox.dev'"; 46 + org-name = "'PyroNet Mail'"; 47 + contact-info = "'pyrox@pyrox.dev'"; 48 + send = "daily"; 49 + # 25 MiB 50 + max-size = 26214400; 51 + sign = "['rsa', 'ed25519']"; 52 + }; 53 + }; 54 + tls.aggregate = { 55 + from-name = "'PyroNet Mail Reports'"; 56 + from-address = "'noreply-tls@pyrox.dev'"; 57 + org-name = "'PyroNet Mail'"; 58 + contact-info = "'pyrox@pyrox.dev'"; 59 + send = "daily"; 60 + # 25 MiB 61 + max-size = 26214400; 62 + sign = "['rsa', 'ed25519']"; 63 + }; 64 + }
+68
systems/x86_64-linux/prefect/services/mailserver/stalwart/server.nix
··· 1 + { d }: 2 + { 3 + hostname = d.extUrl; 4 + # TLS 5 + # https://stalw.art/docs/server/tls/overview 6 + tls = { 7 + enable = true; 8 + implicit = false; 9 + ignore-client-order = true; 10 + }; 11 + # Listeners 12 + # https://stalw.art/docs/server/listener 13 + listener = { 14 + smtp = { 15 + bind = [ 16 + "[::]:${toString d.intSMTP}" 17 + "[::]:40025" 18 + ]; 19 + protocol = "smtp"; 20 + # Explicit TLS 21 + tls.implicit = false; 22 + }; 23 + smtps = { 24 + bind = "[::]:${toString d.intSMTPS}"; 25 + protocol = "smtp"; 26 + # Implicit TLS 27 + tls.implicit = true; 28 + }; 29 + imap = { 30 + bind = "[::]:${toString d.intIMAP}"; 31 + protocol = "imap"; 32 + # Explicit TLS 33 + tls.implicit = false; 34 + }; 35 + imaps = { 36 + bind = "[::]:${toString d.intIMAPS}"; 37 + protocol = "imap"; 38 + # Implicit TLS 39 + tls.implicit = true; 40 + }; 41 + managesieve = { 42 + bind = "[::]:${toString d.intManageSieve}"; 43 + protocol = "managesieve"; 44 + # Explicit TLS 45 + tls.implicit = false; 46 + }; 47 + https = { 48 + bind = "[::]:${toString d.intHTTPS}"; 49 + protocol = "http"; 50 + # Implicit TLS 51 + tls.implicit = true; 52 + }; 53 + http = { 54 + bind = "[::]:${toString d.intHTTP}"; 55 + protocol = "http"; 56 + # Implicit TLS 57 + tls.implicit = false; 58 + }; 59 + }; 60 + # Proxy Protocol from Caddy 61 + # Only accepts proxy protocol from Tailscale IP Ranges 62 + # https://tailscale.com/kb/1015/100.x-addresses 63 + # https://tailscale.com/kb/1033/ip-and-dns-addresses 64 + proxy.trusted-networks = [ 65 + "fd7a:115c:a1e0::/48" 66 + "100.64.0.0/10" 67 + ]; 68 + }
+42
systems/x86_64-linux/prefect/services/mailserver/stalwart/session.nix
··· 1 + { isAuthenticated, otherwise }: 2 + # MTA Settings 3 + # https://stalw.art/docs/mta/overview 4 + { 5 + # Inbound 6 + # https://stalw.art/docs/mta/inbound/overview 7 + # # EHLO Stage 8 + # # https://stalw.art/docs/mta/inbound/ehlo 9 + ehlo = { 10 + require = true; 11 + }; 12 + # # RCPT Stage 13 + # # https://stalw.art/docs/mta/inbound/rcpt 14 + rcpt = { 15 + relay = [ 16 + (isAuthenticated true) 17 + (otherwise false) 18 + ]; 19 + subaddressing = true; 20 + }; 21 + extensions = 22 + let 23 + ifAuthed = [ 24 + (isAuthenticated true) 25 + (otherwise false) 26 + ]; 27 + in 28 + { 29 + pipelining = true; 30 + chunking = true; 31 + requiretls = true; 32 + no-soliciting = ""; 33 + dsn = ifAuthed; 34 + deliver-by = [ 35 + (isAuthenticated "15d") 36 + (otherwise false) 37 + ]; 38 + mt-priority = false; 39 + vrfy = ifAuthed; 40 + expn = ifAuthed; 41 + }; 42 + }
+41
systems/x86_64-linux/prefect/services/mailserver/stalwart/signature.nix
··· 1 + let 2 + headers = [ 3 + "From" 4 + "To" 5 + "Cc" 6 + "Date" 7 + "Subject" 8 + "Message-ID" 9 + "Organization" 10 + "MIME-Version" 11 + "Content-Type" 12 + "In-Reply-To" 13 + "References" 14 + "List-Id" 15 + "User-Agent" 16 + "Thread-Topic" 17 + "Thread-Index" 18 + ]; 19 + in 20 + { 21 + rsa = { 22 + inherit headers; 23 + private-key = "%{file:/run/credentials/stalwart-mail.service/rsa_private_key}"; 24 + domain = "pyrox.dev"; 25 + selector = "rsa-default"; 26 + algorithm = "rsa-sha256"; 27 + canonicalization = "relaxed/relaxed"; 28 + expire = "10d"; 29 + report = true; 30 + }; 31 + ed25519 = { 32 + inherit headers; 33 + private-key = "%{file:/run/credentials/stalwart-mail.service/ed25519_private_key}"; 34 + domain = "pyrox.dev"; 35 + selector = "default"; 36 + algorithm = "ed25519-sha256"; 37 + canonicalization = "relaxed/relaxed"; 38 + expire = "10d"; 39 + report = true; 40 + }; 41 + }