e13736db67
Create a nicer option structure that deals with the mail storage and its owner, uid, group and gid. Also includes the directory layout as a property of how mails are stored..
1755 lines
58 KiB
Nix
1755 lines
58 KiB
Nix
# nixos-mailserver: a simple mail server
|
|
# Copyright (C) 2016-2018 Robin Raymond
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
inherit (lib)
|
|
literalExpression
|
|
literalMD
|
|
mkChangedOptionModule
|
|
mkEnableOption
|
|
mkOption
|
|
mkOptionType
|
|
mkRemovedOptionModule
|
|
mkRenamedOptionModule
|
|
types
|
|
;
|
|
|
|
cfg = config.mailserver;
|
|
in
|
|
{
|
|
options.mailserver = {
|
|
enable = mkEnableOption "nixos-mailserver";
|
|
|
|
enableNixpkgsReleaseCheck = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = ''
|
|
Whether to check for a release mismatch between NixOS mailserver and
|
|
Nixpkgs.
|
|
|
|
Using mismatched versions is likely to cause compatibility issues
|
|
and may require migrations that make an eventual rollback tricky.
|
|
|
|
It is therefore highly recommended to use a release of NixOS mailserver
|
|
that corresponds with your chosen release of Nixpkgs.
|
|
'';
|
|
};
|
|
|
|
stateVersion = mkOption {
|
|
type = types.nullOr types.ints.positive;
|
|
default = null;
|
|
description = ''
|
|
Tracking stateful version changes as an incrementing number.
|
|
|
|
When a new release comes out we may require manual migration steps to
|
|
be completed, before the new version can be put into production.
|
|
|
|
If your `stateVersion` is too low one or multiple assertions may
|
|
trigger to give you instructions on what migrations steps are required
|
|
to continue. Increase the `stateVersion` as instructed by the assertion
|
|
message.
|
|
'';
|
|
};
|
|
|
|
openFirewall = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Automatically open ports in the firewall.";
|
|
};
|
|
|
|
fqdn = mkOption {
|
|
type = types.str;
|
|
example = "mx.example.com";
|
|
description = "The fully qualified domain name of the mail server.";
|
|
};
|
|
|
|
systemName = mkOption {
|
|
type = types.str;
|
|
default = "${cfg.systemDomain} mail system";
|
|
defaultText = literalExpression "\${config.mailserver.systemDomain} mail system";
|
|
example = "ACME Corp.";
|
|
description = ''
|
|
The sender name given in automated reports.
|
|
'';
|
|
};
|
|
|
|
systemContact = mkOption {
|
|
type = types.str;
|
|
example = "postmaster@example.com";
|
|
description = ''
|
|
The email address where the administrative contact for this mail server is reachable.
|
|
|
|
Currently, this is only required when one of the following features is enabled:
|
|
- SMTP TLS reports (`mailserver.tlsrpt.enable`)
|
|
'';
|
|
};
|
|
|
|
systemDomain = mkOption {
|
|
type = types.str;
|
|
default =
|
|
if (config.networking.domain != null && lib.elem config.networking.domain cfg.domains) then
|
|
config.networking.domain
|
|
else
|
|
lib.head cfg.domains;
|
|
defaultText = literalExpression ''
|
|
if config.networking.domain != null && lib.elem config.networking.domain cfg.domains then
|
|
config.networking.domain
|
|
else
|
|
lib.head cfg.domains
|
|
'';
|
|
example = literalExpression "config.networking.domain";
|
|
description = ''
|
|
The primary domain used for sending automated reports.
|
|
'';
|
|
};
|
|
|
|
domains = mkOption {
|
|
type = types.listOf types.str;
|
|
example = [ "example.com" ];
|
|
default = [ ];
|
|
description = "The domains that this mail server serves.";
|
|
};
|
|
|
|
messageSizeLimit = mkOption {
|
|
type = types.int;
|
|
example = 52428800;
|
|
default = 20971520;
|
|
description = "Message size limit enforced by Postfix.";
|
|
};
|
|
|
|
accounts = mkOption {
|
|
type = types.attrsOf (
|
|
types.submodule (
|
|
{ name, ... }:
|
|
{
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
default = name;
|
|
example = "user1@example.com";
|
|
readOnly = true;
|
|
internal = true;
|
|
description = ''
|
|
The login username for this account.
|
|
'';
|
|
};
|
|
|
|
hashedPassword = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
example = "$y$j9T$vfGrwkAaXCjCEWtVNMQck1$383uIXQmn2z0hnmVAA8kwFQmjNj78.nYbvWeyNLIaP1";
|
|
description = ''
|
|
The hashed login password for this account.
|
|
|
|
Use `mkpasswd` to create password hashes:
|
|
```
|
|
nix-shell -p mkpasswd --run 'mkpasswd -s'
|
|
```
|
|
|
|
:::{note}
|
|
This is a convenience option, when your threat model allows
|
|
storing hashed secrets in the world-readable Nix store.
|
|
|
|
Passing the hash through
|
|
{option}`mailserver.accounts.<name>.hashedPasswordFile`
|
|
allows relying on filesystem discretionary access control as
|
|
another security boundary.
|
|
:::
|
|
'';
|
|
};
|
|
|
|
hashedPasswordFile = mkOption {
|
|
type = with types; nullOr path;
|
|
default = null;
|
|
example = "/run/keys/user1-pw-hash";
|
|
description = ''
|
|
The hashed login password for this account read from a file.
|
|
|
|
Use `mkpasswd to create password hashes:
|
|
```
|
|
nix-shell -p mkpasswd --run 'mkpasswd -s'
|
|
```
|
|
'';
|
|
};
|
|
|
|
passwordFile = mkOption {
|
|
type =
|
|
with types;
|
|
nullOr (pathWith {
|
|
inStore = false;
|
|
});
|
|
default = null;
|
|
example = "/run/keys/user1-pw";
|
|
description = ''
|
|
The plaintext login password for this account read from a file.
|
|
|
|
:::{note}
|
|
The password is hashed before it is passed on to Dovecot.
|
|
:::
|
|
'';
|
|
};
|
|
|
|
aliases = mkOption {
|
|
type = with types; listOf types.str;
|
|
example = [
|
|
"abuse@example.com"
|
|
"postmaster@example.com"
|
|
];
|
|
default = [ ];
|
|
description = ''
|
|
List of additional mail addresses (aliases) that get routed to this account.
|
|
|
|
:::{admonition} Catch-all with sending permissions
|
|
:class: tip
|
|
Configure `@example.com` to create a catch-all for this domain
|
|
that also allows sending from all addresses.
|
|
:::
|
|
'';
|
|
};
|
|
|
|
aliasesRegexp = mkOption {
|
|
type = with types; listOf types.str;
|
|
example = [ ''/^tom\..*@domain\.com$/'' ];
|
|
default = [ ];
|
|
description = ''
|
|
Same as {option}`mailserver.accounts.<name>.aliases` but
|
|
using PCRE (Perl compatible regex).
|
|
'';
|
|
};
|
|
|
|
catchAll = mkOption {
|
|
type = with types; listOf (enum cfg.domains);
|
|
example = [
|
|
"example.com"
|
|
"example2.com"
|
|
];
|
|
default = [ ];
|
|
description = ''
|
|
For which domains should this account act as a catch all?
|
|
|
|
:::{warning}
|
|
Does not allow sending from all addresses of these domains.
|
|
Use {option}`mailserver.accounts.<name>.aliases` if that
|
|
is required.
|
|
:::
|
|
'';
|
|
};
|
|
|
|
quota = mkOption {
|
|
type = with types; nullOr types.str;
|
|
default = null;
|
|
example = "2G";
|
|
description = ''
|
|
Per user quota rules. Accepted sizes are `xx k/M/G/T` with the
|
|
obvious meaning. Leave blank for the standard quota `100G`.
|
|
'';
|
|
};
|
|
|
|
sieveScript = mkOption {
|
|
type = with types; nullOr lines;
|
|
default = null;
|
|
example = ''
|
|
require ["fileinto", "mailbox"];
|
|
|
|
if address :is "from" "gitlab@mg.gitlab.com" {
|
|
fileinto :create "GitLab";
|
|
stop;
|
|
}
|
|
|
|
# This must be the last rule, it will check if list-id is set, and
|
|
# file the message into the Lists folder for further investigation
|
|
elsif header :matches "list-id" "<?*>" {
|
|
fileinto :create "Lists";
|
|
stop;
|
|
}
|
|
'';
|
|
description = ''
|
|
Per-user sieve script.
|
|
'';
|
|
};
|
|
|
|
sendOnly = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Specifies if the account should be a send-only account.
|
|
|
|
Emails sent to send-only accounts will
|
|
be rejected with the reason configured in
|
|
{option}`mailserver.accounts.<name>.sendOnlyRejectMessage`.
|
|
'';
|
|
};
|
|
|
|
sendOnlyRejectMessage = mkOption {
|
|
type = types.str;
|
|
default = "This account cannot receive emails.";
|
|
description = ''
|
|
The message returned to the sender for a send-only account.
|
|
|
|
See {option}`mailserver.accounts.<name>.sendOnly`.
|
|
'';
|
|
};
|
|
};
|
|
}
|
|
)
|
|
);
|
|
example = lib.literalExpression ''
|
|
{
|
|
user1 = {
|
|
# This password hash leaks into the Nix store
|
|
hashedPassword = "$y$j9T$y6eZ1o.IvVNfdGMAsUEvh1$6K/llP52uw2iDh4iSwtAn54/JYy7FzCcoCHmjmx00H5";
|
|
};
|
|
user2 = {
|
|
# Hashed password passed as a file
|
|
hashedPasswordFile = "/run/keys/user2-pw-hash";
|
|
};
|
|
user3 = {
|
|
# Plaintext password file
|
|
passwordFile = "/run/keys/user3-pw";
|
|
};
|
|
}
|
|
'';
|
|
description = ''
|
|
Attribute set of mail accounts.
|
|
|
|
Each entry defines a mailbox and login credentials, where the attribute
|
|
name is used as the login username and optionally routed mail address.
|
|
|
|
Use `mkpasswd` to generate password hashes.
|
|
'';
|
|
default = { };
|
|
};
|
|
|
|
ldap = {
|
|
enable = mkEnableOption "LDAP support";
|
|
|
|
uris = mkOption {
|
|
type = types.listOf types.str;
|
|
example = literalExpression ''
|
|
[
|
|
"ldaps://ldap1.example.com"
|
|
"ldaps://ldap2.example.com"
|
|
]
|
|
'';
|
|
description = ''
|
|
List of LDAP server URIs. Multiple can be specified.
|
|
|
|
Use `ldaps://` for implicit TLS or `ldap://` for a plain connection. See
|
|
also {option}`mailserver.ldap.startTls` to enable StartTLS on plain
|
|
connections.
|
|
'';
|
|
};
|
|
|
|
startTls = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to enable StartTLS on ``ldap://`` connections.
|
|
'';
|
|
};
|
|
|
|
caFile = mkOption {
|
|
type = types.path;
|
|
default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
|
defaultText = lib.literalExpression "\${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
|
description = ''
|
|
Bundle of CA certificates used to authenticate the LDAP server certificate.
|
|
'';
|
|
};
|
|
|
|
bind = {
|
|
dn = mkOption {
|
|
type = types.str;
|
|
example = "cn=mail,ou=accounts,dc=example,dc=com";
|
|
description = ''
|
|
DN used to bind against the LDAP server.
|
|
|
|
The server uses this account to lookup and filter accounts.
|
|
'';
|
|
};
|
|
|
|
passwordFile = mkOption {
|
|
type = types.pathWith { inStore = false; };
|
|
example = "/run/my-secret";
|
|
description = ''
|
|
File containing the password required to bind against the LDAP server.
|
|
'';
|
|
};
|
|
};
|
|
|
|
base = mkOption {
|
|
type = types.str;
|
|
example = "ou=people,ou=accounts,dc=example,dc=com";
|
|
description = ''
|
|
Base DN below which user accounts are searched for.
|
|
'';
|
|
};
|
|
|
|
scope = mkOption {
|
|
type = types.enum [
|
|
"base"
|
|
"one"
|
|
"sub"
|
|
];
|
|
default = "sub";
|
|
description = ''
|
|
Search scope relative to the {option}`mailserver.ldap.base`.
|
|
|
|
- base: Only the exact Base DN
|
|
- one: Immediate child entries of the Base DN, but not the Base DN itself.
|
|
- sub: Base DN and all descendant entries at any depth.
|
|
|
|
In practice only `one` or `sub` are suitable for multiple LDAP users.
|
|
'';
|
|
};
|
|
|
|
attributes = {
|
|
uuid = mkOption {
|
|
type = types.str;
|
|
default = "entryUUID";
|
|
example = "uuid";
|
|
description = ''
|
|
The long-term stable LDAP attribute to reference accounts across
|
|
username changes. Used to determine a stable Dovecot home and
|
|
mail directory location.
|
|
|
|
Typically the `entryUUID` attribute as defined by [RFC4530].
|
|
|
|
[RFC4530]: https://www.rfc-editor.org/rfc/rfc4530.html
|
|
'';
|
|
};
|
|
|
|
username = mkOption {
|
|
type = types.str;
|
|
default = "uid";
|
|
example = "name";
|
|
description = ''
|
|
The LDAP attribute referencing the username used to login with.
|
|
|
|
Typically the `uid` attribute which is part of the `inetOrgPerson` schema.
|
|
'';
|
|
};
|
|
|
|
password = mkOption {
|
|
type = types.str;
|
|
default = "userPassword";
|
|
example = "unix_password";
|
|
description = ''
|
|
The LDAP attribute referencing the account password used to login with.
|
|
|
|
Typically the `userPassword` attribute which is part of the `inetOrgPerson` schema.
|
|
'';
|
|
};
|
|
|
|
mail = mkOption {
|
|
type = types.str;
|
|
default = "mail";
|
|
example = "maildrop";
|
|
description = ''
|
|
The attribute name used for looking up accounts by mail address.
|
|
|
|
Typically this can be the `mail` attribute from the `inetOrgPerson`
|
|
schema, or the `maildrop` attribute from the unofficial Postfix
|
|
schema.
|
|
'';
|
|
};
|
|
};
|
|
|
|
dovecot = {
|
|
userFilter = mkOption {
|
|
type = types.str;
|
|
default = with cfg.ldap.attributes; "(|(${mail}=%{user})(${username}=%{user}))";
|
|
defaultText = literalExpression ''
|
|
with config.mailserver.ldap.attributes; "(|(''${mail}=%{user})(''${username}=%{user}))";
|
|
'';
|
|
example = "(|(mail=%{user})(uid=%{user}))";
|
|
description = ''
|
|
LDAP filter used for LMTP delivery from Postfix and post-login
|
|
information construction, like the home directory.
|
|
|
|
See the [user_filter] reference at in the Dovecot manual.
|
|
|
|
[user_filter]: https://doc.dovecot.org/2.3/configuration_manual/authentication/ldap_settings_auth/#user-filter
|
|
'';
|
|
};
|
|
|
|
passFilter = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = with cfg.ldap.attributes; "${username}=%{user}";
|
|
defaultText = lib.literalExpression ''
|
|
with config.mailserver.ldap.attributes; "''${username}=%{user}";
|
|
'';
|
|
example =
|
|
with cfg.ldap.attributes;
|
|
"(&(memberOf=cn=mail_users,ou=groups,dc=example,dc=com)(${username}=%{user}))";
|
|
description = ''
|
|
LDAP filter used to restrict which users are eligible to
|
|
authenticate against Dovecot.
|
|
|
|
See the [pass_filter] reference in the Dovecot manual.
|
|
|
|
[pass_filter]: https://doc.dovecot.org/2.3/configuration_manual/authentication/ldap_settings_auth/#pass-filter
|
|
'';
|
|
};
|
|
};
|
|
|
|
postfix = {
|
|
filter = mkOption {
|
|
type = types.str;
|
|
default = with cfg.ldap.attributes; "${mail}=%s";
|
|
defaultText = lib.literalExpression ''
|
|
with config.mailserver.ldap.attributes; "''${mail}=%s";
|
|
'';
|
|
example = "(mail=%s)";
|
|
description = ''
|
|
LDAP filter used to search for an account by mail, where `%s` is a
|
|
substitute for the address in question.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
indexDir = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = ''
|
|
Folder to store search indices. If null, indices are stored
|
|
along with email, which could not necessarily be desirable,
|
|
especially when {option}`mailserver.fullTextSearch.enable` is `true` since
|
|
indices it creates are voluminous and do not need to be backed
|
|
up.
|
|
|
|
Be careful when changing this option value since all indices
|
|
would be recreated at the new location (and clients would need
|
|
to resynchronize).
|
|
|
|
Note the some variables can be used in the file path. See
|
|
https://doc.dovecot.org/2.3/configuration_manual/mail_location/#variables
|
|
for details.
|
|
'';
|
|
example = "/var/lib/dovecot/indices";
|
|
};
|
|
|
|
fullTextSearch = {
|
|
enable = mkEnableOption ''
|
|
Full text search indexing with Xapian through the fts_flatcurve plugin.
|
|
This has significant performance and disk space cost.
|
|
'';
|
|
memoryLimit = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
example = 2000;
|
|
description = ''
|
|
Memory limit for the indexer process, in MiB.
|
|
If null, leaves the default (which is rather low),
|
|
and if 0, no limit.
|
|
'';
|
|
};
|
|
|
|
autoIndex = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable automatic indexing of messages as they are received or modified.";
|
|
};
|
|
autoIndexExclude = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ ];
|
|
example = [
|
|
"\\Trash"
|
|
"SomeFolder"
|
|
"Other/*"
|
|
];
|
|
description = ''
|
|
Mailboxes to exclude from automatic indexing.
|
|
'';
|
|
};
|
|
|
|
enforced = mkOption {
|
|
type = types.enum [
|
|
"yes"
|
|
"no"
|
|
"body"
|
|
];
|
|
default = "no";
|
|
description = ''
|
|
Fail searches when no index is available. If set to
|
|
`body`, then only body searches (as opposed to
|
|
header) are affected. If set to `no`, searches may
|
|
fall back to a very slow brute force search.
|
|
'';
|
|
};
|
|
|
|
languages = mkOption {
|
|
type = types.nonEmptyListOf types.str;
|
|
default = [ "en" ];
|
|
example = [
|
|
"en"
|
|
"de"
|
|
];
|
|
description = ''
|
|
A list of languages that the full text search should detect.
|
|
At least one language must be specified.
|
|
The language listed first is the default and is used when language recognition fails.
|
|
See <https://doc.dovecot.org/main/core/plugins/fts.html#fts_languages>.
|
|
'';
|
|
};
|
|
|
|
substringSearch = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
If enabled, allows substring searches.
|
|
See <https://doc.dovecot.org/main/core/plugins/fts_flatcurve.html#fts_flatcurve_substring_search>.
|
|
|
|
Enabling this requires significant additional storage space.
|
|
'';
|
|
};
|
|
|
|
headerExcludes = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [
|
|
"Received"
|
|
"DKIM-*"
|
|
"X-*"
|
|
"Comments"
|
|
];
|
|
description = ''
|
|
The list of headers to exclude.
|
|
See <https://doc.dovecot.org/main/core/plugins/fts.html#fts_header_excludes>.
|
|
'';
|
|
};
|
|
|
|
filters = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [
|
|
"normalizer-icu"
|
|
"snowball"
|
|
"stopwords"
|
|
];
|
|
description = ''
|
|
The list of filters to apply.
|
|
<https://doc.dovecot.org/main/core/plugins/fts.html#filter-configuration>.
|
|
'';
|
|
};
|
|
};
|
|
|
|
lmtpSaveToDetailMailbox = mkOption {
|
|
type = types.enum [
|
|
"yes"
|
|
"no"
|
|
];
|
|
default = "yes";
|
|
description = ''
|
|
If an email address is delimited by a "+", should it be filed into a
|
|
mailbox matching the string after the "+"? For example,
|
|
user1+test@example.com would be filed into the mailbox "test".
|
|
'';
|
|
};
|
|
|
|
lmtpMemoryLimit = mkOption {
|
|
type = types.int;
|
|
default = 256;
|
|
description = ''
|
|
The memory limit for the LMTP service, in megabytes.
|
|
'';
|
|
};
|
|
|
|
quotaStatusMemoryLimit = mkOption {
|
|
type = types.int;
|
|
default = 256;
|
|
description = ''
|
|
The memory limit for the quota-status service, in megabytes.
|
|
'';
|
|
};
|
|
|
|
aliases = mkOption {
|
|
type =
|
|
let
|
|
account = mkOptionType {
|
|
name = "Login Account";
|
|
check = account: builtins.elem account (builtins.attrNames cfg.accounts);
|
|
};
|
|
in
|
|
with types;
|
|
attrsOf (either account (nonEmptyListOf account));
|
|
example = {
|
|
"postmaster@example.com" = "user1@example.com";
|
|
"abuse@example.com" = "user1@example.com";
|
|
"multi@example.com" = [
|
|
"user1@example.com"
|
|
"user2@example.com"
|
|
];
|
|
};
|
|
description = ''
|
|
Aliases are additional mail addresses routed to one or more existing local accounts.
|
|
|
|
The target accounts are allowed to use the alias as the sender address.
|
|
|
|
:::{note}
|
|
This feature is limited to local accounts and does not support LDAP or
|
|
other external accounts.
|
|
:::
|
|
'';
|
|
default = { };
|
|
};
|
|
|
|
forwards = mkOption {
|
|
type = with types; attrsOf (either (listOf str) str);
|
|
example = {
|
|
"user@example.com" = "user@example.edu";
|
|
"gamenight@example.com" = [
|
|
"bob@example.com"
|
|
"frank@example.org"
|
|
"wendy@example.net"
|
|
];
|
|
};
|
|
description = ''
|
|
Forwards route mail from local addresses to one or more local or external addresses.
|
|
|
|
Unlike {option}`mailserver.aliases`, the target addresses cannot send
|
|
mail using the forward address.
|
|
'';
|
|
default = { };
|
|
};
|
|
|
|
rejectSender = mkOption {
|
|
type = types.listOf types.str;
|
|
example = [
|
|
"example.com"
|
|
"spammer@example.net"
|
|
];
|
|
description = ''
|
|
Reject emails from these addresses from unauthorized senders.
|
|
Use if a spammer is using the same domain or the same sender over and over.
|
|
'';
|
|
default = [ ];
|
|
};
|
|
|
|
rejectSenderMessage = mkOption {
|
|
type = types.str;
|
|
default = "";
|
|
example = "Your e-mail has not been delivered because we have blocked your e-mai address. If you believe that your e-mail address has been blocked by mistake, or if you have any other legitimate concern, please contact <address>.";
|
|
description = ''
|
|
SMTP message returned to rejected senders. If not set the Postfix
|
|
default will be used.
|
|
|
|
The message must be a single line and typically much shorter than 512
|
|
characters.
|
|
|
|
This could for example be used to provide a contact method (postal
|
|
address, phone or alternative email) so rejected senders can exercise
|
|
their [Art. 21 GDPR] right to object.
|
|
|
|
It is good practice to inform senders in advance that their email
|
|
addresses may be processed for this purpose in accordance with [Art. 13
|
|
GDPR]. Storing their mail address for this purpose is generally
|
|
regarded as a legitimate interest.
|
|
|
|
[Art. 13 GDPR]: https://eur-lex.europa.eu/eli/reg/2016/679/oj/eng#:~:text=Article%2013
|
|
[Art. 21 GDPR]: https://eur-lex.europa.eu/eli/reg/2016/679/oj/eng#:~:text=Article%2021
|
|
'';
|
|
};
|
|
|
|
rejectRecipients = mkOption {
|
|
type = types.listOf types.str;
|
|
example = [
|
|
"sales@example.com"
|
|
"info@example.com"
|
|
];
|
|
description = ''
|
|
Reject emails addressed to these local addresses from unauthorized senders.
|
|
Use if a spammer has found email addresses in a catchall domain but you do
|
|
not want to disable the catchall.
|
|
'';
|
|
default = [ ];
|
|
};
|
|
|
|
storage = {
|
|
path = mkOption {
|
|
type = types.path;
|
|
default = "/var/vmail";
|
|
description = ''
|
|
Path on disk where mail home directories are stored.
|
|
'';
|
|
};
|
|
|
|
directoryLayout = mkOption {
|
|
type = types.enum [
|
|
"fs"
|
|
"maildir++"
|
|
];
|
|
default = "maildir++";
|
|
description = ''
|
|
Sets whether dovecot should organize mail in subdirectories:
|
|
|
|
- /var/vmail/example.com/user/.folder.subfolder/ (Maildir++ layout)
|
|
- /var/vmail/example.com/user/folder/subfolder/ (FS layout)
|
|
|
|
See <https://doc.dovecot.org/main/core/config/mailbox_formats/maildir.html#directory-layout>
|
|
See https://doc.dovecot.org/main/core/config/mailbox_formats/maildir.html#maildir-mailbox-format for details.
|
|
'';
|
|
};
|
|
|
|
uid = mkOption {
|
|
type = types.ints.positive;
|
|
default = 5000;
|
|
description = ''
|
|
The user id assigned to the vmail user.
|
|
|
|
This user owns the mail storage files and directories and is used by
|
|
services accessing the mail store.
|
|
|
|
:::{warning}
|
|
If you change this value you also need to manually adjust the
|
|
permissions of your :option:`mailserver.storage.path`.
|
|
:::
|
|
'';
|
|
};
|
|
|
|
owner = mkOption {
|
|
type = types.str;
|
|
default = "virtualMail";
|
|
description = ''
|
|
The name of the user that owns the :option:`mailserver.storage.path`.
|
|
'';
|
|
};
|
|
|
|
gid = mkOption {
|
|
type = types.ints.positive;
|
|
default = 5000;
|
|
description = ''
|
|
The group id of the primary group of the vmail user.
|
|
|
|
This group owns the mail storage directories. Access can be delegated
|
|
to other users via group membership.
|
|
|
|
:::{warning}
|
|
If you change this value you also need to manually adjust the
|
|
permissions of your :option:`mailserver.storage.path`.
|
|
:::
|
|
'';
|
|
};
|
|
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "virtualMail";
|
|
description = ''
|
|
The primary group name of the user that owns the
|
|
:option:`mailserver.storage.path`.
|
|
'';
|
|
};
|
|
};
|
|
|
|
useUTF8FolderNames = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Store mailbox names on disk using UTF-8 instead of modified UTF-7 (mUTF-7).
|
|
'';
|
|
};
|
|
|
|
hierarchySeparator = mkOption {
|
|
type = types.str;
|
|
default = ".";
|
|
description = ''
|
|
The hierarchy separator for mailboxes used by dovecot for the namespace 'inbox'.
|
|
Dovecot defaults to "." but recommends "/".
|
|
This affects how mailboxes appear to mail clients and sieve scripts.
|
|
For instance when using "." then in a sieve script "example.com" would refer to the mailbox "com" in the parent mailbox "example".
|
|
This does not determine the way your mails are stored on disk.
|
|
See https://doc.dovecot.org/main/core/config/namespaces.html#namespaces for details.
|
|
'';
|
|
};
|
|
|
|
mailboxes = mkOption {
|
|
description = ''
|
|
The mailboxes for dovecot.
|
|
Depending on the mail client used it might be necessary to change some mailbox's name.
|
|
'';
|
|
default = {
|
|
Trash = {
|
|
auto = "no";
|
|
specialUse = "Trash";
|
|
};
|
|
Junk = {
|
|
auto = "subscribe";
|
|
specialUse = "Junk";
|
|
};
|
|
Drafts = {
|
|
auto = "subscribe";
|
|
specialUse = "Drafts";
|
|
};
|
|
Sent = {
|
|
auto = "subscribe";
|
|
specialUse = "Sent";
|
|
};
|
|
};
|
|
};
|
|
|
|
x509 = {
|
|
useACMEHost = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
example = literalExpression "config.mailserver.fqdn";
|
|
description = ''
|
|
Common name used in the relevant `security.acme.certs` configuration.
|
|
|
|
Mutually exclusive with {option}`mailserver.x509.certificateFile` and {option}`mailserver.x509.privateKeyFile`.
|
|
'';
|
|
};
|
|
|
|
certificateFile = mkOption {
|
|
type = with types; nullOr path;
|
|
default = null;
|
|
example = "/var/keys/certs/fullchain.pem";
|
|
description = ''
|
|
Path to the signed X509 certificate including intermediate certificates.
|
|
|
|
This is commonly referred to as {file}`fullchain.pem`.
|
|
|
|
Mutually exclusive with {option}`mailserver.x509.useACMEHost`.
|
|
'';
|
|
};
|
|
|
|
privateKeyFile = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
example = "/var/keys/certs/privkey.pem";
|
|
description = ''
|
|
Path to the X509 private key.
|
|
|
|
This is commonly referred to as {file}`privkey.pem`.
|
|
|
|
Mutually exclusive with {option}`mailserver.x509.useACMEHost`.
|
|
'';
|
|
};
|
|
};
|
|
|
|
enableImap = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to enable IMAP with STARTTLS on port 143.
|
|
|
|
The use of this port is deprecated per RFC 8314 4.1.
|
|
'';
|
|
};
|
|
|
|
imapMemoryLimit = mkOption {
|
|
type = types.int;
|
|
default = 256;
|
|
description = ''
|
|
The memory limit for the imap service, in megabytes.
|
|
'';
|
|
};
|
|
|
|
enableImapSsl = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = ''
|
|
Whether to enable IMAP with TLS in wrapper-mode on port 993.
|
|
'';
|
|
};
|
|
|
|
enableSubmission = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to enable SMTP with STARTTLS on port 587.
|
|
|
|
The use of this port is discouraged per RFC 8314 3.3, see also Appendix A.
|
|
'';
|
|
};
|
|
|
|
enableSubmissionSsl = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = ''
|
|
Whether to enable SMTP with TLS in wrapper-mode on port 465.
|
|
'';
|
|
};
|
|
|
|
enablePop3 = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to enable POP3 with STARTTLS on port on port 110.
|
|
|
|
The use of this port is deprecated per RFC 8314 4.1.
|
|
'';
|
|
};
|
|
|
|
enablePop3Ssl = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to enable POP3 with TLS in wrapper-mode on port 995.
|
|
'';
|
|
};
|
|
|
|
enableManageSieve = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to enable ManageSieve, setting this option to true will open
|
|
port 4190 in the firewall.
|
|
|
|
The ManageSieve protocol allows users to manage their Sieve scripts on
|
|
a remote server with a supported client, including Thunderbird.
|
|
'';
|
|
};
|
|
|
|
sieveDirectory = mkOption {
|
|
type = types.path;
|
|
default = "/var/sieve";
|
|
description = ''
|
|
Where to store the sieve scripts.
|
|
'';
|
|
};
|
|
|
|
virusScanning = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to activate virus scanning. Note that virus scanning is _very_
|
|
expensive memory wise.
|
|
'';
|
|
};
|
|
|
|
dkim = {
|
|
enable = mkEnableOption "DKIM signing" // {
|
|
default = true;
|
|
};
|
|
|
|
keyDirectory = mkOption {
|
|
type = types.path;
|
|
default = "/var/dkim";
|
|
description = ''
|
|
The path where DKIM siging keys are stored.
|
|
'';
|
|
};
|
|
|
|
defaults = {
|
|
selector = mkOption {
|
|
type = types.str;
|
|
default = "mail";
|
|
description = ''
|
|
The default selector used to reference and lookup DKIM keys.
|
|
|
|
This value should most likely not be changed. Instead manage
|
|
{option}`mailserver.dkim.domains.<name>.selectors` to sign with one
|
|
or multiple DKIM key pairs and manage migrations.
|
|
'';
|
|
};
|
|
|
|
keyType = mkOption {
|
|
type = types.enum [
|
|
"rsa"
|
|
"ed25519"
|
|
];
|
|
default = "rsa";
|
|
description = ''
|
|
The key type used for generating DKIM keys. Ed25519 support was
|
|
introduced in RFC6376 (2018).
|
|
|
|
:::{warning}
|
|
Ed25519 DKIM keys are currently not recommended for sole use, as
|
|
various DKIM validators out there lack support and consider the
|
|
keypair invalid.
|
|
:::
|
|
|
|
This value should most likely not be changed. Once DKIM keys for
|
|
domain and selector are generated changing this value will not
|
|
regenerate the keypair. Instead create a new selector and configure
|
|
{option}`mailserver.dkim.domains.<name>.selectors.<name>.keyType`.
|
|
'';
|
|
};
|
|
|
|
keyLength = mkOption {
|
|
type = types.int;
|
|
default = 2048;
|
|
description = ''
|
|
The default key length used for generating new DKIM keys.
|
|
|
|
Only applies for RSA keys, Ed25519 keys use a fixed key length.
|
|
|
|
Per [RFC8301 3.2] the minimum RSA key length should be at least
|
|
2048 bit.
|
|
|
|
This value should most likely not be changed. Once DKIM keys for
|
|
domain and selector are generated changing this value will not
|
|
regenerate the keypair. Instead create a new selector and configure
|
|
{option}`mailserver.dkim.domains.<name>.selectors.<name>.keyLength`.
|
|
|
|
[RFC8301 3.2]: https://datatracker.ietf.org/doc/html/rfc8301#section-3.2
|
|
'';
|
|
};
|
|
};
|
|
|
|
domains = mkOption {
|
|
description = "DKIM configuration per domain.";
|
|
type = types.attrsOf (
|
|
types.submodule ({
|
|
options = {
|
|
selectors = mkOption {
|
|
description = ''
|
|
DKIM selectors used for signing outgoing mail for this domain.
|
|
|
|
When no selector is configured a default selector will be
|
|
created with settings inherited from {option}`mailserver.dkim.defaults <mailserver.dkim.defaults.keyLength>`.
|
|
'';
|
|
type = types.attrsOf (
|
|
types.submodule ({
|
|
options = {
|
|
keyType = mkOption {
|
|
type =
|
|
with types;
|
|
nullOr (enum [
|
|
"rsa"
|
|
"ed25519"
|
|
]);
|
|
default = null;
|
|
example = "rsa";
|
|
description = ''
|
|
The key type used for generating this DKIM keypair.
|
|
|
|
:::{warning}
|
|
Ed25519 DKIM keys are currently not recommended for sole use, as
|
|
various DKIM validators out there lack support and consider the
|
|
keypair invalid.
|
|
:::
|
|
|
|
This option is mutually exclusive with `keyFile`.
|
|
'';
|
|
};
|
|
|
|
keyLength = mkOption {
|
|
type = with types; nullOr int;
|
|
default = null;
|
|
example = 2048;
|
|
description = ''
|
|
The key length used for generating this DKIM key.
|
|
|
|
Only applies for RSA keys, Ed25519 keys use a fixed key size.
|
|
|
|
This option is mutually exclusive with `keyFile`.
|
|
'';
|
|
};
|
|
|
|
keyFile = mkOption {
|
|
type =
|
|
with types;
|
|
nullOr (pathWith {
|
|
inStore = false;
|
|
});
|
|
default = null;
|
|
example = "/run/keys/example.com-dkim-rsa-2026-03.key";
|
|
description = ''
|
|
Path to an existing DKIM private key file.
|
|
|
|
DKIM keys can be generated using `rspamadm dkim_keygen`.
|
|
|
|
This option is mutually exclusive with `keyType` and `keyLength`.
|
|
'';
|
|
};
|
|
};
|
|
})
|
|
);
|
|
default = { };
|
|
example = lib.literalExpression ''
|
|
{
|
|
"mail" = {
|
|
# inherit defaults from mailserver.dkim.defaults
|
|
};
|
|
"rsa-2026-03".keyFile = "/run/keys/example.com-dkim-rsa-2026-03.key";
|
|
};
|
|
'';
|
|
};
|
|
};
|
|
})
|
|
);
|
|
default = { };
|
|
example = lib.literalExpression ''
|
|
{
|
|
"example.com".selectors = {
|
|
"mail" = {
|
|
# inherit defaults from mailserver.dkim.defaults
|
|
};
|
|
"rsa-2026-03".keyFile = "/run/keys/example.com-dkim-rsa-2026-03.key";
|
|
};
|
|
};
|
|
'';
|
|
};
|
|
};
|
|
|
|
dmarcReporting = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to send out aggregated, daily DMARC reports in response to incoming
|
|
mail, when the sender domain defines a DMARC policy including the RUA tag.
|
|
|
|
This is helpful for the mail ecosystem, because it allows third parties to
|
|
get notified about SPF/DKIM violations originating from their sender domains.
|
|
|
|
See https://rspamd.com/doc/modules/dmarc.html#reporting
|
|
'';
|
|
};
|
|
|
|
excludeDomains = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ ];
|
|
description = ''
|
|
List of domains or eSLDs to be excluded from DMARC reports.
|
|
'';
|
|
};
|
|
};
|
|
|
|
tlsrpt.enable = mkEnableOption "delivery of SMTP TLS reports according to RFC 8460";
|
|
|
|
debug = {
|
|
all = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to enable verbose logging for all mailserver related services.
|
|
This intended be used for development purposes only, you probably
|
|
don't want to enable this unless you're hacking on nixos-mailserver.
|
|
'';
|
|
};
|
|
|
|
dovecot = mkOption {
|
|
type = types.bool;
|
|
default = cfg.debug.all;
|
|
defaultText = lib.literalExpression "config.mailserver.debug.all";
|
|
description = ''
|
|
Whether to enable verbose logging for Dovecot.
|
|
'';
|
|
};
|
|
|
|
rspamd = mkOption {
|
|
type = types.bool;
|
|
default = cfg.debug.all;
|
|
defaultText = lib.literalExpression "config.mailserver.debug.all";
|
|
description = ''
|
|
Whether to enable verbose logging for Rspamd.
|
|
'';
|
|
};
|
|
};
|
|
|
|
maxConnectionsPerUser = mkOption {
|
|
type = types.int;
|
|
default = 100;
|
|
description = ''
|
|
Maximum number of IMAP/POP3 connections allowed for a user from each IP address.
|
|
E.g. a value of 50 allows for 50 IMAP and 50 POP3 connections at the same
|
|
time for a single user.
|
|
'';
|
|
};
|
|
|
|
localDnsResolver = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = ''
|
|
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.
|
|
'';
|
|
};
|
|
|
|
recipientDelimiter = mkOption {
|
|
type = types.str;
|
|
default = "+";
|
|
description = ''
|
|
Configure the recipient delimiter.
|
|
'';
|
|
};
|
|
|
|
srs = {
|
|
enable = mkEnableOption "Sender Rewrite Scheme";
|
|
|
|
domain = mkOption {
|
|
type = with types; nullOr str;
|
|
default = config.mailserver.systemDomain;
|
|
defaultText = literalExpression "config.mailserver.systemDomain";
|
|
example = "srs.example.com";
|
|
description = ''
|
|
Mail domain used for ephemeral SRS envelope addresses.
|
|
|
|
:::{note}
|
|
This domain can only support relaxed SPF alignment.
|
|
:::
|
|
|
|
:::{important}
|
|
For privacy reasons you should use a dedicated domain when serving multiple unrelated domains.
|
|
:::
|
|
'';
|
|
};
|
|
};
|
|
|
|
redis = {
|
|
configureLocally = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = ''
|
|
Whether to provision a local Redis instance.
|
|
'';
|
|
};
|
|
|
|
address = mkOption {
|
|
type = types.str;
|
|
# read the default from nixos' redis module
|
|
default = config.services.redis.servers.rspamd.unixSocket;
|
|
defaultText = literalExpression "config.services.redis.servers.rspamd.unixSocket";
|
|
description = ''
|
|
Path, IP address or hostname that Rspamd should use to contact Redis.
|
|
'';
|
|
};
|
|
|
|
port = mkOption {
|
|
type = with types; nullOr port;
|
|
default = null;
|
|
example = literalExpression "config.services.redis.servers.rspamd.port";
|
|
description = ''
|
|
Port that Rspamd should use to contact Redis.
|
|
'';
|
|
};
|
|
|
|
password = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = config.services.redis.servers.rspamd.requirePass;
|
|
defaultText = literalExpression "config.services.redis.servers.rspamd.requirePass";
|
|
description = ''
|
|
Password that rspamd should use to contact redis, or null if not required.
|
|
'';
|
|
};
|
|
};
|
|
|
|
rewriteMessageId = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Rewrites the Message-ID's hostname-part of outgoing emails to the FQDN.
|
|
Please be aware that this may cause problems with some mail clients
|
|
relying on the original Message-ID.
|
|
'';
|
|
};
|
|
|
|
sendingFqdn = mkOption {
|
|
type = types.str;
|
|
default = cfg.fqdn;
|
|
defaultText = literalMD "{option}`mailserver.fqdn`";
|
|
example = "myserver.example.com";
|
|
description = ''
|
|
The fully qualified domain name of the mail server used to
|
|
identify with remote servers.
|
|
|
|
If this server's IP serves purposes other than a mail server,
|
|
it may be desirable for the server to have a name other than
|
|
that to which the user will connect. For example, the user
|
|
might connect to mx.example.com, but the server's IP has
|
|
reverse DNS that resolves to myserver.example.com; in this
|
|
scenario, some mail servers may reject or penalize the
|
|
message.
|
|
|
|
This setting allows the server to identify as
|
|
myserver.example.com when forwarding mail, independently of
|
|
{option}`mailserver.fqdn` (which, for SSL reasons, should generally be the name
|
|
to which the user connects).
|
|
|
|
Set this to the name to which the sending IP's reverse DNS
|
|
resolves.
|
|
'';
|
|
};
|
|
|
|
monitoring = {
|
|
enable = mkEnableOption "monitoring via monit";
|
|
|
|
alertAddress = mkOption {
|
|
type = types.str;
|
|
description = ''
|
|
The email address to send alerts to.
|
|
'';
|
|
};
|
|
|
|
config = mkOption {
|
|
type = types.str;
|
|
default = ''
|
|
set daemon 120 with start delay 60
|
|
set mailserver
|
|
localhost
|
|
|
|
set httpd port 2812 and use address localhost
|
|
allow localhost
|
|
allow admin:obwjoawijerfoijsiwfj29jf2f2jd
|
|
|
|
check filesystem root with path /
|
|
if space usage > 80% then alert
|
|
if inode usage > 80% then alert
|
|
|
|
check system $HOST
|
|
if cpu usage > 95% for 10 cycles then alert
|
|
if memory usage > 75% for 5 cycles then alert
|
|
if swap usage > 20% for 10 cycles then alert
|
|
if loadavg (1min) > 90 for 15 cycles then alert
|
|
if loadavg (5min) > 80 for 10 cycles then alert
|
|
if loadavg (15min) > 70 for 8 cycles then alert
|
|
|
|
check process sshd with pidfile /var/run/sshd.pid
|
|
start program "${pkgs.systemd}/bin/systemctl start sshd"
|
|
stop program "${pkgs.systemd}/bin/systemctl stop sshd"
|
|
if failed port 22 protocol ssh for 2 cycles then restart
|
|
|
|
check process postfix with pidfile /var/lib/postfix/queue/pid/master.pid
|
|
start program = "${pkgs.systemd}/bin/systemctl start postfix"
|
|
stop program = "${pkgs.systemd}/bin/systemctl stop postfix"
|
|
if failed port 25 protocol smtp for 5 cycles then restart
|
|
|
|
check process dovecot with pidfile /var/run/dovecot2/master.pid
|
|
start program = "${pkgs.systemd}/bin/systemctl start dovecot"
|
|
stop program = "${pkgs.systemd}/bin/systemctl stop dovecot"
|
|
if failed host ${cfg.fqdn} port 993 type tcpssl sslauto protocol imap for 5 cycles then restart
|
|
|
|
check process rspamd with matching "rspamd: main process"
|
|
start program = "${pkgs.systemd}/bin/systemctl start rspamd"
|
|
stop program = "${pkgs.systemd}/bin/systemctl stop rspamd"
|
|
'';
|
|
defaultText = literalMD "see [source](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/master/default.nix)";
|
|
description = ''
|
|
The configuration used for monitoring via monit.
|
|
Use a mail address that you actively check and set it via 'set alert ...'.
|
|
'';
|
|
};
|
|
};
|
|
|
|
borgbackup = {
|
|
enable = mkEnableOption "backup via borgbackup";
|
|
|
|
repoLocation = mkOption {
|
|
type = types.str;
|
|
default = "/var/borgbackup";
|
|
description = ''
|
|
The location where borg saves the backups.
|
|
This can be a local path or a remote location such as user@host:/path/to/repo.
|
|
It is exported and thus available as an environment variable to
|
|
{option}`mailserver.borgbackup.cmdPreexec` and {option}`mailserver.borgbackup.cmdPostexec`.
|
|
'';
|
|
};
|
|
|
|
startAt = mkOption {
|
|
type = types.str;
|
|
default = "hourly";
|
|
description = "When or how often the backup should run. Must be in the format described in systemd.time 7.";
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "virtualMail";
|
|
description = "The user borg and its launch script is run as.";
|
|
};
|
|
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "virtualMail";
|
|
description = "The group borg and its launch script is run as.";
|
|
};
|
|
|
|
compression = {
|
|
method = mkOption {
|
|
type = types.nullOr (
|
|
types.enum [
|
|
"none"
|
|
"lz4"
|
|
"zstd"
|
|
"zlib"
|
|
"lzma"
|
|
]
|
|
);
|
|
default = null;
|
|
description = "Leaving this unset allows borg to choose. The default for borg 1.1.4 is lz4.";
|
|
};
|
|
|
|
level = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = ''
|
|
Denotes the level of compression used by borg.
|
|
Most methods accept levels from 0 to 9 but zstd which accepts values from 1 to 22.
|
|
If null the decision is left up to borg.
|
|
'';
|
|
};
|
|
|
|
auto = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Leaves it to borg to determine whether an individual file should be compressed.";
|
|
};
|
|
};
|
|
|
|
encryption = {
|
|
method = mkOption {
|
|
type = types.enum [
|
|
"none"
|
|
"authenticated"
|
|
"authenticated-blake2"
|
|
"repokey"
|
|
"keyfile"
|
|
"repokey-blake2"
|
|
"keyfile-blake2"
|
|
];
|
|
default = "none";
|
|
description = ''
|
|
The backup can be encrypted by choosing any other value than 'none'.
|
|
When using encryption the password/passphrase must be provided in `passphraseFile`.
|
|
'';
|
|
};
|
|
|
|
passphraseFile = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
description = "Path to a file containing the encryption password or passphrase.";
|
|
};
|
|
};
|
|
|
|
name = mkOption {
|
|
type = types.str;
|
|
default = "{hostname}-{user}-{now}";
|
|
description = ''
|
|
The name of the individual backups as used by borg.
|
|
Certain placeholders will be replaced by borg.
|
|
'';
|
|
};
|
|
|
|
locations = mkOption {
|
|
type = types.listOf types.path;
|
|
default = [ cfg.storage.path ];
|
|
defaultText = literalExpression "[ config.mailserver.storage.path ]";
|
|
description = "The locations that are to be backed up by borg.";
|
|
};
|
|
|
|
extraArgumentsForInit = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ "--critical" ];
|
|
description = "Additional arguments to add to the borg init command line.";
|
|
};
|
|
|
|
extraArgumentsForCreate = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ ];
|
|
description = "Additional arguments to add to the borg create command line e.g. '--stats'.";
|
|
};
|
|
|
|
cmdPreexec = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = ''
|
|
The command to be executed before each backup operation.
|
|
This is called prior to borg init in the same script that runs borg init and create and `cmdPostexec`.
|
|
'';
|
|
example = ''
|
|
export BORG_RSH="ssh -i /path/to/private/key"
|
|
'';
|
|
};
|
|
|
|
cmdPostexec = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = ''
|
|
The command to be executed after each backup operation.
|
|
This is called after borg create completed successfully and in the same script that runs
|
|
`cmdPreexec`, borg init and create.
|
|
'';
|
|
};
|
|
|
|
};
|
|
|
|
backup = {
|
|
enable = mkEnableOption "backup via rsnapshot";
|
|
|
|
snapshotRoot = mkOption {
|
|
type = types.path;
|
|
default = "/var/rsnapshot";
|
|
description = ''
|
|
The directory where rsnapshot stores the backup.
|
|
'';
|
|
};
|
|
|
|
cmdPreexec = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = ''
|
|
The command to be executed before each backup operation. This is wrapped in a shell script to be called by rsnapshot.
|
|
'';
|
|
};
|
|
|
|
cmdPostexec = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "The command to be executed after each backup operation. This is wrapped in a shell script to be called by rsnapshot.";
|
|
};
|
|
|
|
retain = {
|
|
hourly = mkOption {
|
|
type = types.int;
|
|
default = 24;
|
|
description = "How many hourly snapshots are retained.";
|
|
};
|
|
daily = mkOption {
|
|
type = types.int;
|
|
default = 7;
|
|
description = "How many daily snapshots are retained.";
|
|
};
|
|
weekly = mkOption {
|
|
type = types.int;
|
|
default = 54;
|
|
description = "How many weekly snapshots are retained.";
|
|
};
|
|
};
|
|
|
|
cronIntervals = mkOption {
|
|
type = types.attrsOf types.str;
|
|
default = {
|
|
# minute, hour, day-in-month, month, weekday (0 = sunday)
|
|
hourly = " 0 * * * *"; # Every full hour
|
|
daily = "30 3 * * *"; # Every day at 3:30
|
|
weekly = " 0 5 * * 0"; # Every sunday at 5:00 AM
|
|
};
|
|
description = ''
|
|
Periodicity at which intervals should be run by cron.
|
|
Note that the intervals also have to exist in configuration
|
|
as retain options.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
imports = [
|
|
./mail-server
|
|
|
|
# NixOS 25.05
|
|
(mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "enable" ] ''
|
|
This option is not needed for fts-flatcurve
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "onCalendar" ] ''
|
|
This option is not needed for fts-flatcurve
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "randomizedDelaySec" ] ''
|
|
This option is not needed for fts-flatcurve
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "fullTextSearch" "minSize" ] ''
|
|
This option is not supported by fts-flatcurve
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maxSize" ] ''
|
|
This option is not needed since fts-xapian 1.8.3
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "fullTextSearch" "indexAttachments" ] ''
|
|
Text attachments are always indexed since fts-xapian 1.4.8
|
|
'')
|
|
(mkRenamedOptionModule
|
|
[ "mailserver" "rebootAfterKernelUpgrade" "enable" ]
|
|
[ "system" "autoUpgrade" "allowReboot" ]
|
|
)
|
|
(mkRemovedOptionModule [ "mailserver" "rebootAfterKernelUpgrade" "method" ] ''
|
|
Use `system.autoUpgrade` instead.
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "policydSPFExtraConfig" ] ''
|
|
SPF checking has been migrated to Rspamd, which makes this config redundant. Please look into the rspamd config to migrate your settings.
|
|
It may be that they are redundant and are already configured in rspamd like for skip_addresses.
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "dkimHeaderCanonicalization" ] ''
|
|
DKIM signing has been migrated to Rspamd, which always uses relaxed canonicalization.
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "dkimBodyCanonicalization" ] ''
|
|
DKIM signing has been migrated to Rspamd, which always uses relaxed canonicalization.
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "smtpdForbidBareNewline" ] ''
|
|
The workaround for the SMTP Smuggling attack is default enabled in Postfix >3.9. Use `services.postfix.config.smtpd_forbid_bare_newline` if you need to deviate from its default.
|
|
'')
|
|
|
|
# NixOS 25.11
|
|
(mkRenamedOptionModule [ "mailserver" "dmarcReporting" "domain" ] [ "mailserver" "systemDomain" ])
|
|
(mkRenamedOptionModule
|
|
[ "mailserver" "dmarcReporting" "organizationName" ]
|
|
[ "mailserver" "systemName" ]
|
|
)
|
|
(mkRemovedOptionModule [ "mailserver" "dmarcReporting" "localpart" ] ''
|
|
The localpart is now fixed at `noreply-dmarc` to simplify the configuration.
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "dmarcReporting" "email" ] ''
|
|
The address is now fixed at `noreply-dmarc@''${config.mailserver.systemDomain}` to simplify the configuration.
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "dmarcReporting" "fromName" ] ''
|
|
The name in the `FROM` field for DMARC report now uses the `mailserver.systemName`.
|
|
'')
|
|
|
|
# NixOS 26.05
|
|
(mkRemovedOptionModule [ "mailserver" "certificateDomains" ] ''
|
|
Configure `security.acme.certs.''${config.mailserver.fqdn}.extraDomains` instead.
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "certificateScheme" ] "")
|
|
(mkRemovedOptionModule [ "mailserver" "certificateDirectory" ] ''
|
|
Automatic creation of self-signed certificates is no longer supported.
|
|
'')
|
|
(mkRenamedOptionModule [ "mailserver" "acmeCertificateName" ] [ "mailserver" "x509" "useACMEHost" ])
|
|
(mkRenamedOptionModule [ "mailserver" "certificateFile" ] [ "mailserver" "x509" "certificateFile" ])
|
|
(mkRenamedOptionModule [ "mailserver" "keyFile" ] [ "mailserver" "x509" "privateKeyFile" ])
|
|
|
|
(mkRenamedOptionModule [ "mailserver" "dkimSigning" ] [ "mailserver" "dkim" "enable" ])
|
|
(mkRenamedOptionModule [ "mailserver" "dkimKeyDirectory" ] [ "mailserver" "dkim" "keyDirectory" ])
|
|
(mkRenamedOptionModule
|
|
[ "mailserver" "dkimSelector" ]
|
|
[ "mailserver" "dkim" "defaults" "selector" ]
|
|
)
|
|
(mkRenamedOptionModule [ "mailserver" "dkimKeyType" ] [ "mailserver" "dkim" "defaults" "keyType" ])
|
|
(mkRenamedOptionModule
|
|
[ "mailserver" "dkimKeyBits" ]
|
|
[ "mailserver" "dkim" "defaults" "keyLength" ]
|
|
)
|
|
(mkRemovedOptionModule [ "mailserver" "ldap" "dovecot" "userAttrs" ] ''
|
|
The user_attrs field is now used internally to map the home and mail directories.
|
|
'')
|
|
(mkRemovedOptionModule [ "mailserver" "ldap" "dovecot" "passAttrs" ] ''
|
|
The pass_attrs field is now used internally. You can customize the `mailserver.ldap.attributes.password` field instead.
|
|
'')
|
|
(mkRenamedOptionModule [ "mailserver" "ldap" "tlsCAFile" ] [ "mailserver" "ldap" "caFile" ])
|
|
(mkRenamedOptionModule [ "mailserver" "ldap" "searchBase" ] [ "mailserver" "ldap" "base" ])
|
|
(mkRenamedOptionModule [ "mailserver" "ldap" "searchScope" ] [ "mailserver" "ldap" "scope" ])
|
|
(mkRenamedOptionModule
|
|
[ "mailserver" "ldap" "postfix" "uidAttribute" ]
|
|
[ "mailserver" "ldap" "attributes" "username" ]
|
|
)
|
|
(mkRenamedOptionModule
|
|
[ "mailserver" "ldap" "postfix" "mailAttribute" ]
|
|
[ "mailserver" "ldap" "attributes" "mail" ]
|
|
)
|
|
(mkRenamedOptionModule [ "mailserver" "extraVirtualAliases" ] [ "mailserver" "aliases" ])
|
|
(mkRenamedOptionModule [ "mailserver" "loginAccounts" ] [ "mailserver" "accounts" ])
|
|
(mkRenamedOptionModule [ "mailserver" "vmailUID" ] [ "mailserver" "storage" "uid" ])
|
|
(mkRenamedOptionModule [ "mailserver" "vmailUserName" ] [ "mailserver" "storage" "owner" ])
|
|
(mkRenamedOptionModule [ "mailserver" "vmailGroupName" ] [ "mailserver" "storage" "group" ])
|
|
(mkRenamedOptionModule [ "mailserver" "mailDirectory" ] [ "mailserver" "storage" "path" ])
|
|
(mkChangedOptionModule [ "mailserver" "useFSLayout" ] [ "mailserver" "storage" "directoryLayout" ] (
|
|
config: if config.mailserver.useFSLayout then "fs" else "maildir++"
|
|
))
|
|
];
|
|
}
|