Self-host your own digital island
1# nixos-mailserver: a simple mail server 2# Copyright (C) 2016-2018 Robin Raymond 3# 4# This program is free software: you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation, either version 3 of the License, or 7# (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with this program. If not, see <http://www.gnu.org/licenses/> 16 17{ config, lib, pkgs, ... }: 18 19with lib; 20 21let 22 cfg = config.mailserver; 23in 24{ 25 options.mailserver = { 26 enable = lib.mkEnableOption "nixos-mailserver"; 27 28 openFirewall = mkOption { 29 type = types.bool; 30 default = true; 31 description = "Automatically open ports in the firewall."; 32 }; 33 34 fqdn = mkOption { 35 type = types.str; 36 example = "mx.example.com"; 37 description = "The fully qualified domain name of the mail server."; 38 }; 39 40 domains = mkOption { 41 type = types.listOf types.str; 42 example = [ "example.com" ]; 43 default = []; 44 description = "The domains that this mail server serves."; 45 }; 46 47 certificateDomains = mkOption { 48 type = types.listOf types.str; 49 example = [ "imap.example.com" "pop3.example.com" ]; 50 default = []; 51 description = "Secondary domains and subdomains for which it is necessary to generate a certificate."; 52 }; 53 54 messageSizeLimit = mkOption { 55 type = types.int; 56 example = 52428800; 57 default = 20971520; 58 description = "Message size limit enforced by Postfix."; 59 }; 60 61 loginAccounts = mkOption { 62 type = types.attrsOf (types.submodule ({ name, ... }: { 63 options = { 64 name = mkOption { 65 type = types.str; 66 example = "user1@example.com"; 67 description = "Username"; 68 }; 69 70 hashedPassword = mkOption { 71 type = with types; nullOr str; 72 default = null; 73 example = "$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/"; 74 description = '' 75 The user's hashed password. Use `htpasswd` as follows 76 77 ``` 78 nix run nixpkgs.apacheHttpd -c htpasswd -nbB "" "super secret password" | cut -d: -f2 79 ``` 80 81 Warning: this is stored in plaintext in the Nix store! 82 Use `hashedPasswordFile` instead. 83 ''; 84 }; 85 86 hashedPasswordFile = mkOption { 87 type = with types; nullOr path; 88 default = null; 89 example = "/run/keys/user1-passwordhash"; 90 description = '' 91 A file containing the user's hashed password. Use `htpasswd` as follows 92 93 ``` 94 nix run nixpkgs.apacheHttpd -c htpasswd -nbB "" "super secret password" | cut -d: -f2 95 ``` 96 ''; 97 }; 98 99 aliases = mkOption { 100 type = with types; listOf types.str; 101 example = ["abuse@example.com" "postmaster@example.com"]; 102 default = []; 103 description = '' 104 A list of aliases of this login account. 105 Note: Use list entries like "@example.com" to create a catchAll 106 that allows sending from all email addresses in these domain. 107 ''; 108 }; 109 110 catchAll = mkOption { 111 type = with types; listOf (enum cfg.domains); 112 example = ["example.com" "example2.com"]; 113 default = []; 114 description = '' 115 For which domains should this account act as a catch all? 116 Note: Does not allow sending from all addresses of these domains. 117 ''; 118 }; 119 120 quota = mkOption { 121 type = with types; nullOr types.str; 122 default = null; 123 example = "2G"; 124 description = '' 125 Per user quota rules. Accepted sizes are `xx k/M/G/T` with the 126 obvious meaning. Leave blank for the standard quota `100G`. 127 ''; 128 }; 129 130 sieveScript = mkOption { 131 type = with types; nullOr lines; 132 default = null; 133 example = '' 134 require ["fileinto", "mailbox"]; 135 136 if address :is "from" "gitlab@mg.gitlab.com" { 137 fileinto :create "GitLab"; 138 stop; 139 } 140 141 # This must be the last rule, it will check if list-id is set, and 142 # file the message into the Lists folder for further investigation 143 elsif header :matches "list-id" "<?*>" { 144 fileinto :create "Lists"; 145 stop; 146 } 147 ''; 148 description = '' 149 Per-user sieve script. 150 ''; 151 }; 152 153 sendOnly = mkOption { 154 type = types.bool; 155 default = false; 156 description = '' 157 Specifies if the account should be a send-only account. 158 Emails sent to send-only accounts will be rejected from 159 unauthorized senders with the sendOnlyRejectMessage 160 stating the reason. 161 ''; 162 }; 163 164 sendOnlyRejectMessage = mkOption { 165 type = types.str; 166 default = "This account cannot receive emails."; 167 description = '' 168 The message that will be returned to the sender when an email is 169 sent to a send-only account. Only used if the account is marked 170 as send-only. 171 ''; 172 }; 173 }; 174 175 config.name = mkDefault name; 176 })); 177 example = { 178 user1 = { 179 hashedPassword = "$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/"; 180 }; 181 user2 = { 182 hashedPassword = "$6$oE0ZNv2n7Vk9gOf$9xcZWCCLGdMflIfuA0vR1Q1Xblw6RZqPrP94mEit2/81/7AKj2bqUai5yPyWE.QYPyv6wLMHZvjw3Rlg7yTCD/"; 183 }; 184 }; 185 description = '' 186 The login account of the domain. Every account is mapped to a unix user, 187 e.g. `user1@example.com`. To generate the passwords use `htpasswd` as 188 follows 189 190 ``` 191 nix run nixpkgs.apacheHttpd -c htpasswd -nbB "" "super secret password" | cut -d: -f2 192 ``` 193 ''; 194 default = {}; 195 }; 196 197 indexDir = mkOption { 198 type = types.nullOr types.str; 199 default = null; 200 description = '' 201 Folder to store search indices. If null, indices are stored 202 along with email, which could not necessarily be desirable, 203 especially when the fullTextSearch option is enable since 204 indices it creates are voluminous and do not need to be backed 205 up. 206 207 Be careful when changing this option value since all indices 208 would be recreated at the new location (and clients would need 209 to resynchronize). 210 211 Note the some variables can be used in the file path. See 212 https://doc.dovecot.org/configuration_manual/mail_location/#variables 213 for details. 214 ''; 215 example = "/var/lib/dovecot/indices"; 216 }; 217 218 fullTextSearch = { 219 enable = lib.mkEnableOption "Full text search indexing with xapian. This has significant performance and disk space cost."; 220 autoIndex = mkOption { 221 type = types.bool; 222 default = true; 223 description = "Enable automatic indexing of messages as they are received or modified."; 224 }; 225 autoIndexExclude = mkOption { 226 type = types.listOf types.str; 227 default = [ ]; 228 example = [ "\\Trash" "SomeFolder" "Other/*" ]; 229 description = '' 230 Mailboxes to exclude from automatic indexing. 231 ''; 232 }; 233 234 indexAttachments = mkOption { 235 type = types.bool; 236 default = false; 237 description = "Also index text-only attachements. Binary attachements are never indexed."; 238 }; 239 240 enforced = mkOption { 241 type = types.enum [ "yes" "no" "body" ]; 242 default = "no"; 243 description = '' 244 Fail searches when no index is available. If set to 245 <literal>body</literal>, then only body searches (as opposed to 246 header) are affected. If set to <literal>no</literal>, searches may 247 fall back to a very slow brute force search. 248 ''; 249 }; 250 251 minSize = mkOption { 252 type = types.int; 253 default = 2; 254 description = "Size of the smallest n-gram to index."; 255 }; 256 maxSize = mkOption { 257 type = types.int; 258 default = 20; 259 description = "Size of the largest n-gram to index."; 260 }; 261 memoryLimit = mkOption { 262 type = types.nullOr types.int; 263 default = null; 264 example = 2000; 265 description = "Memory limit for the indexer process, in MiB. If null, leaves the default (which is rather low), and if 0, no limit."; 266 }; 267 268 maintenance = { 269 enable = mkOption { 270 type = types.bool; 271 default = true; 272 description = "Regularly optmize indices, as recommended by upstream."; 273 }; 274 275 onCalendar = mkOption { 276 type = types.str; 277 default = "daily"; 278 description = "When to run the maintenance job. See systemd.time(7) for more information about the format."; 279 }; 280 281 randomizedDelaySec = mkOption { 282 type = types.int; 283 default = 1000; 284 description = "Run the maintenance job not exactly at the time specified with <literal>onCalendar</literal>, but plus or minus this many seconds."; 285 }; 286 }; 287 }; 288 289 lmtpSaveToDetailMailbox = mkOption { 290 type = types.enum ["yes" "no"]; 291 default = "yes"; 292 description = '' 293 If an email address is delimited by a "+", should it be filed into a 294 mailbox matching the string after the "+"? For example, 295 user1+test@example.com would be filed into the mailbox "test". 296 ''; 297 }; 298 299 extraVirtualAliases = mkOption { 300 type = let 301 loginAccount = mkOptionType { 302 name = "Login Account"; 303 check = (account: builtins.elem account (builtins.attrNames cfg.loginAccounts)); 304 }; 305 in with types; attrsOf (either loginAccount (nonEmptyListOf loginAccount)); 306 example = { 307 "info@example.com" = "user1@example.com"; 308 "postmaster@example.com" = "user1@example.com"; 309 "abuse@example.com" = "user1@example.com"; 310 "multi@example.com" = [ "user1@example.com" "user2@example.com" ]; 311 }; 312 description = '' 313 Virtual Aliases. A virtual alias `"info@example.com" = "user1@example.com"` means that 314 all mail to `info@example.com` is forwarded to `user1@example.com`. Note 315 that it is expected that `postmaster@example.com` and `abuse@example.com` is 316 forwarded to some valid email address. (Alternatively you can create login 317 accounts for `postmaster` and (or) `abuse`). Furthermore, it also allows 318 the user `user1@example.com` to send emails as `info@example.com`. 319 It's also possible to create an alias for multiple accounts. In this 320 example all mails for `multi@example.com` will be forwarded to both 321 `user1@example.com` and `user2@example.com`. 322 ''; 323 default = {}; 324 }; 325 326 forwards = mkOption { 327 type = with types; attrsOf (either (listOf str) str); 328 example = { 329 "user@example.com" = "user@elsewhere.com"; 330 }; 331 description = '' 332 To forward mails to an external address. For instance, 333 the value {`"user@example.com" = "user@elsewhere.com";}` 334 means that mails to `user@example.com` are forwarded to 335 `user@elsewhere.com`. The difference with the 336 `extraVirtualAliases` option is that `user@elsewhere.com` 337 can't send mail as `user@example.com`. Also, this option 338 allows to forward mails to external addresses. 339 ''; 340 default = {}; 341 }; 342 343 rejectSender = mkOption { 344 type = types.listOf types.str; 345 example = [ "@example.com" "spammer@example.net" ]; 346 description = '' 347 Reject emails from these addresses from unauthorized senders. 348 Use if a spammer is using the same domain or the same sender over and over. 349 ''; 350 default = []; 351 }; 352 353 rejectRecipients = mkOption { 354 type = types.listOf types.str; 355 example = [ "sales@example.com" "info@example.com" ]; 356 description = '' 357 Reject emails addressed to these local addresses from unauthorized senders. 358 Use if a spammer has found email addresses in a catchall domain but you do 359 not want to disable the catchall. 360 ''; 361 default = []; 362 }; 363 364 vmailUID = mkOption { 365 type = types.int; 366 default = 5000; 367 description = '' 368 The unix UID of the virtual mail user. Be mindful that if this is 369 changed, you will need to manually adjust the permissions of 370 mailDirectory. 371 ''; 372 }; 373 374 vmailUserName = mkOption { 375 type = types.str; 376 default = "virtualMail"; 377 description = '' 378 The user name and group name of the user that owns the directory where all 379 the mail is stored. 380 ''; 381 }; 382 383 vmailGroupName = mkOption { 384 type = types.str; 385 default = "virtualMail"; 386 description = '' 387 The user name and group name of the user that owns the directory where all 388 the mail is stored. 389 ''; 390 }; 391 392 mailDirectory = mkOption { 393 type = types.path; 394 default = "/var/vmail"; 395 description = '' 396 Where to store the mail. 397 ''; 398 }; 399 400 useFsLayout = mkOption { 401 type = types.bool; 402 default = false; 403 description = '' 404 Sets whether dovecot should organize mail in subdirectories: 405 406 - /var/vmail/example.com/user/.folder.subfolder/ (default layout) 407 - /var/vmail/example.com/user/folder/subfolder/ (FS layout) 408 409 See https://wiki2.dovecot.org/MailboxFormat/Maildir for details. 410 ''; 411 }; 412 413 hierarchySeparator = mkOption { 414 type = types.str; 415 default = "."; 416 description = '' 417 The hierarchy separator for mailboxes used by dovecot for the namespace 'inbox'. 418 Dovecot defaults to "." but recommends "/". 419 This affects how mailboxes appear to mail clients and sieve scripts. 420 For instance when using "." then in a sieve script "example.com" would refer to the mailbox "com" in the parent mailbox "example". 421 This does not determine the way your mails are stored on disk. 422 See https://wiki.dovecot.org/Namespaces for details. 423 ''; 424 }; 425 426 mailboxes = mkOption { 427 description = '' 428 The mailboxes for dovecot. 429 Depending on the mail client used it might be necessary to change some mailbox's name. 430 ''; 431 default = { 432 Trash = { 433 auto = "no"; 434 specialUse = "Trash"; 435 }; 436 Junk = { 437 auto = "subscribe"; 438 specialUse = "Junk"; 439 }; 440 Drafts = { 441 auto = "subscribe"; 442 specialUse = "Drafts"; 443 }; 444 Sent = { 445 auto = "subscribe"; 446 specialUse = "Sent"; 447 }; 448 }; 449 }; 450 451 certificateScheme = mkOption { 452 type = types.enum [ 1 2 3 ]; 453 default = 2; 454 description = '' 455 Certificate Files. There are three options for these. 456 457 1) You specify locations and manually copy certificates there. 458 2) You let the server create new (self signed) certificates on the fly. 459 3) You let the server create a certificate via `Let's Encrypt`. Note that 460 this implies that a stripped down webserver has to be started. This also 461 implies that the FQDN must be set as an `A` record to point to the IP of 462 the server. In particular port 80 on the server will be opened. For details 463 on how to set up the domain records, see the guide in the readme. 464 ''; 465 }; 466 467 certificateFile = mkOption { 468 type = types.path; 469 example = "/root/mail-server.crt"; 470 description = '' 471 Scheme 1) 472 Location of the certificate 473 ''; 474 }; 475 476 keyFile = mkOption { 477 type = types.path; 478 example = "/root/mail-server.key"; 479 description = '' 480 Scheme 1) 481 Location of the key file 482 ''; 483 }; 484 485 certificateDirectory = mkOption { 486 type = types.path; 487 default = "/var/certs"; 488 description = '' 489 Scheme 2) 490 This is the folder where the certificate will be created. The name is 491 hardcoded to "cert-DOMAIN.pem" and "key-DOMAIN.pem" and the 492 certificate is valid for 10 years. 493 ''; 494 }; 495 496 enableImap = mkOption { 497 type = types.bool; 498 default = true; 499 description = '' 500 Whether to enable IMAP with STARTTLS on port 143. 501 ''; 502 }; 503 504 enableImapSsl = mkOption { 505 type = types.bool; 506 default = true; 507 description = '' 508 Whether to enable IMAP with TLS in wrapper-mode on port 993. 509 ''; 510 }; 511 512 enableSubmission = mkOption { 513 type = types.bool; 514 default = true; 515 description = '' 516 Whether to enable SMTP with STARTTLS on port 587. 517 ''; 518 }; 519 520 enableSubmissionSsl = mkOption { 521 type = types.bool; 522 default = true; 523 description = '' 524 Whether to enable SMTP with TLS in wrapper-mode on port 465. 525 ''; 526 }; 527 528 enablePop3 = mkOption { 529 type = types.bool; 530 default = false; 531 description = '' 532 Whether to enable POP3 with STARTTLS on port on port 110. 533 ''; 534 }; 535 536 enablePop3Ssl = mkOption { 537 type = types.bool; 538 default = false; 539 description = '' 540 Whether to enable POP3 with TLS in wrapper-mode on port 995. 541 ''; 542 }; 543 544 enableManageSieve = mkOption { 545 type = types.bool; 546 default = false; 547 description = '' 548 Whether to enable ManageSieve, setting this option to true will open 549 port 4190 in the firewall. 550 551 The ManageSieve protocol allows users to manage their Sieve scripts on 552 a remote server with a supported client, including Thunderbird. 553 ''; 554 }; 555 556 sieveDirectory = mkOption { 557 type = types.path; 558 default = "/var/sieve"; 559 description = '' 560 Where to store the sieve scripts. 561 ''; 562 }; 563 564 virusScanning = mkOption { 565 type = types.bool; 566 default = false; 567 description = '' 568 Whether to activate virus scanning. Note that virus scanning is _very_ 569 expensive memory wise. 570 ''; 571 }; 572 573 dkimSigning = mkOption { 574 type = types.bool; 575 default = true; 576 description = '' 577 Whether to activate dkim signing. 578 ''; 579 }; 580 581 dkimSelector = mkOption { 582 type = types.str; 583 default = "mail"; 584 description = '' 585 586 ''; 587 }; 588 589 dkimKeyDirectory = mkOption { 590 type = types.path; 591 default = "/var/dkim"; 592 description = '' 593 594 ''; 595 }; 596 597 dkimKeyBits = mkOption { 598 type = types.int; 599 default = 1024; 600 description = '' 601 How many bits in generated DKIM keys. RFC6376 advises minimum 1024-bit keys. 602 603 If you have already deployed a key with a different number of bits than specified 604 here, then you should use a different selector (dkimSelector). In order to get 605 this package to generate a key with the new number of bits, you will either have to 606 change the selector or delete the old key file. 607 ''; 608 }; 609 610 dkimHeaderCanonicalization = mkOption { 611 type = types.enum ["relaxed" "simple"]; 612 default = "relaxed"; 613 description = '' 614 DKIM canonicalization algorithm for message headers. 615 616 See https://datatracker.ietf.org/doc/html/rfc6376/#section-3.4 for details. 617 ''; 618 }; 619 620 dkimBodyCanonicalization = mkOption { 621 type = types.enum ["relaxed" "simple"]; 622 default = "relaxed"; 623 description = '' 624 DKIM canonicalization algorithm for message bodies. 625 626 See https://datatracker.ietf.org/doc/html/rfc6376/#section-3.4 for details. 627 ''; 628 }; 629 630 debug = mkOption { 631 type = types.bool; 632 default = false; 633 description = '' 634 Whether to enable verbose logging for mailserver related services. This 635 intended be used for development purposes only, you probably don't want 636 to enable this unless you're hacking on nixos-mailserver. 637 ''; 638 }; 639 640 maxConnectionsPerUser = mkOption { 641 type = types.int; 642 default = 100; 643 description = '' 644 Maximum number of IMAP/POP3 connections allowed for a user from each IP address. 645 E.g. a value of 50 allows for 50 IMAP and 50 POP3 connections at the same 646 time for a single user. 647 ''; 648 }; 649 650 localDnsResolver = mkOption { 651 type = types.bool; 652 default = true; 653 description = '' 654 Runs a local DNS resolver (kresd) as recommended when running rspamd. This prevents your log file from filling up with rspamd_monitored_dns_mon entries. 655 ''; 656 }; 657 658 recipientDelimiter = mkOption { 659 type = types.str; 660 default = "+"; 661 description = '' 662 Configure the recipient delimiter. 663 ''; 664 }; 665 666 redis = { 667 address = mkOption { 668 type = types.str; 669 # read the default from nixos' redis module 670 default = let 671 cf = config.services.redis.servers.rspamd.bind; 672 cfdefault = if cf == null then "127.0.0.1" else cf; 673 ips = lib.strings.splitString " " cfdefault; 674 ip = lib.lists.head (ips ++ [ "127.0.0.1" ]); 675 isIpv6 = ip: lib.lists.elem ":" (lib.stringToCharacters ip); 676 in 677 if (ip == "0.0.0.0" || ip == "::") 678 then "127.0.0.1" 679 else if isIpv6 ip then "[${ip}]" else ip; 680 defaultText = lib.literalDocBook "computed from <option>config.services.redis.servers.rspamd.bind</option>"; 681 description = '' 682 Address that rspamd should use to contact redis. 683 ''; 684 }; 685 686 port = mkOption { 687 type = types.port; 688 default = config.services.redis.servers.rspamd.port; 689 defaultText = lib.literalExpression "config.services.redis.servers.rspamd.port"; 690 description = '' 691 Port that rspamd should use to contact redis. 692 ''; 693 }; 694 695 password = mkOption { 696 type = types.nullOr types.str; 697 default = config.services.redis.servers.rspamd.requirePass; 698 defaultText = lib.literalExpression "config.services.redis.servers.rspamd.requirePass"; 699 description = '' 700 Password that rspamd should use to contact redis, or null if not required. 701 ''; 702 }; 703 }; 704 705 rewriteMessageId = mkOption { 706 type = types.bool; 707 default = false; 708 description = '' 709 Rewrites the Message-ID's hostname-part of outgoing emails to the FQDN. 710 Please be aware that this may cause problems with some mail clients 711 relying on the original Message-ID. 712 ''; 713 }; 714 715 sendingFqdn = mkOption { 716 type = types.str; 717 default = cfg.fqdn; 718 defaultText = "config.mailserver.fqdn"; 719 example = "myserver.example.com"; 720 description = '' 721 The fully qualified domain name of the mail server used to 722 identify with remote servers. 723 724 If this server's IP serves purposes other than a mail server, 725 it may be desirable for the server to have a name other than 726 that to which the user will connect. For example, the user 727 might connect to mx.example.com, but the server's IP has 728 reverse DNS that resolves to myserver.example.com; in this 729 scenario, some mail servers may reject or penalize the 730 message. 731 732 This setting allows the server to identify as 733 myserver.example.com when forwarding mail, independently of 734 `fqdn` (which, for SSL reasons, should generally be the name 735 to which the user connects). 736 737 Set this to the name to which the sending IP's reverse DNS 738 resolves. 739 ''; 740 }; 741 742 policydSPFExtraConfig = mkOption { 743 type = types.lines; 744 default = ""; 745 example = '' 746 skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1 747 ''; 748 description = '' 749 Extra configuration options for policyd-spf. This can be use to among 750 other things skip spf checking for some IP addresses. 751 ''; 752 }; 753 754 monitoring = { 755 enable = lib.mkEnableOption "monitoring via monit"; 756 757 alertAddress = mkOption { 758 type = types.str; 759 description = '' 760 The email address to send alerts to. 761 ''; 762 }; 763 764 config = mkOption { 765 type = types.str; 766 default = '' 767 set daemon 120 with start delay 60 768 set mailserver 769 localhost 770 771 set httpd port 2812 and use address localhost 772 allow localhost 773 allow admin:obwjoawijerfoijsiwfj29jf2f2jd 774 775 check filesystem root with path / 776 if space usage > 80% then alert 777 if inode usage > 80% then alert 778 779 check system $HOST 780 if cpu usage > 95% for 10 cycles then alert 781 if memory usage > 75% for 5 cycles then alert 782 if swap usage > 20% for 10 cycles then alert 783 if loadavg (1min) > 90 for 15 cycles then alert 784 if loadavg (5min) > 80 for 10 cycles then alert 785 if loadavg (15min) > 70 for 8 cycles then alert 786 787 check process sshd with pidfile /var/run/sshd.pid 788 start program "${pkgs.systemd}/bin/systemctl start sshd" 789 stop program "${pkgs.systemd}/bin/systemctl stop sshd" 790 if failed port 22 protocol ssh for 2 cycles then restart 791 792 check process postfix with pidfile /var/lib/postfix/queue/pid/master.pid 793 start program = "${pkgs.systemd}/bin/systemctl start postfix" 794 stop program = "${pkgs.systemd}/bin/systemctl stop postfix" 795 if failed port 25 protocol smtp for 5 cycles then restart 796 797 check process dovecot with pidfile /var/run/dovecot2/master.pid 798 start program = "${pkgs.systemd}/bin/systemctl start dovecot2" 799 stop program = "${pkgs.systemd}/bin/systemctl stop dovecot2" 800 if failed host ${cfg.fqdn} port 993 type tcpssl sslauto protocol imap for 5 cycles then restart 801 802 check process rspamd with matching "rspamd: main process" 803 start program = "${pkgs.systemd}/bin/systemctl start rspamd" 804 stop program = "${pkgs.systemd}/bin/systemctl stop rspamd" 805 ''; 806 defaultText = lib.literalDocBook "see source"; 807 description = '' 808 The configuration used for monitoring via monit. 809 Use a mail address that you actively check and set it via 'set alert ...'. 810 ''; 811 }; 812 }; 813 814 borgbackup = { 815 enable = lib.mkEnableOption "backup via borgbackup"; 816 817 repoLocation = mkOption { 818 type = types.str; 819 default = "/var/borgbackup"; 820 description = '' 821 The location where borg saves the backups. 822 This can be a local path or a remote location such as user@host:/path/to/repo. 823 It is exported and thus available as an environment variable to cmdPreexec and cmdPostexec. 824 ''; 825 }; 826 827 startAt = mkOption { 828 type = types.str; 829 default = "hourly"; 830 description = "When or how often the backup should run. Must be in the format described in systemd.time 7."; 831 }; 832 833 user = mkOption { 834 type = types.str; 835 default = "virtualMail"; 836 description = "The user borg and its launch script is run as."; 837 }; 838 839 group = mkOption { 840 type = types.str; 841 default = "virtualMail"; 842 description = "The group borg and its launch script is run as."; 843 }; 844 845 compression = { 846 method = mkOption { 847 type = types.nullOr (types.enum ["none" "lz4" "zstd" "zlib" "lzma"]); 848 default = null; 849 description = "Leaving this unset allows borg to choose. The default for borg 1.1.4 is lz4."; 850 }; 851 852 level = mkOption { 853 type = types.nullOr types.int; 854 default = null; 855 description = '' 856 Denotes the level of compression used by borg. 857 Most methods accept levels from 0 to 9 but zstd which accepts values from 1 to 22. 858 If null the decision is left up to borg. 859 ''; 860 }; 861 862 auto = mkOption { 863 type = types.bool; 864 default = false; 865 description = "Leaves it to borg to determine whether an individual file should be compressed."; 866 }; 867 }; 868 869 encryption = { 870 method = mkOption { 871 type = types.enum [ 872 "none" 873 "authenticated" 874 "authenticated-blake2" 875 "repokey" 876 "keyfile" 877 "repokey-blake2" 878 "keyfile-blake2" 879 ]; 880 default = "none"; 881 description = '' 882 The backup can be encrypted by choosing any other value than 'none'. 883 When using encryption the password / passphrase must be provided in passphraseFile. 884 ''; 885 }; 886 887 passphraseFile = mkOption { 888 type = types.nullOr types.path; 889 default = null; 890 description = "Path to a file containing the encryption password or passphrase."; 891 }; 892 }; 893 894 name = mkOption { 895 type = types.str; 896 default = "{hostname}-{user}-{now}"; 897 description = '' 898 The name of the individual backups as used by borg. 899 Certain placeholders will be replaced by borg. 900 ''; 901 }; 902 903 locations = mkOption { 904 type = types.listOf types.path; 905 default = [cfg.mailDirectory]; 906 description = "The locations that are to be backed up by borg."; 907 }; 908 909 extraArgumentsForInit = mkOption { 910 type = types.listOf types.str; 911 default = ["--critical"]; 912 description = "Additional arguments to add to the borg init command line."; 913 }; 914 915 extraArgumentsForCreate = mkOption { 916 type = types.listOf types.str; 917 default = [ ]; 918 description = "Additional arguments to add to the borg create command line e.g. '--stats'."; 919 }; 920 921 cmdPreexec = mkOption { 922 type = types.nullOr types.str; 923 default = null; 924 description = '' 925 The command to be executed before each backup operation. 926 This is called prior to borg init in the same script that runs borg init and create and cmdPostexec. 927 Example: 928 export BORG_RSH="ssh -i /path/to/private/key" 929 ''; 930 }; 931 932 cmdPostexec = mkOption { 933 type = types.nullOr types.str; 934 default = null; 935 description = '' 936 The command to be executed after each backup operation. 937 This is called after borg create completed successfully and in the same script that runs 938 cmdPreexec, borg init and create. 939 ''; 940 }; 941 942 }; 943 944 rebootAfterKernelUpgrade = { 945 enable = mkOption { 946 type = types.bool; 947 default = false; 948 example = true; 949 description = '' 950 Whether to enable automatic reboot after kernel upgrades. 951 This is to be used in conjunction with system.autoUpgrade.enable = true" 952 ''; 953 }; 954 method = mkOption { 955 type = types.enum [ "reboot" "systemctl kexec" ]; 956 default = "reboot"; 957 description = '' 958 Whether to issue a full "reboot" or just a "systemctl kexec"-only reboot. 959 It is recommended to use the default value because the quicker kexec reboot has a number of problems. 960 Also if your server is running in a virtual machine the regular reboot will already be very quick. 961 ''; 962 }; 963 }; 964 965 backup = { 966 enable = lib.mkEnableOption "backup via rsnapshot"; 967 968 snapshotRoot = mkOption { 969 type = types.path; 970 default = "/var/rsnapshot"; 971 description = '' 972 The directory where rsnapshot stores the backup. 973 ''; 974 }; 975 976 cmdPreexec = mkOption { 977 type = types.nullOr types.str; 978 default = null; 979 description = '' 980 The command to be executed before each backup operation. This is wrapped in a shell script to be called by rsnapshot. 981 ''; 982 }; 983 984 cmdPostexec = mkOption { 985 type = types.nullOr types.str; 986 default = null; 987 description = "The command to be executed after each backup operation. This is wrapped in a shell script to be called by rsnapshot."; 988 }; 989 990 retain = { 991 hourly = mkOption { 992 type = types.int; 993 default = 24; 994 description = "How many hourly snapshots are retained."; 995 }; 996 daily = mkOption { 997 type = types.int; 998 default = 7; 999 description = "How many daily snapshots are retained."; 1000 }; 1001 weekly = mkOption { 1002 type = types.int; 1003 default = 54; 1004 description = "How many weekly snapshots are retained."; 1005 }; 1006 }; 1007 1008 cronIntervals = mkOption { 1009 type = types.attrsOf types.str; 1010 default = { 1011 # minute, hour, day-in-month, month, weekday (0 = sunday) 1012 hourly = " 0 * * * *"; # Every full hour 1013 daily = "30 3 * * *"; # Every day at 3:30 1014 weekly = " 0 5 * * 0"; # Every sunday at 5:00 AM 1015 }; 1016 description = '' 1017 Periodicity at which intervals should be run by cron. 1018 Note that the intervals also have to exist in configuration 1019 as retain options. 1020 ''; 1021 }; 1022 }; 1023 }; 1024 1025 imports = [ 1026 ./borgbackup.nix 1027 ./debug.nix 1028 ./rsnapshot.nix 1029 ./clamav.nix 1030 ./monit.nix 1031 ./users.nix 1032 ./environment.nix 1033 ./networking.nix 1034 ./systemd.nix 1035 ./dovecot.nix 1036 ./opendkim.nix 1037 ./postfix.nix 1038 ./rspamd.nix 1039 ./nginx.nix 1040 ./kresd.nix 1041 ./post-upgrade-check.nix 1042 ]; 1043}