792 lines
26 KiB
Nix
792 lines
26 KiB
Nix
{
|
|
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 = "e33fbde199eaad513ef5d0746db19d5878150232";
|
|
}
|
|
+ "/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 dovecot instance.
|
|
|
|
This allows you to backup that email provider
|
|
and centralize your accounts in this dovecot 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.storage.path}/''${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.storage.owner;
|
|
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 = null;
|
|
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";
|
|
};
|
|
};
|
|
}
|
|
);
|
|
};
|
|
|
|
stateVersion = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
# SHB started at stateVersion 3.
|
|
default = 4;
|
|
description = ''
|
|
Tracking stateful version changes as an incrementing number.
|
|
|
|
When a new release comes out we may require manual migration steps to
|
|
be completed, before the new version can be put into production.
|
|
|
|
If your `stateVersion` is too low one or multiple assertions may
|
|
trigger to give you instructions on what migrations steps are required
|
|
to continue. Increase the `stateVersion` as instructed by the assertion
|
|
message.
|
|
|
|
See https://nixos-mailserver.readthedocs.io/en/latest/release-notes.html
|
|
and https://nixos-mailserver.readthedocs.io/en/latest/migrations.html
|
|
'';
|
|
};
|
|
|
|
backup = lib.mkOption {
|
|
description = ''
|
|
Backup emails, index and sieve.
|
|
'';
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
options = shb.contracts.backup.mkRequester {
|
|
user = config.mailserver.storage.owner;
|
|
sourceDirectories = builtins.filter (x: x != null) [
|
|
config.mailserver.indexDir
|
|
config.mailserver.storage.path
|
|
config.mailserver.sieveDirectory
|
|
];
|
|
sourceDirectoriesText = ''
|
|
[
|
|
config.mailserver.indexDir
|
|
config.mailserver.storage.path
|
|
config.mailserver.sieveDirectory
|
|
]
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
backupDKIM = lib.mkOption {
|
|
description = ''
|
|
Backup dkim directory.
|
|
'';
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
options = shb.contracts.backup.mkRequester {
|
|
user = config.services.rspamd.user;
|
|
userText = "services.rspamd.user";
|
|
sourceDirectories = builtins.filter (x: x != null) [
|
|
config.mailserver.dkim.keyDirectory
|
|
];
|
|
sourceDirectoriesText = ''
|
|
[
|
|
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.storage.path;
|
|
sieve = config.mailserver.sieveDirectory;
|
|
dkim = config.mailserver.dkimKeyDirectory;
|
|
};
|
|
defaultText = lib.literalExpression ''
|
|
{
|
|
index = config.mailserver.indexDir;
|
|
mail = config.mailserver.storage.path;
|
|
sieve = config.mailserver.sieveDirectory;
|
|
dkim = config.mailserver.dkimKeyDirectory;
|
|
}
|
|
'';
|
|
};
|
|
|
|
dashboard = lib.mkOption {
|
|
description = ''
|
|
Dashboard contract consumer
|
|
'';
|
|
default = { };
|
|
type = lib.types.submodule {
|
|
options = shb.contracts.dashboard.mkRequester {
|
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
|
externalUrlText = "https://\${config.shb.mailserver.subdomain}.\${config.shb.mailserver.domain}";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkMerge [
|
|
(lib.mkIf cfg.enable {
|
|
mailserver = {
|
|
enable = true;
|
|
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
|
inherit (cfg) stateVersion;
|
|
domains = [ cfg.domain ];
|
|
|
|
localDnsResolver = false;
|
|
|
|
enableImapSsl = true;
|
|
enableSubmissionSsl = true;
|
|
x509 = {
|
|
certificateFile = cfg.ssl.paths.cert;
|
|
privateKeyFile = cfg.ssl.paths.key;
|
|
};
|
|
|
|
# Using / is needed for iOS mail.
|
|
# Both following options are used to organize subfolders in subdirectories.
|
|
hierarchySeparator = "/";
|
|
storage = {
|
|
directoryLayout = "fs";
|
|
};
|
|
};
|
|
|
|
services.postfix.settings.main = {
|
|
smtpd_tls_security_level = lib.mkForce "encrypt";
|
|
};
|
|
|
|
# Is probably needed for iOS mail.
|
|
services.dovecot2.settings = {
|
|
ssl_min_protocol = "TLSv1.2";
|
|
# This conflicts with the default setting which seems much better than this one.
|
|
# So I'm leaving the default settings and we'll see if something breaks.
|
|
# 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>SSL</socketType>
|
|
<authentication>password-cleartext</authentication>
|
|
<username>%EMAILADDRESS%</username>
|
|
</outgoingServer>
|
|
|
|
</emailProvider>
|
|
</clientConfig>
|
|
'';
|
|
in
|
|
{
|
|
forceSSL = true; # Redirect HTTP → HTTPS
|
|
sslCertificate = cfg.ssl.paths.cert;
|
|
sslCertificateKey = cfg.ssl.paths.key;
|
|
root = "/var/www"; # Dummy root
|
|
locations."/.well-known/autoconfig/mail/" = {
|
|
alias = "${announce}/";
|
|
extraConfig = ''
|
|
default_type application/xml;
|
|
'';
|
|
};
|
|
};
|
|
virtualHosts."${cfg.subdomain}.${cfg.domain}" =
|
|
let
|
|
landingPage = pkgs.writeTextDir "index.html" ''
|
|
<html><body>
|
|
<p>Configuration of the mailserver is done automatically thanks to
|
|
<a href="https://${cfg.domain}/.well-known/autoconfig/mail/config-v1.1.xml">${cfg.domain}/.well-known/autoconfig/mail/config-v1.1.xml</a>.</p>
|
|
</body></html>
|
|
'';
|
|
in
|
|
{
|
|
forceSSL = true; # Redirect HTTP → HTTPS
|
|
sslCertificate = cfg.ssl.paths.cert;
|
|
sslCertificateKey = cfg.ssl.paths.key;
|
|
root = "/var/www"; # Dummy root
|
|
locations."/" = {
|
|
alias = "${landingPage}/";
|
|
extraConfig = ''
|
|
default_type application/html;
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
})
|
|
(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}"
|
|
];
|
|
base = "ou=people,${cfg.ldap.dcdomain}";
|
|
scope = "sub";
|
|
bind = {
|
|
dn = "uid=${cfg.ldap.adminName},ou=people,${cfg.ldap.dcdomain}";
|
|
passwordFile = cfg.ldap.adminPassword.result.path;
|
|
};
|
|
dovecot =
|
|
let
|
|
filter = "(&(objectClass=inetOrgPerson)(mail=%{user})(memberOf=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain}))";
|
|
in
|
|
{
|
|
passFilter = filter;
|
|
userFilter = filter;
|
|
};
|
|
# username needs to be set to mail so postfix maps correctly the mail address.
|
|
# Otherwise we get error messages when sending
|
|
# Sender address rejected: not owned by user
|
|
attributes = {
|
|
username = "mail";
|
|
};
|
|
postfix = {
|
|
filter = "(&(objectClass=inetOrgPerson)(mail=%s)(memberOf=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain}))";
|
|
};
|
|
};
|
|
};
|
|
services.dovecot2.settings = {
|
|
"userdb ldap" = {
|
|
fields.home = lib.mkForce "${config.mailserver.storage.path}/${cfg.ldap.account}/%{user}";
|
|
fields.mail_index_path = lib.mkForce "${config.mailserver.storage.path}/${cfg.ldap.account}/%{user}";
|
|
};
|
|
"passdb ldap" = {
|
|
# LLDAP does not understand ldap user password setup.
|
|
fields.password = lib.mkForce null;
|
|
# We use bind DN auth.
|
|
# https://doc.dovecot.org/2.3/configuration_manual/authentication/ldap_bind/#authentication-ldap-bind
|
|
bind = true;
|
|
};
|
|
};
|
|
})
|
|
(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.storage.path}/${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.storage.path}/${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.storage.owner;
|
|
};
|
|
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.storage.path}/${name}' 0750 ${config.mailserver.storage.owner} ${config.mailserver.storage.group} - -"
|
|
"d '${config.mailserver.storage.path}/${name}/${acct.username}' 0750 ${config.mailserver.storage.owner} ${config.mailserver.storage.group} - -"
|
|
];
|
|
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" ];
|
|
};
|
|
}
|
|
))
|
|
];
|
|
}
|