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}