mailserver: init

This commit is contained in:
ibizaman 2026-01-12 19:53:31 +01:00
parent a7c73695db
commit 3d2831e339
6 changed files with 1398 additions and 12 deletions

View file

@ -174,6 +174,7 @@ Also, the stack fits together nicely thanks to [contracts](#contracts).
- Nextcloud
- Audiobookshelf
- Deluge + *arr stack
- Simple NixOS Mailserver
- Firefly-iii
- Forgejo
- Grocy

View file

@ -2720,6 +2720,9 @@
"services-category-documents": [
"services.html#services-category-documents"
],
"services-category-emails": [
"services.html#services-category-emails"
],
"services-category-finance": [
"services.html#services-category-finance"
],
@ -3944,6 +3947,294 @@
"services-karakeep-usage": [
"services-karakeep.html#services-karakeep-usage"
],
"services-mailserver": [
"services-mailserver.html#services-mailserver"
],
"services-mailserver-certs": [
"services-mailserver.html#services-mailserver-certs"
],
"services-mailserver-debug": [
"services-mailserver.html#services-mailserver-debug"
],
"services-mailserver-debug-auth": [
"services-mailserver.html#services-mailserver-debug-auth"
],
"services-mailserver-debug-folder-mapping": [
"services-mailserver.html#services-mailserver-debug-folder-mapping"
],
"services-mailserver-debug-folders": [
"services-mailserver.html#services-mailserver-debug-folders"
],
"services-mailserver-debug-local-folders": [
"services-mailserver.html#services-mailserver-debug-local-folders"
],
"services-mailserver-debug-ports": [
"services-mailserver.html#services-mailserver-debug-ports"
],
"services-mailserver-debug-systemd": [
"services-mailserver.html#services-mailserver-debug-systemd"
],
"services-mailserver-declarative-ldap": [
"services-mailserver.html#services-mailserver-declarative-ldap"
],
"services-mailserver-impermanence": [
"services-mailserver.html#services-mailserver-impermanence"
],
"services-mailserver-mobile": [
"services-mailserver.html#services-mailserver-mobile"
],
"services-mailserver-options": [
"services-mailserver.html#services-mailserver-options"
],
"services-mailserver-options-shb.mailserver.adminPassword": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword"
],
"services-mailserver-options-shb.mailserver.adminPassword.request": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.request"
],
"services-mailserver-options-shb.mailserver.adminPassword.request.group": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.request.group"
],
"services-mailserver-options-shb.mailserver.adminPassword.request.mode": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.request.mode"
],
"services-mailserver-options-shb.mailserver.adminPassword.request.owner": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.request.owner"
],
"services-mailserver-options-shb.mailserver.adminPassword.request.restartUnits": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.request.restartUnits"
],
"services-mailserver-options-shb.mailserver.adminPassword.result": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.result"
],
"services-mailserver-options-shb.mailserver.adminPassword.result.path": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.adminPassword.result.path"
],
"services-mailserver-options-shb.mailserver.adminUsername": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.adminUsername"
],
"services-mailserver-options-shb.mailserver.backup": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.backup"
],
"services-mailserver-options-shb.mailserver.backup.request": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request"
],
"services-mailserver-options-shb.mailserver.backup.request.excludePatterns": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.excludePatterns"
],
"services-mailserver-options-shb.mailserver.backup.request.hooks": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.hooks"
],
"services-mailserver-options-shb.mailserver.backup.request.hooks.afterBackup": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.hooks.afterBackup"
],
"services-mailserver-options-shb.mailserver.backup.request.hooks.beforeBackup": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.hooks.beforeBackup"
],
"services-mailserver-options-shb.mailserver.backup.request.sourceDirectories": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.sourceDirectories"
],
"services-mailserver-options-shb.mailserver.backup.request.user": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.backup.request.user"
],
"services-mailserver-options-shb.mailserver.backup.result": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.backup.result"
],
"services-mailserver-options-shb.mailserver.backup.result.backupService": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.backup.result.backupService"
],
"services-mailserver-options-shb.mailserver.backup.result.restoreScript": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.backup.result.restoreScript"
],
"services-mailserver-options-shb.mailserver.domain": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.domain"
],
"services-mailserver-options-shb.mailserver.enable": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.enable"
],
"services-mailserver-options-shb.mailserver.imapSync": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.host": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.host"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialDrafts": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialDrafts"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialJunk": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialJunk"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialSent": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialSent"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialTrash": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.mapSpecialTrash"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.group": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.group"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.mode": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.mode"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.owner": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.owner"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.restartUnits": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.request.restartUnits"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.result": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.result"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.result.path": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.password.result.path"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.port": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.port"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.sslType": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.sslType"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.timeout": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.timeout"
],
"services-mailserver-options-shb.mailserver.imapSync.accounts._name_.username": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.accounts._name_.username"
],
"services-mailserver-options-shb.mailserver.imapSync.debug": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.debug"
],
"services-mailserver-options-shb.mailserver.imapSync.syncTimer": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.imapSync.syncTimer"
],
"services-mailserver-options-shb.mailserver.impermanence": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.impermanence"
],
"services-mailserver-options-shb.mailserver.ldap": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap"
],
"services-mailserver-options-shb.mailserver.ldap.account": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.account"
],
"services-mailserver-options-shb.mailserver.ldap.adminName": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminName"
],
"services-mailserver-options-shb.mailserver.ldap.adminPassword": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword"
],
"services-mailserver-options-shb.mailserver.ldap.adminPassword.request": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.request"
],
"services-mailserver-options-shb.mailserver.ldap.adminPassword.request.group": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.request.group"
],
"services-mailserver-options-shb.mailserver.ldap.adminPassword.request.mode": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.request.mode"
],
"services-mailserver-options-shb.mailserver.ldap.adminPassword.request.owner": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.request.owner"
],
"services-mailserver-options-shb.mailserver.ldap.adminPassword.request.restartUnits": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.request.restartUnits"
],
"services-mailserver-options-shb.mailserver.ldap.adminPassword.result": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.result"
],
"services-mailserver-options-shb.mailserver.ldap.adminPassword.result.path": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.adminPassword.result.path"
],
"services-mailserver-options-shb.mailserver.ldap.dcdomain": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.dcdomain"
],
"services-mailserver-options-shb.mailserver.ldap.enable": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.enable"
],
"services-mailserver-options-shb.mailserver.ldap.host": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.host"
],
"services-mailserver-options-shb.mailserver.ldap.port": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.port"
],
"services-mailserver-options-shb.mailserver.ldap.userGroup": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ldap.userGroup"
],
"services-mailserver-options-shb.mailserver.smtpRelay": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay"
],
"services-mailserver-options-shb.mailserver.smtpRelay.host": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.host"
],
"services-mailserver-options-shb.mailserver.smtpRelay.password": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password"
],
"services-mailserver-options-shb.mailserver.smtpRelay.password.request": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.request"
],
"services-mailserver-options-shb.mailserver.smtpRelay.password.request.group": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.request.group"
],
"services-mailserver-options-shb.mailserver.smtpRelay.password.request.mode": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.request.mode"
],
"services-mailserver-options-shb.mailserver.smtpRelay.password.request.owner": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.request.owner"
],
"services-mailserver-options-shb.mailserver.smtpRelay.password.request.restartUnits": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.request.restartUnits"
],
"services-mailserver-options-shb.mailserver.smtpRelay.password.result": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.result"
],
"services-mailserver-options-shb.mailserver.smtpRelay.password.result.path": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.password.result.path"
],
"services-mailserver-options-shb.mailserver.smtpRelay.port": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.port"
],
"services-mailserver-options-shb.mailserver.smtpRelay.username": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.smtpRelay.username"
],
"services-mailserver-options-shb.mailserver.ssl": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ssl"
],
"services-mailserver-options-shb.mailserver.ssl.paths": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ssl.paths"
],
"services-mailserver-options-shb.mailserver.ssl.paths.cert": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ssl.paths.cert"
],
"services-mailserver-options-shb.mailserver.ssl.paths.key": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ssl.paths.key"
],
"services-mailserver-options-shb.mailserver.ssl.systemdService": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.ssl.systemdService"
],
"services-mailserver-options-shb.mailserver.subdomain": [
"services-mailserver.html#services-mailserver-options-shb.mailserver.subdomain"
],
"services-mailserver-usage": [
"services-mailserver.html#services-mailserver-usage"
],
"services-mailserver-usage-backup": [
"services-mailserver.html#services-mailserver-usage-backup"
],
"services-mailserver-usage-disk-layout": [
"services-mailserver.html#services-mailserver-usage-disk-layout"
],
"services-mailserver-usage-ldap": [
"services-mailserver.html#services-mailserver-usage-ldap"
],
"services-mailserver-usage-secrets": [
"services-mailserver.html#services-mailserver-usage-secrets"
],
"services-monitoring-features": [
"blocks-monitoring.html#services-monitoring-features"
],

