Compare commits
16 Commits
havefun-22
...
havefun-23
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
478da2c67b | ||
|
|
acc7791ee9 | ||
|
|
758fa8f9bc | ||
|
|
811389e31b | ||
|
|
1bcfcf786b | ||
|
|
a948c49ca7 | ||
|
|
42c5564791 | ||
|
|
fd605a419b | ||
|
|
d8131ffc61 | ||
|
|
bd99079363 | ||
|
|
c04e4f22da | ||
|
|
e2ca6e45f3 | ||
|
|
6d0d9fb966 | ||
|
|
0bbb2ac74e | ||
|
|
4fcab839d7 | ||
|
|
bc667fb6af |
@@ -32,8 +32,8 @@ let
|
|||||||
|
|
||||||
desc = prJobsets // {
|
desc = prJobsets // {
|
||||||
"master" = mkFlakeJobset "master";
|
"master" = mkFlakeJobset "master";
|
||||||
"nixos-22.05" = mkFlakeJobset "nixos-22.05";
|
|
||||||
"nixos-22.11" = mkFlakeJobset "nixos-22.11";
|
"nixos-22.11" = mkFlakeJobset "nixos-22.11";
|
||||||
|
"nixos-23.05" = mkFlakeJobset "nixos-23.05";
|
||||||
};
|
};
|
||||||
|
|
||||||
log = {
|
log = {
|
||||||
|
|||||||
@@ -5,9 +5,17 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
build:
|
build:
|
||||||
os: ubuntu-20.04
|
os: ubuntu-22.04
|
||||||
tools:
|
tools:
|
||||||
python: "3.9"
|
python: "3"
|
||||||
|
apt_packages:
|
||||||
|
- nix
|
||||||
|
- proot
|
||||||
|
jobs:
|
||||||
|
pre_install:
|
||||||
|
- mkdir -p ~/.nix ~/.config/nix
|
||||||
|
- echo "experimental-features = nix-command flakes" > ~/.config/nix/nix.conf
|
||||||
|
- proot -b ~/.nix:/nix /bin/sh -c "nix build -L .#optionsDoc && cp -v result docs/options.md"
|
||||||
|
|
||||||
sphinx:
|
sphinx:
|
||||||
configuration: docs/conf.py
|
configuration: docs/conf.py
|
||||||
|
|||||||
48
README.md
48
README.md
@@ -8,26 +8,21 @@
|
|||||||
For each NixOS release, we publish a branch. You then have to use the
|
For each NixOS release, we publish a branch. You then have to use the
|
||||||
SNM branch corresponding to your NixOS version.
|
SNM branch corresponding to your NixOS version.
|
||||||
|
|
||||||
* For NixOS 22.05
|
* For NixOS 23.05
|
||||||
- Use the [SNM branch `nixos-22.05`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-22.05)
|
- Use the [SNM branch `nixos-23.05`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-23.05)
|
||||||
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-22.05/)
|
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-23.05/)
|
||||||
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-22.05/release-notes.html#nixos-22-05)
|
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-23.05/release-notes.html#nixos-23-05)
|
||||||
* For NixOS 21.11
|
* For NixOS 22.11
|
||||||
- Use the [SNM branch `nixos-21.11`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-21.11)
|
- Use the [SNM branch `nixos-22.11`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-22.11)
|
||||||
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-21.11/)
|
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-22.11/)
|
||||||
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-21.11/release-notes.html#nixos-21-11)
|
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-22.11/release-notes.html#nixos-22-11)
|
||||||
* For NixOS unstable
|
* For NixOS unstable
|
||||||
- Use the [SNM branch `master`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/master)
|
- Use the [SNM branch `master`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/master)
|
||||||
- [Documentation](https://nixos-mailserver.readthedocs.io/en/latest/)
|
- [Documentation](https://nixos-mailserver.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
[Subscribe to SNM Announcement List](https://www.freelists.org/list/snm)
|
[Subscribe to SNM Announcement List](https://www.freelists.org/list/snm)
|
||||||
This is a very low volume list where new releases of SNM are announced, so you
|
This is a very low volume list where new releases of SNM are announced, so you
|
||||||
can stay up to date with bug fixes and updates. All announcements are signed by
|
can stay up to date with bug fixes and updates.
|
||||||
the gpg key with fingerprint
|
|
||||||
|
|
||||||
```
|
|
||||||
D9FE 4119 F082 6F15 93BD BD36 6162 DBA5 635E A16A
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -117,30 +112,9 @@ For a complete list of options, see `default.nix`.
|
|||||||
## How to Set Up a 10/10 Mail Server Guide
|
## How to Set Up a 10/10 Mail Server Guide
|
||||||
Check out the [Complete Setup Guide](https://nixos-mailserver.readthedocs.io/en/latest/setup-guide.html) in the project's documentation.
|
Check out the [Complete Setup Guide](https://nixos-mailserver.readthedocs.io/en/latest/setup-guide.html) in the project's documentation.
|
||||||
|
|
||||||
## How to Backup
|
|
||||||
|
|
||||||
Checkout the [Complete Backup Guide](https://nixos-mailserver.readthedocs.io/en/latest/backup-guide.html). Backups are easy with `SNM`.
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
See the [How to Develop SNM](https://nixos-mailserver.readthedocs.io/en/latest/howto-develop.html) wiki page.
|
See the [How to Develop SNM](https://nixos-mailserver.readthedocs.io/en/latest/howto-develop.html) documentation page.
|
||||||
|
|
||||||
## Release notes
|
|
||||||
|
|
||||||
### nixos-20.03
|
|
||||||
|
|
||||||
- Rspamd is upgraded to 2.0 which deprecates the SQLite Bayes
|
|
||||||
backend. We then moved to the Redis backend (the default since
|
|
||||||
Rspamd 2.0). If you don't want to relearn the Redis backend from the
|
|
||||||
scratch, we could manually run
|
|
||||||
|
|
||||||
rspamadm statconvert --spam-db /var/lib/rspamd/bayes.spam.sqlite --ham-db /var/lib/rspamd/bayes.ham.sqlite -h 127.0.0.1:6379 --symbol-ham BAYES_HAM --symbol-spam BAYES_SPAM
|
|
||||||
|
|
||||||
See the [Rspamd migration
|
|
||||||
notes](https://rspamd.com/doc/migration.html#migration-to-rspamd-20)
|
|
||||||
and [this SNM Merge
|
|
||||||
Request](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/merge_requests/164)
|
|
||||||
for details.
|
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
See the [contributor tab](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/graphs/master)
|
See the [contributor tab](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/graphs/master)
|
||||||
@@ -155,6 +129,4 @@ See the [contributor tab](https://gitlab.com/simple-nixos-mailserver/nixos-mails
|
|||||||
* Logo made with [Logomakr.com](https://logomakr.com)
|
* Logo made with [Logomakr.com](https://logomakr.com)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[logo]: docs/logo.png
|
[logo]: docs/logo.png
|
||||||
|
|||||||
99
default.nix
99
default.nix
@@ -48,7 +48,11 @@ in
|
|||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
example = [ "imap.example.com" "pop3.example.com" ];
|
example = [ "imap.example.com" "pop3.example.com" ];
|
||||||
default = [];
|
default = [];
|
||||||
description = "Secondary domains and subdomains for which it is necessary to generate a certificate.";
|
description = ''
|
||||||
|
({option}`mailserver.certificateScheme` == `acme-nginx`)
|
||||||
|
|
||||||
|
Secondary domains and subdomains for which it is necessary to generate a certificate.
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
messageSizeLimit = mkOption {
|
messageSizeLimit = mkOption {
|
||||||
@@ -79,7 +83,7 @@ in
|
|||||||
```
|
```
|
||||||
|
|
||||||
Warning: this is stored in plaintext in the Nix store!
|
Warning: this is stored in plaintext in the Nix store!
|
||||||
Use `hashedPasswordFile` instead.
|
Use {option}`mailserver.loginAccounts.<name>.hashedPasswordFile` instead.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -156,7 +160,7 @@ in
|
|||||||
description = ''
|
description = ''
|
||||||
Specifies if the account should be a send-only account.
|
Specifies if the account should be a send-only account.
|
||||||
Emails sent to send-only accounts will be rejected from
|
Emails sent to send-only accounts will be rejected from
|
||||||
unauthorized senders with the sendOnlyRejectMessage
|
unauthorized senders with the `sendOnlyRejectMessage`
|
||||||
stating the reason.
|
stating the reason.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
@@ -200,7 +204,7 @@ in
|
|||||||
description = ''
|
description = ''
|
||||||
Folder to store search indices. If null, indices are stored
|
Folder to store search indices. If null, indices are stored
|
||||||
along with email, which could not necessarily be desirable,
|
along with email, which could not necessarily be desirable,
|
||||||
especially when the fullTextSearch option is enable since
|
especially when {option}`mailserver.fullTextSearch.enable` is `true` since
|
||||||
indices it creates are voluminous and do not need to be backed
|
indices it creates are voluminous and do not need to be backed
|
||||||
up.
|
up.
|
||||||
|
|
||||||
@@ -242,8 +246,8 @@ in
|
|||||||
default = "no";
|
default = "no";
|
||||||
description = ''
|
description = ''
|
||||||
Fail searches when no index is available. If set to
|
Fail searches when no index is available. If set to
|
||||||
<literal>body</literal>, then only body searches (as opposed to
|
`body`, then only body searches (as opposed to
|
||||||
header) are affected. If set to <literal>no</literal>, searches may
|
header) are affected. If set to `no`, searches may
|
||||||
fall back to a very slow brute force search.
|
fall back to a very slow brute force search.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
@@ -281,7 +285,7 @@ in
|
|||||||
randomizedDelaySec = mkOption {
|
randomizedDelaySec = mkOption {
|
||||||
type = types.int;
|
type = types.int;
|
||||||
default = 1000;
|
default = 1000;
|
||||||
description = "Run the maintenance job not exactly at the time specified with <literal>onCalendar</literal>, but plus or minus this many seconds.";
|
description = "Run the maintenance job not exactly at the time specified with `onCalendar`, but plus or minus this many seconds.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -333,7 +337,7 @@ in
|
|||||||
the value {`"user@example.com" = "user@elsewhere.com";}`
|
the value {`"user@example.com" = "user@elsewhere.com";}`
|
||||||
means that mails to `user@example.com` are forwarded to
|
means that mails to `user@example.com` are forwarded to
|
||||||
`user@elsewhere.com`. The difference with the
|
`user@elsewhere.com`. The difference with the
|
||||||
`extraVirtualAliases` option is that `user@elsewhere.com`
|
{option}`mailserver.extraVirtualAliases` option is that `user@elsewhere.com`
|
||||||
can't send mail as `user@example.com`. Also, this option
|
can't send mail as `user@example.com`. Also, this option
|
||||||
allows to forward mails to external addresses.
|
allows to forward mails to external addresses.
|
||||||
'';
|
'';
|
||||||
@@ -367,7 +371,7 @@ in
|
|||||||
description = ''
|
description = ''
|
||||||
The unix UID of the virtual mail user. Be mindful that if this is
|
The unix UID of the virtual mail user. Be mindful that if this is
|
||||||
changed, you will need to manually adjust the permissions of
|
changed, you will need to manually adjust the permissions of
|
||||||
mailDirectory.
|
`mailDirectory`.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -448,19 +452,26 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
certificateScheme = mkOption {
|
certificateScheme = let
|
||||||
type = types.enum [ 1 2 3 ];
|
schemes = [ "manual" "selfsigned" "acme-nginx" "acme" ];
|
||||||
default = 2;
|
translate = i: warn "setting mailserver.certificateScheme by number is deprecated, please use names instead"
|
||||||
|
(builtins.elemAt schemes (i - 1));
|
||||||
|
in mkOption {
|
||||||
|
type = with types; coercedTo (enum [ 1 2 3 ]) translate (enum schemes);
|
||||||
|
default = "selfsigned";
|
||||||
description = ''
|
description = ''
|
||||||
Certificate Files. There are three options for these.
|
The scheme to use for managing TLS certificates:
|
||||||
|
|
||||||
1) You specify locations and manually copy certificates there.
|
1. `manual`: you specify locations via {option}`mailserver.certificateFile` and
|
||||||
2) You let the server create new (self signed) certificates on the fly.
|
{option}`mailserver.keyFile` and manually copy certificates there.
|
||||||
3) You let the server create a certificate via `Let's Encrypt`. Note that
|
2. `selfsigned`: you let the server create new (self-signed) certificates on the fly.
|
||||||
this implies that a stripped down webserver has to be started. This also
|
3. `acme-nginx`: you let the server request certificates from [Let's Encrypt](https://letsencrypt.org)
|
||||||
implies that the FQDN must be set as an `A` record to point to the IP of
|
via NixOS' ACME module. By default, this will set up a stripped-down Nginx server for
|
||||||
the server. In particular port 80 on the server will be opened. For details
|
{option}`mailserver.fqdn` and open port 80. For this to work, the FQDN must be properly
|
||||||
on how to set up the domain records, see the guide in the readme.
|
configured to point to your server (see the [setup guide](setup-guide.rst) for more information).
|
||||||
|
4. `acme`: you already have an ACME certificate set up (for example, you're already running a TLS-enabled
|
||||||
|
Nginx server on the FQDN). This is better than `manual` because the appropriate services will be reloaded
|
||||||
|
when the certificate is renewed.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -468,8 +479,9 @@ in
|
|||||||
type = types.path;
|
type = types.path;
|
||||||
example = "/root/mail-server.crt";
|
example = "/root/mail-server.crt";
|
||||||
description = ''
|
description = ''
|
||||||
Scheme 1)
|
({option}`mailserver.certificateScheme` == `manual`)
|
||||||
Location of the certificate
|
|
||||||
|
Location of the certificate.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -477,8 +489,9 @@ in
|
|||||||
type = types.path;
|
type = types.path;
|
||||||
example = "/root/mail-server.key";
|
example = "/root/mail-server.key";
|
||||||
description = ''
|
description = ''
|
||||||
Scheme 1)
|
({option}`mailserver.certificateScheme` == `manual`)
|
||||||
Location of the key file
|
|
||||||
|
Location of the key file.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -486,8 +499,9 @@ in
|
|||||||
type = types.path;
|
type = types.path;
|
||||||
default = "/var/certs";
|
default = "/var/certs";
|
||||||
description = ''
|
description = ''
|
||||||
Scheme 2)
|
({option}`mailserver.certificateScheme` == `selfsigned`)
|
||||||
This is the folder where the certificate will be created. The name is
|
|
||||||
|
This is the folder where the self-signed certificate will be created. The name is
|
||||||
hardcoded to "cert-DOMAIN.pem" and "key-DOMAIN.pem" and the
|
hardcoded to "cert-DOMAIN.pem" and "key-DOMAIN.pem" and the
|
||||||
certificate is valid for 10 years.
|
certificate is valid for 10 years.
|
||||||
'';
|
'';
|
||||||
@@ -582,7 +596,7 @@ in
|
|||||||
type = types.str;
|
type = types.str;
|
||||||
default = "mail";
|
default = "mail";
|
||||||
description = ''
|
description = ''
|
||||||
|
The DKIM selector.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -590,7 +604,7 @@ in
|
|||||||
type = types.path;
|
type = types.path;
|
||||||
default = "/var/dkim";
|
default = "/var/dkim";
|
||||||
description = ''
|
description = ''
|
||||||
|
The DKIM directory.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -601,7 +615,7 @@ in
|
|||||||
How many bits in generated DKIM keys. RFC6376 advises minimum 1024-bit keys.
|
How many bits in generated DKIM keys. RFC6376 advises minimum 1024-bit keys.
|
||||||
|
|
||||||
If you have already deployed a key with a different number of bits than specified
|
If you have already deployed a key with a different number of bits than specified
|
||||||
here, then you should use a different selector (dkimSelector). In order to get
|
here, then you should use a different selector ({option}`mailserver.dkimSelector`). In order to get
|
||||||
this package to generate a key with the new number of bits, you will either have to
|
this package to generate a key with the new number of bits, you will either have to
|
||||||
change the selector or delete the old key file.
|
change the selector or delete the old key file.
|
||||||
'';
|
'';
|
||||||
@@ -673,7 +687,7 @@ in
|
|||||||
type = types.str;
|
type = types.str;
|
||||||
example = "ACME Corp.";
|
example = "ACME Corp.";
|
||||||
description = ''
|
description = ''
|
||||||
The name of your organization used in the <literal>org_name</literal> attribute in
|
The name of your organization used in the `org_name` attribute in
|
||||||
DMARC reports.
|
DMARC reports.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
@@ -681,7 +695,7 @@ in
|
|||||||
fromName = mkOption {
|
fromName = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = cfg.dmarcReporting.organizationName;
|
default = cfg.dmarcReporting.organizationName;
|
||||||
defaultText = literalExpression "organizationName";
|
defaultText = literalMD "{option}`mailserver.dmarcReporting.organizationName`";
|
||||||
description = ''
|
description = ''
|
||||||
The sender name for DMARC reports. Defaults to the organization name.
|
The sender name for DMARC reports. Defaults to the organization name.
|
||||||
'';
|
'';
|
||||||
@@ -738,7 +752,7 @@ in
|
|||||||
if (ip == "0.0.0.0" || ip == "::")
|
if (ip == "0.0.0.0" || ip == "::")
|
||||||
then "127.0.0.1"
|
then "127.0.0.1"
|
||||||
else if isIpv6 ip then "[${ip}]" else ip;
|
else if isIpv6 ip then "[${ip}]" else ip;
|
||||||
defaultText = lib.literalDocBook "computed from <option>config.services.redis.servers.rspamd.bind</option>";
|
defaultText = lib.literalMD "computed from `config.services.redis.servers.rspamd.bind`";
|
||||||
description = ''
|
description = ''
|
||||||
Address that rspamd should use to contact redis.
|
Address that rspamd should use to contact redis.
|
||||||
'';
|
'';
|
||||||
@@ -776,7 +790,7 @@ in
|
|||||||
sendingFqdn = mkOption {
|
sendingFqdn = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = cfg.fqdn;
|
default = cfg.fqdn;
|
||||||
defaultText = "config.mailserver.fqdn";
|
defaultText = lib.literalMD "{option}`mailserver.fqdn`";
|
||||||
example = "myserver.example.com";
|
example = "myserver.example.com";
|
||||||
description = ''
|
description = ''
|
||||||
The fully qualified domain name of the mail server used to
|
The fully qualified domain name of the mail server used to
|
||||||
@@ -792,7 +806,7 @@ in
|
|||||||
|
|
||||||
This setting allows the server to identify as
|
This setting allows the server to identify as
|
||||||
myserver.example.com when forwarding mail, independently of
|
myserver.example.com when forwarding mail, independently of
|
||||||
`fqdn` (which, for SSL reasons, should generally be the name
|
{option}`mailserver.fqdn` (which, for SSL reasons, should generally be the name
|
||||||
to which the user connects).
|
to which the user connects).
|
||||||
|
|
||||||
Set this to the name to which the sending IP's reverse DNS
|
Set this to the name to which the sending IP's reverse DNS
|
||||||
@@ -864,7 +878,7 @@ in
|
|||||||
start program = "${pkgs.systemd}/bin/systemctl start rspamd"
|
start program = "${pkgs.systemd}/bin/systemctl start rspamd"
|
||||||
stop program = "${pkgs.systemd}/bin/systemctl stop rspamd"
|
stop program = "${pkgs.systemd}/bin/systemctl stop rspamd"
|
||||||
'';
|
'';
|
||||||
defaultText = lib.literalDocBook "see source";
|
defaultText = lib.literalMD "see [source](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/master/default.nix)";
|
||||||
description = ''
|
description = ''
|
||||||
The configuration used for monitoring via monit.
|
The configuration used for monitoring via monit.
|
||||||
Use a mail address that you actively check and set it via 'set alert ...'.
|
Use a mail address that you actively check and set it via 'set alert ...'.
|
||||||
@@ -881,7 +895,8 @@ in
|
|||||||
description = ''
|
description = ''
|
||||||
The location where borg saves the backups.
|
The location where borg saves the backups.
|
||||||
This can be a local path or a remote location such as user@host:/path/to/repo.
|
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 cmdPreexec and cmdPostexec.
|
It is exported and thus available as an environment variable to
|
||||||
|
{option}`mailserver.borgbackup.cmdPreexec` and {option}`mailserver.borgbackup.cmdPostexec`.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -941,7 +956,7 @@ in
|
|||||||
default = "none";
|
default = "none";
|
||||||
description = ''
|
description = ''
|
||||||
The backup can be encrypted by choosing any other value than 'none'.
|
The backup can be encrypted by choosing any other value than 'none'.
|
||||||
When using encryption the password / passphrase must be provided in passphraseFile.
|
When using encryption the password/passphrase must be provided in `passphraseFile`.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -964,6 +979,7 @@ in
|
|||||||
locations = mkOption {
|
locations = mkOption {
|
||||||
type = types.listOf types.path;
|
type = types.listOf types.path;
|
||||||
default = [cfg.mailDirectory];
|
default = [cfg.mailDirectory];
|
||||||
|
defaultText = lib.literalExpression "[ config.mailserver.mailDirectory ]";
|
||||||
description = "The locations that are to be backed up by borg.";
|
description = "The locations that are to be backed up by borg.";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -984,8 +1000,9 @@ in
|
|||||||
default = null;
|
default = null;
|
||||||
description = ''
|
description = ''
|
||||||
The command to be executed before each backup operation.
|
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.
|
This is called prior to borg init in the same script that runs borg init and create and `cmdPostexec`.
|
||||||
Example:
|
'';
|
||||||
|
example = ''
|
||||||
export BORG_RSH="ssh -i /path/to/private/key"
|
export BORG_RSH="ssh -i /path/to/private/key"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
@@ -996,7 +1013,7 @@ in
|
|||||||
description = ''
|
description = ''
|
||||||
The command to be executed after each backup operation.
|
The command to be executed after each backup operation.
|
||||||
This is called after borg create completed successfully and in the same script that runs
|
This is called after borg create completed successfully and in the same script that runs
|
||||||
cmdPreexec, borg init and create.
|
`cmdPreexec`, borg init and create.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1009,7 +1026,7 @@ in
|
|||||||
example = true;
|
example = true;
|
||||||
description = ''
|
description = ''
|
||||||
Whether to enable automatic reboot after kernel upgrades.
|
Whether to enable automatic reboot after kernel upgrades.
|
||||||
This is to be used in conjunction with system.autoUpgrade.enable = true"
|
This is to be used in conjunction with `system.autoUpgrade.enable = true;`
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
method = mkOption {
|
method = mkOption {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Add Roundcube, a webmail
|
Add Roundcube, a webmail
|
||||||
=======================
|
========================
|
||||||
|
|
||||||
The NixOS module for roundcube nearly works out of the box with SNM. By
|
The NixOS module for roundcube nearly works out of the box with SNM. By
|
||||||
default, it sets up a nginx virtual host to serve the webmail, other web
|
default, it sets up a nginx virtual host to serve the webmail, other web
|
||||||
|
|||||||
17
docs/autodiscovery.rst
Normal file
17
docs/autodiscovery.rst
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
Autodiscovery
|
||||||
|
=============
|
||||||
|
|
||||||
|
`RFC6186 <https://www.rfc-editor.org/rfc/rfc6186>`_ allows supporting email clients to automatically discover SMTP / IMAP addresses
|
||||||
|
of the mailserver. For that, the following records are required:
|
||||||
|
|
||||||
|
================ ==== ==== ======== ====== ==== =================
|
||||||
|
Record TTL Type Priority Weight Port Value
|
||||||
|
================ ==== ==== ======== ====== ==== =================
|
||||||
|
_submission._tcp 3600 SRV 5 0 587 mail.example.com.
|
||||||
|
_imap._tcp 3600 SRV 5 0 143 mail.example.com.
|
||||||
|
_imaps._tcp 3600 SRV 5 0 993 mail.example.com.
|
||||||
|
================ ==== ==== ======== ====== ==== =================
|
||||||
|
|
||||||
|
Please note that only a few MUAs currently implement this. For vendor-specific
|
||||||
|
discovery mechanisms `automx <https://github.com/rseichter/automx2>`_ can be used instead.
|
||||||
|
|
||||||
12
docs/conf.py
12
docs/conf.py
@@ -18,7 +18,7 @@
|
|||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
project = 'NixOS Mailserver'
|
project = 'NixOS Mailserver'
|
||||||
copyright = '2020, NixOS Mailserver Contributors'
|
copyright = '2022, NixOS Mailserver Contributors'
|
||||||
author = 'NixOS Mailserver Contributors'
|
author = 'NixOS Mailserver Contributors'
|
||||||
|
|
||||||
|
|
||||||
@@ -28,8 +28,16 @@ author = 'NixOS Mailserver Contributors'
|
|||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
# ones.
|
# ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
|
'myst_parser'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
myst_enable_extensions = [
|
||||||
|
'colon_fence',
|
||||||
|
'linkify',
|
||||||
|
]
|
||||||
|
|
||||||
|
smartquotes = False
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
|
||||||
@@ -50,4 +58,4 @@ html_theme = 'sphinx_rtd_theme'
|
|||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = []
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Run NixOS tests
|
|||||||
---------------
|
---------------
|
||||||
|
|
||||||
To run the test suite, you need to enable `Nix Flakes
|
To run the test suite, you need to enable `Nix Flakes
|
||||||
<https://nixos.wiki/wiki/Flakes#Installing_flakes>`.
|
<https://nixos.wiki/wiki/Flakes#Installing_flakes>`_.
|
||||||
|
|
||||||
You can then run the testsuite via
|
You can then run the testsuite via
|
||||||
|
|
||||||
@@ -30,28 +30,20 @@ run tests manually. For instance:
|
|||||||
Contributing to the documentation
|
Contributing to the documentation
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
The documentation is written in RST, build with Sphinx and published
|
The documentation is written in RST (except option documentation which is in CommonMark),
|
||||||
by `Read the Docs <https://readthedocs.org/>`_.
|
built with Sphinx and published by `Read the Docs <https://readthedocs.org/>`_.
|
||||||
|
|
||||||
For the syntax, see `RST/Sphinx Cheatsheet
|
For the syntax, see the `RST/Sphinx primer
|
||||||
<https://sphinx-tutorial.readthedocs.io/cheatsheet/>`_.
|
<https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html>`_.
|
||||||
|
|
||||||
|
To build the documentation, you need to enable `Nix Flakes
|
||||||
|
<https://nixos.wiki/wiki/Flakes#Installing_flakes>`_.
|
||||||
|
|
||||||
The ``shell.nix`` provides all the tooling required to build the
|
|
||||||
documentation:
|
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
$ nix-shell
|
$ nix build .#documentation
|
||||||
$ cd docs
|
$ xdg-open result/index.html
|
||||||
$ make html
|
|
||||||
$ firefox ./_build/html/index.html
|
|
||||||
|
|
||||||
Note if you modify some NixOS mailserver options, you would also need
|
|
||||||
to regenerate the ``options.rst`` file:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
$ nix-shell --run generate-rst-options
|
|
||||||
|
|
||||||
Nixops
|
Nixops
|
||||||
------
|
------
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ Welcome to NixOS Mailserver's documentation!
|
|||||||
rspamd-tuning
|
rspamd-tuning
|
||||||
fts
|
fts
|
||||||
flakes
|
flakes
|
||||||
|
autodiscovery
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|||||||
1319
docs/options.rst
1319
docs/options.rst
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,12 @@
|
|||||||
Release Notes
|
Release Notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
NixOS 22.11
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- Allow Rspamd to send dmarc reporting
|
||||||
|
(`merge request <https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/merge_requests/244>`__)
|
||||||
|
|
||||||
NixOS 22.05
|
NixOS 22.05
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
sphinx==4.0.2
|
sphinx ~= 5.3
|
||||||
sphinx_rtd_theme==0.5.2
|
sphinx_rtd_theme ~= 1.1
|
||||||
|
myst-parser ~= 0.18
|
||||||
|
linkify-it-py ~= 2.0
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ these should be the most common ones.
|
|||||||
|
|
||||||
# Use Let's Encrypt certificates. Note that this needs to set up a stripped
|
# Use Let's Encrypt certificates. Note that this needs to set up a stripped
|
||||||
# down nginx and opens port 80.
|
# down nginx and opens port 80.
|
||||||
certificateScheme = 3;
|
certificateScheme = "acme-nginx";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
39
flake.lock
generated
39
flake.lock
generated
@@ -16,13 +16,29 @@
|
|||||||
"type": "gitlab"
|
"type": "gitlab"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1668681692,
|
||||||
|
"narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "009399224d5e398d03b22badca40a37ac85412a1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1669542132,
|
"lastModified": 1670751203,
|
||||||
"narHash": "sha256-DRlg++NJAwPh8io3ExBJdNW7Djs3plVI5jgYQ+iXAZQ=",
|
"narHash": "sha256-XdoH1v3shKDGlrwjgrNX/EN8s3c+kQV7xY6cLCE8vcI=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a115bb9bd56831941be3776c8a94005867f316a7",
|
"rev": "64e0bf055f9d25928c31fb12924e59ff8ce71e60",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -46,11 +62,28 @@
|
|||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixpkgs-23_05": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1684782344,
|
||||||
|
"narHash": "sha256-SHN8hPYYSX0thDrMLMWPWYulK3YFgASOrCsIL3AJ78g=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "8966c43feba2c701ed624302b6a935f97bcbdf88",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-23.05",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"blobs": "blobs",
|
"blobs": "blobs",
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"nixpkgs-22_11": "nixpkgs-22_11",
|
"nixpkgs-22_11": "nixpkgs-22_11",
|
||||||
|
"nixpkgs-23_05": "nixpkgs-23_05",
|
||||||
"utils": "utils"
|
"utils": "utils"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
98
flake.nix
98
flake.nix
@@ -2,16 +2,22 @@
|
|||||||
description = "A complete and Simple Nixos Mailserver";
|
description = "A complete and Simple Nixos Mailserver";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
|
flake-compat = {
|
||||||
|
url = "github:edolstra/flake-compat";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
utils.url = "github:numtide/flake-utils";
|
utils.url = "github:numtide/flake-utils";
|
||||||
nixpkgs.url = "flake:nixpkgs/nixos-unstable";
|
nixpkgs.url = "flake:nixpkgs/nixos-unstable";
|
||||||
nixpkgs-22_11.url = "flake:nixpkgs/nixos-22.11";
|
nixpkgs-22_11.url = "flake:nixpkgs/nixos-22.11";
|
||||||
|
nixpkgs-23_05.url = "flake:nixpkgs/nixos-23.05";
|
||||||
blobs = {
|
blobs = {
|
||||||
url = "gitlab:simple-nixos-mailserver/blobs";
|
url = "gitlab:simple-nixos-mailserver/blobs";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, utils, blobs, nixpkgs, nixpkgs-22_11 }: let
|
outputs = { self, utils, blobs, nixpkgs, nixpkgs-22_11, nixpkgs-23_05, ... }: let
|
||||||
|
lib = nixpkgs.lib;
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
releases = [
|
releases = [
|
||||||
@@ -20,7 +26,7 @@
|
|||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
name = "22.11";
|
name = "23.05";
|
||||||
pkgs = nixpkgs-22_11.legacyPackages.${system};
|
pkgs = nixpkgs-22_11.legacyPackages.${system};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -31,7 +37,7 @@
|
|||||||
"multiple"
|
"multiple"
|
||||||
];
|
];
|
||||||
genTest = testName: release: {
|
genTest = testName: release: {
|
||||||
"name"= "${testName}-${release.name}";
|
"name"= "${testName}-${builtins.replaceStrings ["."] ["_"] release.name}";
|
||||||
"value"= import (./tests/. + "/${testName}.nix") {
|
"value"= import (./tests/. + "/${testName}.nix") {
|
||||||
pkgs = release.pkgs;
|
pkgs = release.pkgs;
|
||||||
inherit blobs;
|
inherit blobs;
|
||||||
@@ -43,22 +49,18 @@
|
|||||||
# external-21_05 = <derivation>;
|
# external-21_05 = <derivation>;
|
||||||
# ...
|
# ...
|
||||||
# }
|
# }
|
||||||
allTests = pkgs.lib.listToAttrs (
|
allTests = lib.listToAttrs (
|
||||||
pkgs.lib.flatten (map (t: map (r: genTest t r) releases) testNames));
|
lib.flatten (map (t: map (r: genTest t r) releases) testNames));
|
||||||
|
|
||||||
mailserverModule = import ./.;
|
mailserverModule = import ./.;
|
||||||
|
|
||||||
# Generate a rst file describing options of the NixOS mailserver module
|
# Generate a MarkDown file describing the options of the NixOS mailserver module
|
||||||
generateRstOptions = let
|
optionsDoc = let
|
||||||
eval = import (pkgs.path + "/nixos/lib/eval-config.nix") {
|
eval = lib.evalModules {
|
||||||
inherit system;
|
|
||||||
modules = [
|
modules = [
|
||||||
mailserverModule
|
mailserverModule
|
||||||
{
|
{
|
||||||
# Because the blockbook package is currently broken (we
|
_module.check = false;
|
||||||
# don't care about this package but it is part of the
|
|
||||||
# NixOS module evaluation)
|
|
||||||
nixpkgs.config.allowBroken = true;
|
|
||||||
mailserver = {
|
mailserver = {
|
||||||
fqdn = "mx.example.com";
|
fqdn = "mx.example.com";
|
||||||
domains = [
|
domains = [
|
||||||
@@ -71,71 +73,55 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
};
|
};
|
||||||
options = pkgs.nixosOptionsDoc {
|
options = builtins.toFile "options.json" (builtins.toJSON
|
||||||
options = eval.options;
|
(lib.filter (opt: opt.visible && !opt.internal && lib.head opt.loc == "mailserver")
|
||||||
};
|
(lib.optionAttrSetToDocList eval.options)));
|
||||||
in pkgs.runCommand "options.rst" { buildInputs = [pkgs.python3]; } ''
|
in pkgs.runCommand "options.md" { buildInputs = [pkgs.python3Minimal]; } ''
|
||||||
echo Generating options.rst from ${options.optionsJSON}/share/doc/nixos/options.json
|
echo "Generating options.md from ${options}"
|
||||||
python ${./scripts/generate-rst-options.py} ${options.optionsJSON}/share/doc/nixos/options.json > $out
|
python ${./scripts/generate-options.py} ${options} > $out
|
||||||
'';
|
|
||||||
|
|
||||||
# This is a script helping users to generate this file in the docs directory
|
|
||||||
generateRstOptionsScript = pkgs.writeScriptBin "generate-rst-options" ''
|
|
||||||
cp -v ${generateRstOptions} ./docs/options.rst
|
|
||||||
'';
|
|
||||||
|
|
||||||
# This is to ensure we don't forget to update the options.rst file
|
|
||||||
testRstOptions = pkgs.runCommand "test-rst-options" {} ''
|
|
||||||
if ! diff -q ${./docs/options.rst} ${generateRstOptions}
|
|
||||||
then
|
|
||||||
echo "The file ./docs/options.rst is not up-to-date and needs to be regenerated!"
|
|
||||||
echo " hint: run 'nix-shell --run generate-rst-options' to generate this file"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "test: ok" > $out
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
documentation = pkgs.stdenv.mkDerivation {
|
documentation = pkgs.stdenv.mkDerivation {
|
||||||
name = "documentation";
|
name = "documentation";
|
||||||
src = pkgs.lib.sourceByRegex ./docs ["logo.png" "conf.py" "Makefile" ".*rst$"];
|
src = lib.sourceByRegex ./docs ["logo\\.png" "conf\\.py" "Makefile" ".*\\.rst"];
|
||||||
buildInputs = [(
|
buildInputs = [(
|
||||||
pkgs.python3.withPackages(p: [
|
pkgs.python3.withPackages (p: with p; [
|
||||||
p.sphinx
|
sphinx
|
||||||
p.sphinx_rtd_theme
|
sphinx_rtd_theme
|
||||||
|
myst-parser
|
||||||
])
|
])
|
||||||
)];
|
)];
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
cp ${generateRstOptions} options.rst
|
cp ${optionsDoc} options.md
|
||||||
mkdir -p _static
|
|
||||||
# Workaround for https://github.com/sphinx-doc/sphinx/issues/3451
|
# Workaround for https://github.com/sphinx-doc/sphinx/issues/3451
|
||||||
export SOURCE_DATE_EPOCH=$(${pkgs.coreutils}/bin/date +%s)
|
unset SOURCE_DATE_EPOCH
|
||||||
make html
|
make html
|
||||||
'';
|
'';
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
cp -r _build/html $out
|
cp -Tr _build/html $out
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
in rec {
|
in {
|
||||||
nixosModules.mailserver = mailserverModule ;
|
nixosModules = rec {
|
||||||
nixosModule = self.nixosModules.mailserver;
|
mailserver = mailserverModule;
|
||||||
|
default = mailserver;
|
||||||
|
};
|
||||||
|
nixosModule = self.nixosModules.default; # compatibility
|
||||||
hydraJobs.${system} = allTests // {
|
hydraJobs.${system} = allTests // {
|
||||||
test-rst-options = testRstOptions;
|
|
||||||
inherit documentation;
|
inherit documentation;
|
||||||
};
|
};
|
||||||
checks.${system} = allTests;
|
checks.${system} = allTests;
|
||||||
devShell.${system} = pkgs.mkShell {
|
packages.${system} = {
|
||||||
buildInputs = with pkgs; [
|
inherit optionsDoc documentation;
|
||||||
generateRstOptionsScript
|
};
|
||||||
(python3.withPackages (p: with p; [
|
devShells.${system}.default = pkgs.mkShell {
|
||||||
sphinx
|
inputsFrom = [ documentation ];
|
||||||
sphinx_rtd_theme
|
packages = with pkgs; [
|
||||||
]))
|
|
||||||
jq
|
|
||||||
clamav
|
clamav
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
devShell.${system} = self.devShells.${system}.default; # compatibility
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,22 +21,22 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
# cert :: PATH
|
# cert :: PATH
|
||||||
certificatePath = if cfg.certificateScheme == 1
|
certificatePath = if cfg.certificateScheme == "manual"
|
||||||
then cfg.certificateFile
|
then cfg.certificateFile
|
||||||
else if cfg.certificateScheme == 2
|
else if cfg.certificateScheme == "selfsigned"
|
||||||
then "${cfg.certificateDirectory}/cert-${cfg.fqdn}.pem"
|
then "${cfg.certificateDirectory}/cert-${cfg.fqdn}.pem"
|
||||||
else if cfg.certificateScheme == 3
|
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
|
||||||
then "${config.security.acme.certs.${cfg.fqdn}.directory}/fullchain.pem"
|
then "${config.security.acme.certs.${cfg.fqdn}.directory}/fullchain.pem"
|
||||||
else throw "Error: Certificate Scheme must be in { 1, 2, 3 }";
|
else throw "unknown certificate scheme";
|
||||||
|
|
||||||
# key :: PATH
|
# key :: PATH
|
||||||
keyPath = if cfg.certificateScheme == 1
|
keyPath = if cfg.certificateScheme == "manual"
|
||||||
then cfg.keyFile
|
then cfg.keyFile
|
||||||
else if cfg.certificateScheme == 2
|
else if cfg.certificateScheme == "selfsigned"
|
||||||
then "${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
|
then "${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
|
||||||
else if cfg.certificateScheme == 3
|
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
|
||||||
then "${config.security.acme.certs.${cfg.fqdn}.directory}/key.pem"
|
then "${config.security.acme.certs.${cfg.fqdn}.directory}/key.pem"
|
||||||
else throw "Error: Certificate Scheme must be in { 1, 2, 3 }";
|
else throw "unknown certificate scheme";
|
||||||
|
|
||||||
passwordFiles = let
|
passwordFiles = let
|
||||||
mkHashFile = name: hash: pkgs.writeText "${builtins.hashString "sha256" name}-password-hash" hash;
|
mkHashFile = name: hash: pkgs.writeText "${builtins.hashString "sha256" name}-password-hash" hash;
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ let
|
|||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
|
|
||||||
passwdDir = "/run/dovecot2";
|
passwdDir = "/run/dovecot2";
|
||||||
passwdFile = "${passwdDir}/passwd";
|
passdbFile = "${passwdDir}/passdb";
|
||||||
|
userdbFile = "${passwdDir}/userdb";
|
||||||
|
|
||||||
bool2int = x: if x then "1" else "0";
|
bool2int = x: if x then "1" else "0";
|
||||||
|
|
||||||
@@ -74,16 +75,23 @@ let
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
cat <<EOF > ${passwdFile}
|
cat <<EOF > ${passdbFile}
|
||||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value:
|
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value:
|
||||||
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}:${builtins.toString cfg.vmailUID}:${builtins.toString cfg.vmailUID}::${cfg.mailDirectory}:/run/current-system/sw/bin/nologin:"
|
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
|
||||||
|
) cfg.loginAccounts)}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF > ${userdbFile}
|
||||||
|
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value:
|
||||||
|
"${name}:::::::"
|
||||||
+ (if lib.isString value.quota
|
+ (if lib.isString value.quota
|
||||||
then "userdb_quota_rule=*:storage=${value.quota}"
|
then "userdb_quota_rule=*:storage=${value.quota}"
|
||||||
else "")
|
else "")
|
||||||
) cfg.loginAccounts)}
|
) cfg.loginAccounts)}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chmod 600 ${passwdFile}
|
chmod 600 ${passdbFile}
|
||||||
|
chmod 600 ${userdbFile}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
junkMailboxes = builtins.attrNames (lib.filterAttrs (n: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes);
|
junkMailboxes = builtins.attrNames (lib.filterAttrs (n: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes);
|
||||||
@@ -212,12 +220,13 @@ in
|
|||||||
|
|
||||||
passdb {
|
passdb {
|
||||||
driver = passwd-file
|
driver = passwd-file
|
||||||
args = ${passwdFile}
|
args = ${passdbFile}
|
||||||
}
|
}
|
||||||
|
|
||||||
userdb {
|
userdb {
|
||||||
driver = passwd-file
|
driver = passwd-file
|
||||||
args = ${passwdFile}
|
args = ${userdbFile}
|
||||||
|
default_fields = uid=${builtins.toString cfg.vmailUID} gid=${builtins.toString cfg.vmailUID} home=${cfg.mailDirectory}
|
||||||
}
|
}
|
||||||
|
|
||||||
service auth {
|
service auth {
|
||||||
@@ -243,7 +252,7 @@ in
|
|||||||
|
|
||||||
# From elsewhere to Spam folder
|
# From elsewhere to Spam folder
|
||||||
imapsieve_mailbox1_name = ${junkMailboxName}
|
imapsieve_mailbox1_name = ${junkMailboxName}
|
||||||
imapsieve_mailbox1_causes = COPY
|
imapsieve_mailbox1_causes = COPY,APPEND
|
||||||
imapsieve_mailbox1_before = file:${stateDir}/imap_sieve/report-spam.sieve
|
imapsieve_mailbox1_before = file:${stateDir}/imap_sieve/report-spam.sieve
|
||||||
|
|
||||||
# From Spam folder to elsewhere
|
# From Spam folder to elsewhere
|
||||||
|
|||||||
@@ -23,6 +23,6 @@ in
|
|||||||
config = with cfg; lib.mkIf enable {
|
config = with cfg; lib.mkIf enable {
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
dovecot opendkim openssh postfix rspamd
|
dovecot opendkim openssh postfix rspamd
|
||||||
] ++ (if certificateScheme == 2 then [ openssl ] else []);
|
] ++ (if certificateScheme == "selfsigned" then [ openssl ] else []);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{ config, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
@@ -31,7 +31,7 @@ in
|
|||||||
++ lib.optional enablePop3 110
|
++ lib.optional enablePop3 110
|
||||||
++ lib.optional enablePop3Ssl 995
|
++ lib.optional enablePop3Ssl 995
|
||||||
++ lib.optional enableManageSieve 4190
|
++ lib.optional enableManageSieve 4190
|
||||||
++ lib.optional (certificateScheme == 3) 80;
|
++ lib.optional (certificateScheme == "acme-nginx") 80;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ let
|
|||||||
acmeRoot = "/var/lib/acme/acme-challenge";
|
acmeRoot = "/var/lib/acme/acme-challenge";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = lib.mkIf (cfg.enable && cfg.certificateScheme == 3) {
|
config = lib.mkIf (cfg.enable && (cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx")) {
|
||||||
services.nginx = {
|
services.nginx = lib.mkIf (cfg.certificateScheme == "acme-nginx") {
|
||||||
enable = true;
|
enable = true;
|
||||||
virtualHosts."${cfg.fqdn}" = {
|
virtualHosts."${cfg.fqdn}" = {
|
||||||
serverName = cfg.fqdn;
|
serverName = cfg.fqdn;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ let
|
|||||||
--directory="${cfg.dkimKeyDirectory}"
|
--directory="${cfg.dkimKeyDirectory}"
|
||||||
mv "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.private" "${dkim_key}"
|
mv "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.private" "${dkim_key}"
|
||||||
mv "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.txt" "${dkim_txt}"
|
mv "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.txt" "${dkim_txt}"
|
||||||
|
chmod 644 "${dkim_txt}"
|
||||||
echo "Generated key for domain ${dom} selector ${cfg.dkimSelector}"
|
echo "Generated key for domain ${dom} selector ${cfg.dkimSelector}"
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|||||||
@@ -19,9 +19,9 @@
|
|||||||
let
|
let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
certificatesDeps =
|
certificatesDeps =
|
||||||
if cfg.certificateScheme == 1 then
|
if cfg.certificateScheme == "manual" then
|
||||||
[]
|
[]
|
||||||
else if cfg.certificateScheme == 2 then
|
else if cfg.certificateScheme == "selfsigned" then
|
||||||
[ "mailserver-selfsigned-certificate.service" ]
|
[ "mailserver-selfsigned-certificate.service" ]
|
||||||
else
|
else
|
||||||
[ "acme-finished-${cfg.fqdn}.target" ];
|
[ "acme-finished-${cfg.fqdn}.target" ];
|
||||||
@@ -29,7 +29,7 @@ in
|
|||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf enable {
|
config = with cfg; lib.mkIf enable {
|
||||||
# Create self signed certificate
|
# Create self signed certificate
|
||||||
systemd.services.mailserver-selfsigned-certificate = lib.mkIf (cfg.certificateScheme == 2) {
|
systemd.services.mailserver-selfsigned-certificate = lib.mkIf (cfg.certificateScheme == "selfsigned") {
|
||||||
after = [ "local-fs.target" ];
|
after = [ "local-fs.target" ];
|
||||||
script = ''
|
script = ''
|
||||||
# Create certificates if they do not exist yet
|
# Create certificates if they do not exist yet
|
||||||
|
|||||||
81
scripts/generate-options.py
Normal file
81
scripts/generate-options.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
header = """
|
||||||
|
# Mailserver options
|
||||||
|
|
||||||
|
## `mailserver`
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
template = """
|
||||||
|
`````{{option}} {key}
|
||||||
|
{description}
|
||||||
|
|
||||||
|
{type}
|
||||||
|
{default}
|
||||||
|
{example}
|
||||||
|
`````
|
||||||
|
"""
|
||||||
|
|
||||||
|
f = open(sys.argv[1])
|
||||||
|
options = json.load(f)
|
||||||
|
|
||||||
|
groups = ["mailserver.loginAccounts",
|
||||||
|
"mailserver.certificate",
|
||||||
|
"mailserver.dkim",
|
||||||
|
"mailserver.dmarcReporting",
|
||||||
|
"mailserver.fullTextSearch",
|
||||||
|
"mailserver.redis",
|
||||||
|
"mailserver.monitoring",
|
||||||
|
"mailserver.backup",
|
||||||
|
"mailserver.borgbackup"]
|
||||||
|
|
||||||
|
def render_option_value(opt, attr):
|
||||||
|
if attr in opt:
|
||||||
|
if isinstance(opt[attr], dict) and '_type' in opt[attr]:
|
||||||
|
if opt[attr]['_type'] == 'literalExpression':
|
||||||
|
if '\n' in opt[attr]['text']:
|
||||||
|
res = '\n```nix\n' + opt[attr]['text'].rstrip('\n') + '\n```'
|
||||||
|
else:
|
||||||
|
res = '```{}```'.format(opt[attr]['text'])
|
||||||
|
elif opt[attr]['_type'] == 'literalMD':
|
||||||
|
res = opt[attr]['text']
|
||||||
|
else:
|
||||||
|
s = str(opt[attr])
|
||||||
|
if s == "":
|
||||||
|
res = '`""`'
|
||||||
|
elif '\n' in s:
|
||||||
|
res = '\n```\n' + s.rstrip('\n') + '\n```'
|
||||||
|
else:
|
||||||
|
res = '```{}```'.format(s)
|
||||||
|
res = '- ' + attr + ': ' + res
|
||||||
|
else:
|
||||||
|
res = ""
|
||||||
|
return res
|
||||||
|
|
||||||
|
def print_option(opt):
|
||||||
|
if isinstance(opt['description'], dict) and '_type' in opt['description']: # mdDoc
|
||||||
|
description = opt['description']['text']
|
||||||
|
else:
|
||||||
|
description = opt['description']
|
||||||
|
print(template.format(
|
||||||
|
key=opt['name'],
|
||||||
|
description=description or "",
|
||||||
|
type="- type: ```{}```".format(opt['type']),
|
||||||
|
default=render_option_value(opt, 'default'),
|
||||||
|
example=render_option_value(opt, 'example')))
|
||||||
|
|
||||||
|
|
||||||
|
print(header)
|
||||||
|
for opt in options:
|
||||||
|
if any([opt['name'].startswith(c) for c in groups]):
|
||||||
|
continue
|
||||||
|
print_option(opt)
|
||||||
|
|
||||||
|
for c in groups:
|
||||||
|
print('## `{}`'.format(c))
|
||||||
|
print()
|
||||||
|
for opt in options:
|
||||||
|
if opt['name'].startswith(c):
|
||||||
|
print_option(opt)
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import json
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
header = """
|
|
||||||
Mailserver Options
|
|
||||||
==================
|
|
||||||
|
|
||||||
mailserver
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
template = """
|
|
||||||
{key}
|
|
||||||
{line}
|
|
||||||
|
|
||||||
{description}
|
|
||||||
|
|
||||||
{type}
|
|
||||||
{default}
|
|
||||||
{example}
|
|
||||||
"""
|
|
||||||
|
|
||||||
f = open(sys.argv[1])
|
|
||||||
options = json.load(f)
|
|
||||||
|
|
||||||
options = {k: v for k, v in options.items()
|
|
||||||
if k.startswith("mailserver.")}
|
|
||||||
|
|
||||||
groups = ["mailserver.loginAccount",
|
|
||||||
"mailserver.certificate",
|
|
||||||
"mailserver.dkim",
|
|
||||||
"mailserver.dmarcReporting",
|
|
||||||
"mailserver.fullTextSearch",
|
|
||||||
"mailserver.redis",
|
|
||||||
"mailserver.monitoring",
|
|
||||||
"mailserver.backup",
|
|
||||||
"mailserver.borg"]
|
|
||||||
|
|
||||||
def render_option_value(opt, attr):
|
|
||||||
if attr in opt:
|
|
||||||
if isinstance(opt[attr], dict) and '_type' in opt[attr]:
|
|
||||||
if opt[attr]['_type'] == 'literalExpression':
|
|
||||||
if '\n' in opt[attr]['text']:
|
|
||||||
res = '\n.. code:: nix\n\n' + textwrap.indent(opt[attr]['text'], ' ') + '\n'
|
|
||||||
else:
|
|
||||||
res = '``{}``'.format(opt[attr]['text'])
|
|
||||||
elif opt[attr]['_type'] == 'literalDocBook':
|
|
||||||
res = opt[attr]['text']
|
|
||||||
else:
|
|
||||||
s = str(opt[attr])
|
|
||||||
if s == "":
|
|
||||||
res = '``""``'
|
|
||||||
elif '\n' in s:
|
|
||||||
res = '\n.. code::\n\n' + textwrap.indent(s, ' ') + '\n'
|
|
||||||
else:
|
|
||||||
res = '``{}``'.format(s)
|
|
||||||
res = '- ' + attr + ': ' + res
|
|
||||||
else:
|
|
||||||
res = ""
|
|
||||||
return res
|
|
||||||
|
|
||||||
def print_option(name, value):
|
|
||||||
print(template.format(
|
|
||||||
key=name,
|
|
||||||
line="-"*len(name),
|
|
||||||
description=value['description'] or "",
|
|
||||||
type="- type: ``{}``".format(value['type']),
|
|
||||||
default=render_option_value(value, 'default'),
|
|
||||||
example=render_option_value(value, 'example')))
|
|
||||||
|
|
||||||
|
|
||||||
print(header)
|
|
||||||
for k, v in options.items():
|
|
||||||
if any([k.startswith(c) for c in groups]):
|
|
||||||
continue
|
|
||||||
print_option(k, v)
|
|
||||||
|
|
||||||
for c in groups:
|
|
||||||
print(c)
|
|
||||||
print("~"*len(c))
|
|
||||||
print()
|
|
||||||
for k, v in options.items():
|
|
||||||
if k.startswith(c):
|
|
||||||
print_option(k, v)
|
|
||||||
11
shell.nix
11
shell.nix
@@ -1 +1,10 @@
|
|||||||
(import (builtins.fetchGit "https://github.com/edolstra/flake-compat") { src = ./.; }).shellNix
|
(import
|
||||||
|
(
|
||||||
|
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
|
||||||
|
fetchTarball {
|
||||||
|
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
||||||
|
sha256 = lock.nodes.flake-compat.locked.narHash;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{ src = ./.; }
|
||||||
|
).shellNix
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ let
|
|||||||
};
|
};
|
||||||
services.dnsmasq = {
|
services.dnsmasq = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
# Fixme: once nixos-23.05 hhas been removed, could be replaced by
|
||||||
|
# settings.mx-host = [ "domain1.com,domain1,10" "domain2.com,domain2,10" ];
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
mx-host=domain1.com,domain1,10
|
mx-host=domain1.com,domain1,10
|
||||||
mx-host=domain2.com,domain2,10
|
mx-host=domain2.com,domain2,10
|
||||||
|
|||||||
Reference in New Issue
Block a user