View file

@ -13,18 +13,19 @@ Not all services are yet documented. You can find all available services [in the
The following table summarizes for each documented service what features it provides. More
information is provided in the respective manual sections.
| Service | Backup | Reverse Proxy | SSO | LDAP | Monitoring | Profiling |
|----------------------|--------|---------------|-----|-------|------------|-----------|
| [*Arr][] | Y (1) | Y | Y | Y (4) | Y (2) | N |
| [Firefly-iii][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Forgejo][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Home-Assistant][] | Y (1) | Y | N | Y | Y (2) | N |
| [Jellyfin][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Karakeep][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Nextcloud Server][] | Y (1) | Y | Y | Y | Y (2) | P (3) |
| [Open WebUI][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Pinchflat][] | Y | Y | Y | Y (4) | Y (5) | N |
| [Vaultwarden][] | Y (1) | Y | Y | Y | Y (2) | N |
| Service | Backup | Reverse Proxy | SSO | LDAP | Monitoring | Profiling |
|-----------------------------|--------|---------------|-----|-------|------------|-----------|
| [*Arr][] | Y (1) | Y | Y | Y (4) | Y (2) | N |
| [Firefly-iii][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Forgejo][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Home-Assistant][] | Y (1) | Y | N | Y | Y (2) | N |
| [Jellyfin][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Karakeep][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Nextcloud Server][] | Y (1) | Y | Y | Y | Y (2) | P (3) |
| [Open WebUI][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Pinchflat][] | Y | Y | Y | Y (4) | Y (5) | N |
| [Simple NixOS Mailserver][] | Y | Y | N | Y | Y | N |
| [Vaultwarden][] | Y (1) | Y | Y | Y | Y (2) | N |
Legend: **N**: no but WIP; **P**: partial; **Y**: yes
@ -44,6 +45,7 @@ Legend: **N**: no but WIP; **P**: partial; **Y**: yes
[Nextcloud Server]: services-nextcloud.html
[Open WebUI]: services-open-webui.html
[Pinchflat]: services-pinchflat.html
[Simple NixOS Mailserver]: services-mailserver.html
[Vaultwarden]: services-vaultwarden.html
## Documents {#services-category-documents}
@ -52,6 +54,12 @@ Legend: **N**: no but WIP; **P**: partial; **Y**: yes
modules/services/nextcloud-server/docs/default.md
```
## Emails {#services-category-emails}
```{=include=} chapters html:into-file=//services-mailserver.html
modules/services/mailserver/docs/default.md
```
## Passwords {#services-category-passwords}
```{=include=} chapters html:into-file=//services-vaultwarden.html

View file

@ -112,6 +112,7 @@
"services/home-assistant" = ./modules/services/home-assistant.nix;
"services/jellyfin" = ./modules/services/jellyfin.nix;
"services/karakeep" = ./modules/services/karakeep.nix;
"services/mailserver" = ./modules/services/mailserver.nix;
"services/nextcloud-server" = {
module = ./modules/services/nextcloud-server.nix;
optionRoot = [
@ -398,6 +399,7 @@
self.nixosModules.home-assistant
self.nixosModules.jellyfin
self.nixosModules.karakeep
self.nixosModules.mailserver
self.nixosModules.nextcloud-server
self.nixosModules.open-webui
self.nixosModules.pinchflat
@ -435,6 +437,7 @@
nixosModules.home-assistant = modules/services/home-assistant.nix;
nixosModules.jellyfin = modules/services/jellyfin.nix;
nixosModules.karakeep = modules/services/karakeep.nix;
nixosModules.mailserver = modules/services/mailserver.nix;
nixosModules.nextcloud-server = modules/services/nextcloud-server.nix;
nixosModules.open-webui = modules/services/open-webui.nix;
nixosModules.paperless = modules/services/paperless.nix;

View file

@ -0,0 +1,703 @@
{
config,
lib,
shb,
pkgs,
...
}:
let
cfg = config.shb.mailserver;
in
{
imports = [
(
builtins.fetchGit {
url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver.git";
ref = "master";
rev = "5965fae920b6b97f39f94bdb6195631e274c93a5";
}
+ "/default.nix"
)
../blocks/lldap.nix
];
options.shb.mailserver = {
enable = lib.mkEnableOption "SHB's nixos-mailserver module";
subdomain = lib.mkOption {
type = lib.types.str;
description = "Subdomain under which imap and smtp functions will be served.";
default = "imap";
};
domain = lib.mkOption {
type = lib.types.str;
description = "domain under which imap and smtp functions will be served.";
example = "mydomain.com";
};
ssl = lib.mkOption {
description = "Path to SSL files";
type = lib.types.nullOr shb.contracts.ssl.certs;
default = null;
};
adminUsername = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Admin username.
postmaster will be made an alias of this user.
'';
example = "admin";
};
adminPassword = lib.mkOption {
description = "Admin user password.";
default = null;
type = lib.types.nullOr (
lib.types.submodule {
options = shb.contracts.secret.mkRequester {
mode = "0400";
owner = config.services.postfix.user;
ownerText = "services.postfix.user";
restartUnits = [ "dovecot.service" ];
};
}
);
};
imapSync = lib.mkOption {
description = ''
Synchronize one or more email providers through IMAP
to your dovecot2 instance.
This allows you to backup that email provider
and centralize your accounts in this dovecot2 instance.
'';
default = null;
type = lib.types.nullOr (
lib.types.submodule {
options = {
syncTimer = lib.mkOption {
type = lib.types.str;
default = "5m";
description = ''
Systemd timer for when imap sync job should happen.
This timer is not scheduling the job at regular intervals.
After a job finishes, the given amount of time is waited then the next job is started.
The default is set deliberatily slow to not spam you when setting up your mailserver.
When everything works, you will want to reduce it to 10s or something like that.
'';
example = "10s";
};
debug = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable verbose mbsync logging.";
};
accounts = lib.mkOption {
description = ''
Accounts to sync emails from using IMAP.
Emails will be stored under `''${config.mailserver.mailDirectory}/''${name}/''${username}`
'';
type = lib.types.attrsOf (
lib.types.submodule {
options = {
host = lib.mkOption {
type = lib.types.str;
description = "Hostname of the email's provider IMAP server.";
example = "imap.fastmail.com";
};
port = lib.mkOption {
type = lib.types.port;
description = "Port of the email's provider IMAP server.";
default = 993;
};
username = lib.mkOption {
type = lib.types.str;
description = "Username used to login to the email's provider IMAP server.";
example = "userA@fastmail.com";
};
password = lib.mkOption {
description = ''
Password used to login to the email's provider IMAP server.
The password could be an "app password" like for [Fastmail](https://www.fastmail.help/hc/en-us/articles/360058752854-App-passwords)
'';
type = lib.types.submodule {
options = shb.contracts.secret.mkRequester {
mode = "0400";
owner = config.mailserver.vmailUserName;
restartUnits = [ "mbsync.service" ];
};
};
};
sslType = lib.mkOption {
description = "Connection security method.";
type = lib.types.enum [
"IMAPS"
"STARTTLS"
];
default = "IMAPS";
};
timeout = lib.mkOption {
description = "Connect and data timeout.";
type = lib.types.int;
default = 120;
};
mapSpecialDrafts = lib.mkOption {
type = lib.types.str;
default = "Drafts";
description = ''
Drafts special folder name on far side.
You only need to change this if mbsync logs the following error:
Error: ... far side box Drafts cannot be opened
'';
};
mapSpecialSent = lib.mkOption {
type = lib.types.str;
default = "Sent";
description = ''
Sent special folder name on far side.
You only need to change this if mbsync logs the following error:
Error: ... far side box Sent cannot be opened
'';
};
mapSpecialTrash = lib.mkOption {
type = lib.types.str;
default = "Trash";
description = ''
Trash special folder name on far side.
You only need to change this if mbsync logs the following error:
Error: ... far side box Trash cannot be opened
'';
};
mapSpecialJunk = lib.mkOption {
type = lib.types.str;
default = "Junk";
description = ''
Junk special folder name on far side.
You only need to change this if mbsync logs the following error:
Error: ... far side box Junk cannot be opened
'';
example = "Spam";
};
};
}
);
};
};
}
);
};
smtpRelay = lib.mkOption {
description = ''
Proxy outgoing emails through an email provider.
In short, this can help you avoid having your outgoing emails marked as spam.
See the manual for a lengthier explanation.
'';
default = null;
type = lib.types.nullOr (
lib.types.submodule {
options = {
host = lib.mkOption {
type = lib.types.str;
description = "Hostname of the email's provider SMTP server.";
example = "smtp.fastmail.com";
};
port = lib.mkOption {
type = lib.types.port;
description = "Port of the email's provider SMTP server.";
default = 587;
};
username = lib.mkOption {
description = "Username used to login to the email's provider SMTP server.";
type = lib.types.str;
};
password = lib.mkOption {
description = ''
Password used to login to the email's provider IMAP server.
The password could be an "app password" like for [Fastmail](https://www.fastmail.help/hc/en-us/articles/360058752854-App-passwords)
'';
type = lib.types.submodule {
options = shb.contracts.secret.mkRequester {
mode = "0400";
owner = config.services.postfix.user;
ownerText = "services.postfix.user";
restartUnits = [ "postfix.service" ];
};
};
};
};
}
);
};
ldap = lib.mkOption {
description = ''
LDAP Integration.
Enabling this app will create a new LDAP configuration or update one that exists with
the given host.
'';
default = { };
type = lib.types.nullOr (
lib.types.submodule {
options = {
enable = lib.mkEnableOption "LDAP app.";
host = lib.mkOption {
type = lib.types.str;
description = ''
Host serving the LDAP server.
'';
default = "127.0.0.1";
};
port = lib.mkOption {
type = lib.types.port;
description = ''
Port of the service serving the LDAP server.
'';
default = 389;
};
dcdomain = lib.mkOption {
type = lib.types.str;
description = "dc domain for ldap.";
example = "dc=mydomain,dc=com";
};
account = lib.mkOption {
type = lib.types.str;
description = ''
Select one account from those defined in `shb.mailserver.imapSync.accounts`
to login with.
Using LDAP, you can only connect to one account.
This limitation could maybe be lifted, feel free to post an issue if you need this.
'';
};
adminName = lib.mkOption {
type = lib.types.str;
description = "Admin user of the LDAP server.";
default = "admin";
};
adminPassword = lib.mkOption {
description = "LDAP server admin password.";
type = lib.types.submodule {
options = shb.contracts.secret.mkRequester {
mode = "0400";
owner = "nextcloud";
restartUnits = [ "dovecot.service" ];
};
};
};
userGroup = lib.mkOption {
type = lib.types.str;
description = "Group users must belong to to be able to use mails.";
default = "mail_user";
};
};
}
);
};
backup = lib.mkOption {
description = ''
Backup emails.
'';
default = { };
type = lib.types.submodule {
options = shb.contracts.backup.mkRequester {
user = config.mailserver.vmailUserName;
sourceDirectories = builtins.filter (x: x != null) [
config.mailserver.indexDir
config.mailserver.mailDirectory
config.mailserver.sieveDirectory
config.mailserver.dkimKeyDirectory
];
sourceDirectoriesText = ''
[
config.mailserver.indexDir
config.mailserver.mailDirectory
config.mailserver.sieveDirectory
config.mailserver.dkimKeyDirectory
]
'';
};
};
};
impermanence = lib.mkOption {
description = ''
Path to save when using impermanence setup.
'';
type = lib.types.attrsOf lib.types.str;
default = {
index = config.mailserver.indexDir;
mail = config.mailserver.mailDirectory;
sieve = config.mailserver.sieveDirectory;
dkim = config.mailserver.dkimKeyDirectory;
};
defaultText = lib.literalExpression ''
{
index = config.mailserver.indexDir;
mail = config.mailserver.mailDirectory;
sieve = config.mailserver.sieveDirectory;
dkim = config.mailserver.dkimKeyDirectory;
}
'';
};
};
config = lib.mkMerge [
(lib.mkIf cfg.enable {
mailserver = {
enable = true;
stateVersion = 3;
fqdn = "${cfg.subdomain}.${cfg.domain}";
domains = [ cfg.domain ];
localDnsResolver = false;
certificateScheme = "acme-nginx";
enableImapSsl = true;
enableSubmissionSsl = true;
# Using / is needed for iOS mail.
# Both following options are used to organize subfolders in subdirectories.
hierarchySeparator = "/";
useFsLayout = true;
};
services.postfix.config = {
smtpd_tls_security_level = lib.mkForce "encrypt";
};
# Is probably needed for iOS mail.
services.dovecot2.extraConfig = ''
ssl_min_protocol = TLSv1.2
ssl_cipher_list = HIGH:!aNULL:!MD5
'';
services.nginx = {
enable = true;
virtualHosts."${cfg.domain}" =
let
announce = pkgs.writeTextDir "config-v1.1.xml" ''
<?xml version="1.0" encoding="UTF-8"?>
<clientConfig version="1.1">
<emailProvider id="${cfg.domain}">
<domain>${cfg.domain}</domain>
<displayName>${cfg.domain} Mailserver</displayName>
<!-- Incoming IMAP server -->
<incomingServer type="imap">
<hostname>${cfg.subdomain}.${cfg.domain}</hostname>
<port>993</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
<!-- Outgoing SMTP server -->
<outgoingServer type="smtp">
<hostname>${cfg.subdomain}.${cfg.domain}</hostname>
<port>465</port>
<socketType>STARTTLS</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</outgoingServer>
</emailProvider>
</clientConfig>
'';
in
{
forceSSL = true; # Redirect HTTP → HTTPS
root = "/var/www"; # Dummy root
locations."/.well-known/autoconfig/mail/" = {
alias = "${announce}/";
extraConfig = ''
default_type application/xml;
'';
};
};
};
})
(lib.mkIf (cfg.enable && cfg.adminUsername != null) {
assertions = [
{
assertion = cfg.adminPassword != null;
message = "`shb.mailserver.adminPassword` must be not null if `shb.mailserver.adminUsername` is not null.";
}
];
mailserver = {
# To create the password hashes, use:
# nix run nixpkgs#mkpasswd -- --run 'mkpasswd -s'
loginAccounts = {
"${cfg.adminUsername}@${cfg.domain}" = {
hashedPasswordFile = cfg.adminPassword.result.path;
aliases = [ "postmaster@${cfg.domain}" ];
};
};
};
})
(lib.mkIf (cfg.enable && cfg.ldap != null) {
assertions = [
{
assertion = cfg.adminUsername == null;
message = "`shb.mailserver.adminUsername` must be null `shb.mailserver.ldap` integration is set.";
}
];
shb.lldap.ensureGroups = {
${cfg.ldap.userGroup} = { };
};
mailserver = {
ldap = {
enable = true;
uris = [
"ldap://${cfg.ldap.host}:${toString cfg.ldap.port}"
];
searchBase = "ou=people,${cfg.ldap.dcdomain}";
searchScope = "sub";
bind = {
dn = "uid=${cfg.ldap.adminName},ou=people,${cfg.ldap.dcdomain}";
passwordFile = cfg.ldap.adminPassword.result.path;
};
# Note that nixos simple mailserver sets auth_bind=yes
# which means authentication binds are used.
# https://doc.dovecot.org/2.3/configuration_manual/authentication/ldap_bind/#authentication-ldap-bind
dovecot =
let
filter = "(&(objectClass=inetOrgPerson)(mail=%{user})(memberOf=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain}))";
in
{
passAttrs = "user=user";
passFilter = filter;
userAttrs = lib.concatStringsSep "," [
"=home=${config.mailserver.mailDirectory}/${cfg.ldap.account}/%u"
# "mail=maildir:${config.mailserver.mailDirectory}/${cfg.ldap.account}/%u/mail"
"uid=${config.mailserver.vmailUserName}"
"gid=${config.mailserver.vmailGroupName}"
];
userFilter = filter;
};
postfix = {
filter = "(&(objectClass=inetOrgPerson)(mail=%s)(memberOf=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain}))";
mailAttribute = "mail";
uidAttribute = "mail";
};
};
};
})
(lib.mkIf (cfg.enable && cfg.imapSync != null) {
systemd.services.mbsync =
let
configFile =
let
mkAccount = name: acct: ''
# ${name} account
IMAPAccount ${name}
Host ${acct.host}
Port ${toString acct.port}
User ${acct.username}
PassCmd "cat ${acct.password.result.path}"
TLSType ${acct.sslType}
AuthMechs LOGIN
Timeout ${toString acct.timeout}
IMAPStore ${name}-remote
Account ${name}
MaildirStore ${name}-local
INBOX ${config.mailserver.mailDirectory}/${name}/${acct.username}/mail/
# Maps subfolders on far side to actual subfolders on disk.
# The other option is Maildir++ but then the mailserver.hierarchySeparator must be set to a dot '.'
SubFolders Verbatim
Path ${config.mailserver.mailDirectory}/${name}/${acct.username}/mail/
Channel ${name}-main
Far :${name}-remote:
Near :${name}-local:
Patterns * !Drafts !Sent !Trash !Junk !${acct.mapSpecialDrafts} !${acct.mapSpecialSent} !${acct.mapSpecialTrash} !${acct.mapSpecialJunk}
Create Both
Expunge Both
SyncState *
Sync All
CopyArrivalDate yes # Preserve date from incoming message.
Channel ${name}-drafts
Far :${name}-remote:"${acct.mapSpecialDrafts}"
Near :${name}-local:"Drafts"
Create Both
Expunge Both
SyncState *
Sync All
CopyArrivalDate yes # Preserve date from incoming message.
Channel ${name}-sent
Far :${name}-remote:"${acct.mapSpecialSent}"
Near :${name}-local:"Sent"
Create Both
Expunge Both
SyncState *
Sync All
CopyArrivalDate yes # Preserve date from incoming message.
Channel ${name}-trash
Far :${name}-remote:"${acct.mapSpecialTrash}"
Near :${name}-local:"Trash"
Create Both
Expunge Both
SyncState *
Sync All
CopyArrivalDate yes # Preserve date from incoming message.
Channel ${name}-junk
Far :${name}-remote:"${acct.mapSpecialJunk}"
Near :${name}-local:"Junk"
Create Both
Expunge Both
SyncState *
Sync All
CopyArrivalDate yes # Preserve date from incoming message.
Group ${name}
Channel ${name}-main
Channel ${name}-drafts
Channel ${name}-sent
Channel ${name}-trash
Channel ${name}-junk
# END ${name} account
'';
in
pkgs.writeText "mbsync.conf" (
lib.concatStringsSep "\n" (lib.mapAttrsToList mkAccount cfg.imapSync.accounts)
);
in
{
description = "Sync mailbox";
serviceConfig = {
Type = "oneshot";
User = config.mailserver.vmailUserName;
};
script =
let
debug = if cfg.imapSync.debug then "-V" else "";
in
''
${pkgs.isync}/bin/mbsync --all ${debug} --config ${configFile}
'';
};
systemd.tmpfiles.rules =
let
mkAccount =
name: acct:
# The equal sign makes sure parent directories have the corret user and group too.
[
"d '${config.mailserver.mailDirectory}/${name}' 0750 ${config.mailserver.vmailUserName} ${config.mailserver.vmailGroupName} - -"
"d '${config.mailserver.mailDirectory}/${name}/${acct.username}' 0750 ${config.mailserver.vmailUserName} ${config.mailserver.vmailGroupName} - -"
];
in
lib.flatten (lib.mapAttrsToList mkAccount cfg.imapSync.accounts);
systemd.timers.mbsync = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = cfg.imapSync.syncTimer;
OnUnitActiveSec = cfg.imapSync.syncTimer;
};
};
})
(lib.mkIf (cfg.enable && cfg.smtpRelay != null) (
let
url = "[${cfg.smtpRelay.host}]:${toString cfg.smtpRelay.port}";
in
{
assertions = [
{
assertion = lib.hasAttr cfg.adminPassword != null;
message = "`shb.mailserver.adminPassword` must be not null if `shb.mailserver.adminUsername` is not null.";
}
];
# Inspiration from https://www.brull.me/postfix/debian/fastmail/2016/08/16/fastmail-smtp.html
services.postfix = {
settings.main = {
relayhost = [ url ];
smtp_sasl_auth_enable = "yes";
smtp_sasl_password_maps = "texthash:/run/secrets/postfix/postfix-smtp-relay-password";
smtp_sasl_security_options = "noanonymous";
smtp_use_tls = "yes";
};
};
systemd.services.postfix-pre = {
script = shb.replaceSecrets {
userConfig = {
inherit url;
inherit (cfg.smtpRelay) username;
password.source = cfg.smtpRelay.password.result.path;
};
generator =
name:
{
url,
username,
password,
}:
pkgs.writeText "postfix-smtp-relay-password" ''
${url} ${username}:${password}
'';
resultPath = "/run/secrets/postfix/postfix-smtp-relay-password";
user = config.services.postfix.user;
};
serviceConfig.Type = "oneshot";
wantedBy = [ "multi-user.target" ];
before = [ "postfix.service" ];
requiredBy = [ "postfix.service" ];
};
}
))
];
}

View file

@ -0,0 +1,380 @@
# Mailserver Service {#services-mailserver}
Defined in [`/modules/services/mailserver.nix`](@REPO@/modules/services/mailserver.nix).
This NixOS module is a service that sets up
the [NixOS Simple Mailserver](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver) project.
It integrates the upstream project
with the SHB modules like the SSL module, the contract for secrets and the LLDAP module.
It also exposes an XML file which allows some email clients to auto configure themselves.
Setting up a self-hosted email server in this age
can be quite time consuming because you need to maintain
a good IP hygiene to avoid being marked as spam from the big players.
To avoid needing to deal with this,
this module provides the means
to use an email provider (like Fastmail or ProtonMail) as a mere proxy.
If you also setup the email provider using your own custom domain,
this combination allows you to change email provider
without needing to change your clients or notify your email correspondents
and keep a backup of all your emails at the same time.
The setup looks like so:
```
Domain --[ DNS records ]-> Email Provider --[ mbsync ]-> SHB Server
Internet <---------------- Email Provider <-[ postfix ]-- SHB Server
```
Configuring your domain name to point to your email provider is out of scope here.
See the documentation for "custom domain" for you email provider,
like for [Fastmail](https://www.fastmail.com/features/domains/)
and [ProtonMail](https://proton.me/support/custom-domain)
To use an email provider as a proxy, use the
[shb.mailserver.imapSync](#services-mailserver-options-shb.mailserver.imapSync)
and [shb.mailserver.smtpRelay](#services-mailserver-options-shb.mailserver.smtpRelay),
options.
## Usage {#services-mailserver-usage}
The following snippet assumes a few blocks have been setup already:
- the [secrets block](usage.html#usage-secrets) with SOPS,
- the [`shb.ssl` block](blocks-ssl.html#usage),
- the [`shb.lldap` block](blocks-lldap.html#blocks-lldap-global-setup).
```nix
let
domain = "example.com";
username = "me@example.com";
in
{
imports = [
selfhostblocks.nixosModules.mailserver
];
shb.mailserver = {
enable = true;
inherit domain;
subdomain = "imap";
ssl = config.shb.certs.certs.letsencrypt."domain";
imapSync = {
syncTimer = "10s";
accounts.fastmail = {
host = "imap.fastmail.com";
port = 993;
inherit username;
password.result = config.shb.sops.secret."mailserver/imap/fastmail/password".result;
mapSpecialJunk = "Spam";
};
};
smtpRelay = {
host = "smtp.fastmail.com";
port = 587;
inherit username;
password.result = config.shb.sops.secret."mailserver/smtp/fastmail/password".result;
};
ldap = {
enable = true;
host = "127.0.0.1";
port = config.shb.lldap.ldapPort;
dcdomain = config.shb.lldap.dcdomain;
adminName = "admin";
adminPassword.result = config.shb.sops.secret."mailserver/ldap_admin_password".result;
account = "fastmail";
};
};
# Optionally add some mailboxes
mailserver.mailboxes = {
Drafts = {
auto = "subscribe";
specialUse = "Drafts";
};
Junk = {
auto = "subscribe";
specialUse = "Junk";
};
Sent = {
auto = "subscribe";
specialUse = "Sent";
};
Trash = {
auto = "subscribe";
specialUse = "Trash";
};
Archive = {
auto = "subscribe";
specialUse = "Archive";
};
};
shb.sops.secret."mailserver/smtp/fastmail/password".request =
config.shb.mailserver.smtpRelay.password.request;
shb.sops.secret."mailserver/imap/fastmail/password".request =
config.shb.mailserver.imapSync.accounts.fastmail.password.request;
shb.sops.secret."mailserver/ldap_admin_password" = {
request = config.shb.mailserver.ldap.adminPassword.request;
# This reuses the admin password set in the shb.lldap module.
settings.key = "lldap/user_password";
};
}
```
### Secrets {#services-mailserver-usage-secrets}
Secrets can be randomly generated with `nix run nixpkgs#openssl -- rand -hex 64`.
### LDAP {#services-mailserver-usage-ldap}
The [user](#services-mailserver-options-shb.mailserver.ldap.userGroup)
LDAP group is created automatically.
### Disk Layout {#services-mailserver-usage-disk-layout}
The disk layout has been purposely set to use slashes `/` for subfolders.
By experience, this works better with iOS mail.
### Backup {#services-mailserver-usage-backup}
Backing up your emails using the [Restic block](blocks-restic.html) is done like so:
```nix
shb.restic.instances."mailserver" = {
request = config.shb.mailserver.backup;
settings = {
enable = true;
};
};
```
The name `"mailserver"` in the `instances` can be anything.
The `config.shb.mailserver.backup` option provides what directories to backup.
You can define any number of Restic instances to backup your emails multiple times.
You will then need to configure more options like the `repository`,
as explained in the [restic](blocks-restic.html) documentation.
### Certificates {#services-mailserver-certs}
For Let's Encrypt certificates, add:
```nix
let
domain = "example.com";
in
{
shb.certs.certs.letsencrypt.${domain}.extraDomains = [
"${config.shb.mailserver.subdomain}.${config.shb.mailserver.domain}"
];
}
```
### Impermanence {#services-mailserver-impermanence}
To save the data folder in an impermanence setup, add:
```nix
{
shb.zfs.datasets."safe/mailserver/index".path = config.shb.mailserver.impermanence.index;
shb.zfs.datasets."safe/mailserver/mail".path = config.shb.mailserver.impermanence.mail;
shb.zfs.datasets."safe/mailserver/sieve".path = config.shb.mailserver.impermanence.sieve;
shb.zfs.datasets."safe/mailserver/dkim".path = config.shb.mailserver.impermanence.dkim;
}
```
### Declarative LDAP {#services-mailserver-declarative-ldap}
To add a user `USERNAME` to the user group, add:
```nix
shb.lldap.ensureUsers.USERNAME.groups = [
config.shb.mailserver.ldap.userGroup
];
```
## Debug {#services-mailserver-debug}
Debugging this will be certainly necessary.
The first issue you will encounter will probably be with `mbsync`
under the [shb.mailserver.imapSync](#services-mailserver-options-shb.mailserver.imapSync) option
with the folder name mapping.
### Systemd Services {#services-mailserver-debug-systemd}
The 3 systemd services setup by this module are:
- `mbsync.service`
- `dovecot.service`
- `postfix.service`
### Folders {#services-mailserver-debug-folders}
The 4 folders where state is stored are:
- `config.mailserver.indexDir` = `/var/lib/dovecot/indices`
- `config.mailserver.mailDirectory` = `/var/vmail`
- `config.mailserver.sieveDirectory` = `/var/sieve`
- `config.mailserver.dkimKeyDirectory` = `/var/dkim`
### Open Ports {#services-mailserver-debug-ports}
The ports opened by default in this module are:
- Submissions: 465
- Imap: 993
You will need to forward those ports on your router
if you want to access to your emails from the internet.
The complete list can be found in the [upstream repository](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/5965fae920b6b97f39f94bdb6195631e274c93a5/mail-server/networking.nix).
### List Email Provider Folder Mapping {#services-mailserver-debug-folder-mapping}
Replace `$USER` and `$PASSWORD` by those used to connect to your email provider.
Yes, you will need to enter verbatim `a LOGIN ...` and `b LIST "" "*"`.
```
$ nix run nixpkgs#openssl -- s_client -connect imap.fastmail.com:993 -crlf -quiet
a LOGIN $USER $password
b LIST "" "*"
```
Example output will be:
```
* LIST (\HasNoChildren) "/" INBOX
* LIST (\HasNoChildren \Drafts) "/" Drafts
* LIST (\HasNoChildren \Sent) "/" Sent
* LIST (\Noinferiors \HasNoChildren \Junk) "/" Spam
...
```
Here you can see the special folder `\Junk` is actually named `Spam`.
To handle this, set the `.mapSpecial*` options:
```
{
shb.mailserver.imapSync.accounts.<account> = {
mapSpecialJunk = "Spam";
};
}
```
### List Local Folders {#services-mailserver-debug-local-folders}
Check the local folders to make sure the mapping is correct
and all folders are correctly downloaded.
For example, if the mapping above is wrong, you will see both a
`Junk` and `Spam` folder while if it is correct,
you will only see the `Junk` folder.
```
$ sudo doveadm mailbox list -u $USER
Junk
Trash
Drafts
Sent
INBOX
MyCustomFolder
```
The following command shows the number of messages in a folder:
```
$ sudo doveadm mailbox status -u $USER messages INBOX
INBOX messages=13591
```
If any folder is not appearing or has 0 message but should have some,
it could mean dovecot is not setup correctly and assumes an incorrect folder layout.
If that is the case, check the user config with:
```
$ sudo doveadm user $USER
field value
uid 5000
gid 5000
home /var/vmail/fastmail/$USER
mail maildir:~/mail:LAYOUT=fs
virtualMail
```
### Test Auth {#services-mailserver-debug-auth}
To test authentication to your dovecot instance, run:
```
$ nix run nixpkgs#openssl -- s_client -connect $SUBDOMAIN.$DOMAIN:993 -crlf -quiet
. LOGIN $USER $PASSWORD
```
You must here also enter the second line verbatim,
replacing your user and password with the real one.
On success, you will see:
```
. OK [CAPABILITY IMAP4rev1 ...] Logged in
```
Otherwise, either if the password is wrong or,
when using LDAP if the user is not part of the LDAP group, you will see:
```
. NO [AUTHENTICATIONFAILED] Authentication failed.
```
To test the postfix instance, run:
```
$ swaks \
--server $SUBDOMAIN.$DOMAIN \
--port 465 \
--tls-on-connect \
--auth LOGIN \
--auth-user $USER \
--auth-password '$PASSWORD' \
--from $USER \
--to $USER
```
Try once with a wrong password and once with a correct one.
The former should log:
```
<~* 535 5.7.8 Error: authentication failed: (reason unavailable)
```
## Mobile Apps {#services-mailserver-mobile}
This module was tested with:
- the iOS mail mobile app,
- Thunderbird on NixOS.
The iOS mail app is pretty finicky.
If downloading emails does not work,
make sure the certificate used includes the whole chain:
```bash
$ openssl s_client -connect $SUBDOMAIN.$DOMAIN:993 -showcerts
```
Normally, the other options are setup correctly but if it fails for you,
feel free to open an issue.
## Options Reference {#services-mailserver-options}
```{=include=} options
id-prefix: services-mailserver-options-
list-id: selfhostblocks-service-mailserver-options
source: @OPTIONS_JSON@
```