delete mail container
This commit is contained in:
parent
03a77eaff3
commit
fa1396acb7
|
@ -1,95 +0,0 @@
|
||||||
{
|
|
||||||
self,
|
|
||||||
tree,
|
|
||||||
lib,
|
|
||||||
inputs,
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit (lib.modules) mkMerge mkForce;
|
|
||||||
inherit (lib.lists) flatten;
|
|
||||||
|
|
||||||
ports = [
|
|
||||||
# SMTP
|
|
||||||
25
|
|
||||||
# Submission
|
|
||||||
587
|
|
||||||
# Submission w/ SSL
|
|
||||||
465
|
|
||||||
# IMAP
|
|
||||||
143
|
|
||||||
# IMAP w/ SSL
|
|
||||||
993
|
|
||||||
# Sieve
|
|
||||||
4190
|
|
||||||
];
|
|
||||||
|
|
||||||
sharedFiles = [
|
|
||||||
"/var/lib/acme/mail.owo.monster/fullchain.pem"
|
|
||||||
"/var/lib/acme/mail.owo.monster/key.pem"
|
|
||||||
];
|
|
||||||
in {
|
|
||||||
containers.mail = {
|
|
||||||
autoStart = true;
|
|
||||||
|
|
||||||
bindMounts = mkMerge (map (file: {
|
|
||||||
"${file}" = {
|
|
||||||
hostPath = "${file}";
|
|
||||||
};
|
|
||||||
})
|
|
||||||
sharedFiles);
|
|
||||||
|
|
||||||
specialArgs = {
|
|
||||||
inherit inputs;
|
|
||||||
inherit tree;
|
|
||||||
inherit self;
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {...}: {
|
|
||||||
nixpkgs.pkgs = pkgs;
|
|
||||||
|
|
||||||
imports = flatten (with tree; [
|
|
||||||
presets.nixos.containerBase
|
|
||||||
|
|
||||||
(with hosts.hetzner-arm.containers.mail; [
|
|
||||||
modules.mailserver
|
|
||||||
|
|
||||||
profiles.mailserver
|
|
||||||
profiles.restic
|
|
||||||
])
|
|
||||||
|
|
||||||
./secrets.nix
|
|
||||||
]);
|
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"d /var/lib/acme - root root"
|
|
||||||
"d /var/lib/acme/mail.owo.monster - root root"
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.firewall = {
|
|
||||||
enable = mkForce false;
|
|
||||||
};
|
|
||||||
|
|
||||||
home-manager.users.root.home.stateVersion = "25.05";
|
|
||||||
system.stateVersion = "25.05";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# ssl for mail
|
|
||||||
services.nginx = {
|
|
||||||
enable = true;
|
|
||||||
virtualHosts."mail.owo.monster" = {
|
|
||||||
serverName = "mail.owo.monster";
|
|
||||||
serverAliases = ["owo.monster"];
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
acmeRoot = "/var/lib/acme/acme-challenge";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall = {
|
|
||||||
allowedTCPPorts = ports;
|
|
||||||
allowedUDPPorts = ports;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit (lib) types;
|
|
||||||
inherit (lib.options) mkEnableOption mkOption;
|
|
||||||
|
|
||||||
cfg = config.services.mailserver;
|
|
||||||
in {
|
|
||||||
options.services.mailserver = {
|
|
||||||
enable = mkEnableOption "mailserver";
|
|
||||||
|
|
||||||
fqdn = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = "domain used for mx records";
|
|
||||||
};
|
|
||||||
|
|
||||||
domains = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
description = "all domains for receiving mail on";
|
|
||||||
};
|
|
||||||
|
|
||||||
debugMode = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = "enable debug logging on everything";
|
|
||||||
};
|
|
||||||
|
|
||||||
sslConfig = {
|
|
||||||
useACME = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
};
|
|
||||||
cert = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "/var/lib/acme/${cfg.fqdn}/fullchain.pem";
|
|
||||||
};
|
|
||||||
key = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "/var/lib/acme/${cfg.fqdn}/key.pem";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
spf = {
|
|
||||||
enable = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
};
|
|
||||||
policydConfig = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
accounts = mkOption {
|
|
||||||
# where attrName = email for login
|
|
||||||
default = {};
|
|
||||||
type = types.attrsOf (types.submodule {
|
|
||||||
options = {
|
|
||||||
passwordHashFile = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = ''
|
|
||||||
a file containing the hashed password for user, loaded at runtime
|
|
||||||
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
aliases = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
default = [];
|
|
||||||
description = "a list of aliases for receiving/sending mail";
|
|
||||||
};
|
|
||||||
sieveScript = mkOption {
|
|
||||||
type = types.nullOr types.lines;
|
|
||||||
default = null;
|
|
||||||
description = "a default sieve script for filtering mail";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
extraAliasesFile = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
description = "file containing postfix aliases for receiving, loaded at runtime";
|
|
||||||
};
|
|
||||||
|
|
||||||
sieveDirectory = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "/var/sieve";
|
|
||||||
description = "path used for storing sieve scripts";
|
|
||||||
};
|
|
||||||
|
|
||||||
dkim = {
|
|
||||||
enable = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
};
|
|
||||||
directory = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "/var/dkim";
|
|
||||||
description = "path used for storing dkim signing keys, make sure to keep this backed up";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
vmail = {
|
|
||||||
user = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "vmail";
|
|
||||||
};
|
|
||||||
group = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "${cfg.vmail.user}";
|
|
||||||
};
|
|
||||||
userID = mkOption {
|
|
||||||
type = types.number;
|
|
||||||
default = 5000;
|
|
||||||
};
|
|
||||||
groupID = mkOption {
|
|
||||||
type = types.number;
|
|
||||||
default = cfg.vmail.userID;
|
|
||||||
};
|
|
||||||
directory = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "/home/${cfg.vmail.user}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit (lib.modules) mkIf;
|
|
||||||
inherit (lib.attrsets) mapAttrsToList;
|
|
||||||
inherit (lib.strings) concatStringsSep optionalString;
|
|
||||||
|
|
||||||
mailConfig = config.services.mailserver;
|
|
||||||
vmailConfig = mailConfig.vmail;
|
|
||||||
|
|
||||||
postfixCfg = config.services.postfix;
|
|
||||||
|
|
||||||
dovecotRuntimeDir = "/run/dovecot2";
|
|
||||||
passwdFile = "${dovecotRuntimeDir}/passwd";
|
|
||||||
|
|
||||||
genPasswdScript = pkgs.writeScript "generate-password-file" ''
|
|
||||||
#!${pkgs.stdenv.shell}
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
${concatStringsSep "\n" (map (userPasswdFile: ''
|
|
||||||
if [ ! -f "${userPasswdFile}" ]; then
|
|
||||||
echo "Expected password hash file ${userPasswdFile} does not exist!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
'') (mapAttrsToList (_email: config: config.passwordHashFile) mailConfig.accounts))}
|
|
||||||
|
|
||||||
cat <<EOF > ${passwdFile}
|
|
||||||
${concatStringsSep "\n" (mapAttrsToList (
|
|
||||||
email: config: "${email}:$(head -n 1 ${config.passwordHashFile})"
|
|
||||||
)
|
|
||||||
mailConfig.accounts)}
|
|
||||||
EOF
|
|
||||||
'';
|
|
||||||
in {
|
|
||||||
config = mkIf mailConfig.enable {
|
|
||||||
services.dovecot2 = {
|
|
||||||
enable = true;
|
|
||||||
enableImap = true;
|
|
||||||
enableLmtp = true;
|
|
||||||
enableQuota = true;
|
|
||||||
enablePop3 = false;
|
|
||||||
enablePAM = false; # Not using PAM for Auth
|
|
||||||
|
|
||||||
mailUser = vmailConfig.user;
|
|
||||||
mailGroup = vmailConfig.group;
|
|
||||||
mailLocation = "maildir:${vmailConfig.directory}/%d/%n";
|
|
||||||
|
|
||||||
sslServerCert = mailConfig.sslConfig.cert;
|
|
||||||
sslServerKey = mailConfig.sslConfig.key;
|
|
||||||
|
|
||||||
# For Sieve
|
|
||||||
modules = with pkgs; [
|
|
||||||
dovecot_pigeonhole
|
|
||||||
];
|
|
||||||
protocols = ["sieve"];
|
|
||||||
|
|
||||||
mailboxes = {
|
|
||||||
Trash = {
|
|
||||||
auto = "no";
|
|
||||||
specialUse = "Trash";
|
|
||||||
};
|
|
||||||
Junk = {
|
|
||||||
auto = "subscribe";
|
|
||||||
specialUse = "Junk";
|
|
||||||
};
|
|
||||||
Drafts = {
|
|
||||||
auto = "subscribe";
|
|
||||||
specialUse = "Drafts";
|
|
||||||
};
|
|
||||||
Sent = {
|
|
||||||
auto = "subscribe";
|
|
||||||
specialUse = "Sent";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
extraConfig = ''
|
|
||||||
${optionalString mailConfig.debugMode ''
|
|
||||||
mail_debug = yes
|
|
||||||
auth_debug = yes
|
|
||||||
verbose_ssl = yes
|
|
||||||
''}
|
|
||||||
|
|
||||||
service imap-login {
|
|
||||||
inet_listener imap {
|
|
||||||
port = 143
|
|
||||||
}
|
|
||||||
inet_listener imaps {
|
|
||||||
port = 993
|
|
||||||
ssl = yes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol imap {
|
|
||||||
mail_max_userip_connections = 100
|
|
||||||
mail_plugins = $mail_plugins imap_sieve
|
|
||||||
}
|
|
||||||
|
|
||||||
ssl = required
|
|
||||||
ssl_min_protocol = TLSv1.2
|
|
||||||
ssl_prefer_server_ciphers = yes
|
|
||||||
|
|
||||||
service lmtp {
|
|
||||||
unix_listener dovecot-lmtp {
|
|
||||||
group = ${postfixCfg.group}
|
|
||||||
mode = 0600
|
|
||||||
user = ${postfixCfg.user}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recipient_delimiter = "+"
|
|
||||||
lmtp_save_to_detail_mailbox = "no"
|
|
||||||
|
|
||||||
protocol lmtp {
|
|
||||||
mail_plugins = $mail_plugins sieve
|
|
||||||
}
|
|
||||||
|
|
||||||
mail_access_groups = "${vmailConfig.group}"
|
|
||||||
|
|
||||||
userdb {
|
|
||||||
driver = static
|
|
||||||
args = uid=${toString vmailConfig.userID} gid=${toString vmailConfig.groupID}
|
|
||||||
}
|
|
||||||
|
|
||||||
passdb {
|
|
||||||
driver = passwd-file
|
|
||||||
args = ${passwdFile}
|
|
||||||
}
|
|
||||||
|
|
||||||
service auth {
|
|
||||||
unix_listener auth {
|
|
||||||
mode = 0660
|
|
||||||
user = ${postfixCfg.user}
|
|
||||||
group = ${postfixCfg.group}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auth_mechanisms = plain login
|
|
||||||
|
|
||||||
namespace inbox {
|
|
||||||
separator = "."
|
|
||||||
inbox = yes
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin {
|
|
||||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
|
||||||
sieve = file:${mailConfig.sieveDirectory}/%u/scripts;active=${mailConfig.sieveDirectory}/%u/active.sieve
|
|
||||||
sieve_default = file:${mailConfig.sieveDirectory}/%u/default.sieve
|
|
||||||
sieve_default_name = default
|
|
||||||
sieve_global_extensions = +vnd.dovecot.environment
|
|
||||||
}
|
|
||||||
lda_mailbox_autosubscribe = yes
|
|
||||||
lda_mailbox_autocreate = yes
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd = {
|
|
||||||
tmpfiles.rules = [
|
|
||||||
"f ${passwdFile} 600 dovecot2 dovecot2"
|
|
||||||
];
|
|
||||||
services = {
|
|
||||||
dovecot2.preStart = ''
|
|
||||||
${genPasswdScript}
|
|
||||||
'';
|
|
||||||
postfix.restartTriggers = [genPasswdScript];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
{
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit (lib.modules) mkIf;
|
|
||||||
|
|
||||||
mailConfig = config.services.mailserver;
|
|
||||||
in {
|
|
||||||
config = mkIf mailConfig.enable {
|
|
||||||
networking.firewall = {
|
|
||||||
allowedTCPPorts = [
|
|
||||||
# SMTP
|
|
||||||
25
|
|
||||||
# Submission
|
|
||||||
587
|
|
||||||
# Submission w/ SSL
|
|
||||||
465
|
|
||||||
# IMAP
|
|
||||||
143
|
|
||||||
# IMAP w/ SSL
|
|
||||||
993
|
|
||||||
# Sieve
|
|
||||||
4190
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit (lib.modules) mkIf mkForce;
|
|
||||||
inherit (lib.trivial) flip;
|
|
||||||
inherit (lib.strings) optionalString escapeShellArgs;
|
|
||||||
inherit (builtins) toFile concatStringsSep;
|
|
||||||
|
|
||||||
mailConfig = config.services.mailserver;
|
|
||||||
|
|
||||||
opendkimConfig = config.services.opendkim;
|
|
||||||
opendkimArgs = ["-f" "-l" "-x" opendkimConfig.configFile];
|
|
||||||
dkimUser = opendkimConfig.user;
|
|
||||||
dkimGroup = opendkimConfig.group;
|
|
||||||
|
|
||||||
keyDir = mailConfig.dkim.directory;
|
|
||||||
selector = "mail";
|
|
||||||
|
|
||||||
inherit (mailConfig) domains;
|
|
||||||
|
|
||||||
createDomainDkimCert = dom: let
|
|
||||||
dkimKey = "${keyDir}/${dom}.${selector}.key";
|
|
||||||
dkimDNSFile = "${keyDir}/${dom}.${selector}.txt";
|
|
||||||
in ''
|
|
||||||
if [ ! -f "${dkimKey}" ]
|
|
||||||
then
|
|
||||||
${pkgs.opendkim}/bin/opendkim-genkey -s "${selector}" \
|
|
||||||
-d "${dom}" \
|
|
||||||
--bits="1024" \
|
|
||||||
--directory="${keyDir}"
|
|
||||||
mv "${keyDir}/${selector}.private" "${dkimKey}"
|
|
||||||
mv "${keyDir}/${selector}.txt" "${dkimDNSFile}"
|
|
||||||
echo "Generated key for domain ${dom} selector ${selector}"
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
createAllCerts =
|
|
||||||
concatStringsSep "\n" (map createDomainDkimCert mailConfig.domains);
|
|
||||||
|
|
||||||
keyTable = toFile "opendkim-KeyTable" (concatStringsSep "\n"
|
|
||||||
(flip map domains
|
|
||||||
(dom: "${dom} ${dom}:${selector}:${keyDir}/${dom}.${selector}.key")));
|
|
||||||
|
|
||||||
signingTable =
|
|
||||||
toFile "opendkim-SigningTable"
|
|
||||||
(concatStringsSep "\n" (flip map domains (dom: "${dom} ${dom}")));
|
|
||||||
in {
|
|
||||||
config = mkIf (mailConfig.enable && mailConfig.dkim.enable) {
|
|
||||||
services.opendkim = {
|
|
||||||
enable = true;
|
|
||||||
inherit selector;
|
|
||||||
keyPath = keyDir;
|
|
||||||
domains = "csl:${concatStringsSep "," domains}";
|
|
||||||
configFile = toFile "opendkim.conf" (''
|
|
||||||
Canonicalization relaxed/relaxed
|
|
||||||
UMask 0002
|
|
||||||
Socket ${opendkimConfig.socket}
|
|
||||||
KeyTable file:${keyTable}
|
|
||||||
SigningTable file:${signingTable}
|
|
||||||
''
|
|
||||||
+ (optionalString mailConfig.debugMode ''
|
|
||||||
Syslog yes
|
|
||||||
SyslogSuccess yes
|
|
||||||
LogWhy yes
|
|
||||||
''));
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.tmpfiles.rules = ["d '${keyDir}' - ${dkimUser} ${dkimGroup} - -"];
|
|
||||||
|
|
||||||
users.users.postfix.extraGroups = ["${dkimGroup}"];
|
|
||||||
|
|
||||||
systemd.services.opendkim = {
|
|
||||||
preStart = mkForce createAllCerts;
|
|
||||||
serviceConfig = {
|
|
||||||
ExecStart =
|
|
||||||
mkForce
|
|
||||||
"${pkgs.opendkim}/bin/opendkim ${escapeShellArgs opendkimArgs}";
|
|
||||||
PermissionsStartOnly = mkForce false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,203 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit (lib.modules) mkIf;
|
|
||||||
inherit (lib.strings) concatStringsSep;
|
|
||||||
inherit (lib.lists) flatten optional;
|
|
||||||
inherit (lib.attrsets) mapAttrsToList;
|
|
||||||
inherit (builtins) toFile;
|
|
||||||
|
|
||||||
mailConfig = config.services.mailserver;
|
|
||||||
|
|
||||||
tls_allowed = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
|
|
||||||
tls_disallow = "MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL";
|
|
||||||
|
|
||||||
sendingReceivingAliases = concatStringsSep "\n" (flatten [
|
|
||||||
(mapAttrsToList (email: config:
|
|
||||||
map (
|
|
||||||
alias: "${alias} ${email} "
|
|
||||||
)
|
|
||||||
config.aliases)
|
|
||||||
mailConfig.accounts)
|
|
||||||
(mapAttrsToList (email: _config: [
|
|
||||||
# i dont know if this is actually needed
|
|
||||||
"${email} ${email}"
|
|
||||||
])
|
|
||||||
mailConfig.accounts)
|
|
||||||
]);
|
|
||||||
|
|
||||||
sendingReceivingAliasesMappedName = "sending_receiving_aliases";
|
|
||||||
sendingReceivingAliasesFile = toFile "${sendingReceivingAliasesMappedName}" sendingReceivingAliases;
|
|
||||||
|
|
||||||
extraAliasesCombinedFilePath = "/run/postfix_sending_receiving_aliases";
|
|
||||||
in {
|
|
||||||
config = mkIf mailConfig.enable {
|
|
||||||
systemd.tmpfiles.rules = mkIf (mailConfig.extraAliasesFile != null) [
|
|
||||||
"f ${extraAliasesCombinedFilePath} 660 root root"
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.services.postfix-extra-aliases-setup = mkIf (mailConfig.extraAliasesFile != null) {
|
|
||||||
wantedBy = ["multi-user.target"];
|
|
||||||
partOf = ["postfix.service"];
|
|
||||||
before = ["postfix-setup.service"];
|
|
||||||
script = ''
|
|
||||||
cat "${sendingReceivingAliasesFile}" > ${extraAliasesCombinedFilePath}
|
|
||||||
echo >> ${extraAliasesCombinedFilePath}
|
|
||||||
cat "${mailConfig.extraAliasesFile}" >> ${extraAliasesCombinedFilePath}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
services.postfix = let
|
|
||||||
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
|
||||||
|
|
||||||
sendingReceivingAliasesMappedFile = mappedFile sendingReceivingAliasesMappedName;
|
|
||||||
|
|
||||||
submissionOptions = {
|
|
||||||
smtpd_tls_security_level = "encrypt";
|
|
||||||
smtpd_sasl_auth_enable = "yes";
|
|
||||||
smtpd_sasl_type = "dovecot";
|
|
||||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
|
||||||
smtpd_sasl_security_options = "noanonymous";
|
|
||||||
smtpd_sasl_local_domain = "$myhostname";
|
|
||||||
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
|
||||||
smtpd_sender_login_maps = sendingReceivingAliasesMappedFile;
|
|
||||||
smtpd_sender_restrictions = "reject_sender_login_mismatch";
|
|
||||||
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
|
||||||
cleanup_service_name = "submission-header-cleanup";
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
enable = true;
|
|
||||||
hostname = "${mailConfig.fqdn}";
|
|
||||||
networksStyle = "host";
|
|
||||||
|
|
||||||
mapFiles = {
|
|
||||||
"sending_receiving_aliases" =
|
|
||||||
if (mailConfig.extraAliasesFile == null)
|
|
||||||
then sendingReceivingAliasesFile
|
|
||||||
else "${extraAliasesCombinedFilePath}";
|
|
||||||
};
|
|
||||||
|
|
||||||
enableSubmission = true;
|
|
||||||
enableSubmissions = true;
|
|
||||||
sslCert = mailConfig.sslConfig.cert;
|
|
||||||
sslKey = mailConfig.sslConfig.key;
|
|
||||||
|
|
||||||
config = {
|
|
||||||
# Extra Config
|
|
||||||
mydestination = "";
|
|
||||||
recipient_delimiter = "+";
|
|
||||||
smtpd_banner = "${mailConfig.fqdn} ESMTP NO UCE";
|
|
||||||
disable_vrfy_command = true;
|
|
||||||
message_size_limit = "20971520";
|
|
||||||
|
|
||||||
virtual_uid_maps = "static:${toString mailConfig.vmail.userID}";
|
|
||||||
virtual_gid_maps = "static:${toString mailConfig.vmail.groupID}";
|
|
||||||
virtual_mailbox_base = "${mailConfig.vmail.directory}";
|
|
||||||
virtual_mailbox_domains = toFile "vhosts" (concatStringsSep "\n" mailConfig.domains);
|
|
||||||
virtual_mailbox_maps = sendingReceivingAliasesMappedFile;
|
|
||||||
virtual_alias_maps = sendingReceivingAliasesMappedFile;
|
|
||||||
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
|
||||||
lmtp_destination_recipient_limit = "1";
|
|
||||||
|
|
||||||
smtpd_sasl_type = "dovecot";
|
|
||||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
|
||||||
smtpd_sasl_auth_enable = true;
|
|
||||||
smtpd_relay_restrictions = [
|
|
||||||
"permit_mynetworks"
|
|
||||||
"permit_sasl_authenticated"
|
|
||||||
"reject_unauth_destination"
|
|
||||||
];
|
|
||||||
|
|
||||||
policy-spf_time_limit = mkIf mailConfig.spf.enable "3600s";
|
|
||||||
|
|
||||||
smtpd_recipient_restrictions = flatten [
|
|
||||||
(optional mailConfig.spf.enable "check_policy_service unix:private/policy-spf")
|
|
||||||
];
|
|
||||||
|
|
||||||
smtpd_tls_security_level = "may";
|
|
||||||
smtpd_tls_eecdh_grade = "ultra";
|
|
||||||
|
|
||||||
# Only Alow Modern TLS
|
|
||||||
smtp_tls_protocols = tls_allowed;
|
|
||||||
smtpd_tls_protocols = tls_allowed;
|
|
||||||
smtp_tls_mandatory_protocols = tls_allowed;
|
|
||||||
smtpd_tls_mandatory_protocols = tls_allowed;
|
|
||||||
|
|
||||||
# Disable Old Ciphers
|
|
||||||
smtp_tls_exclude_ciphers = tls_disallow;
|
|
||||||
smtpd_tls_exclude_ciphers = tls_disallow;
|
|
||||||
smtp_tls_mandatory_exclude_ciphers = tls_disallow;
|
|
||||||
smtpd_tls_mandatory_exclude_ciphers = tls_disallow;
|
|
||||||
|
|
||||||
smtp_tls_ciphers = "high";
|
|
||||||
smtpd_tls_ciphers = "high";
|
|
||||||
smtp_tls_mandatory_ciphers = "high";
|
|
||||||
smtpd_tls_mandatory_ciphers = "high";
|
|
||||||
|
|
||||||
tls_preempt_cipherlist = true;
|
|
||||||
|
|
||||||
smtpd_tls_auth_only = true;
|
|
||||||
smtpd_tls_loglevel = "1";
|
|
||||||
|
|
||||||
tls_random_source = "dev:/dev/urandom";
|
|
||||||
|
|
||||||
milter_default_action = "quarantine";
|
|
||||||
|
|
||||||
smtpd_milters = flatten [
|
|
||||||
(optional mailConfig.dkim.enable "unix:/run/opendkim/opendkim.sock")
|
|
||||||
];
|
|
||||||
|
|
||||||
non_smtpd_milters = flatten [
|
|
||||||
(optional mailConfig.dkim.enable "unix:/run/opendkim/opendkim.sock")
|
|
||||||
];
|
|
||||||
|
|
||||||
milter_protocol = "6";
|
|
||||||
milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
|
|
||||||
};
|
|
||||||
|
|
||||||
inherit submissionOptions;
|
|
||||||
submissionsOptions = submissionOptions;
|
|
||||||
|
|
||||||
masterConfig = {
|
|
||||||
"lmtp" = {
|
|
||||||
# Add headers when delivering, see http://www.postfix.org/smtp.8.html
|
|
||||||
# D => Delivered-To, O => X-Original-To, R => Return-Path
|
|
||||||
args = ["flags=O"];
|
|
||||||
};
|
|
||||||
"policy-spf" = mkIf mailConfig.spf.enable {
|
|
||||||
type = "unix";
|
|
||||||
privileged = true;
|
|
||||||
chroot = false;
|
|
||||||
command = "spawn";
|
|
||||||
args = let
|
|
||||||
policydConfig = toFile "policyd-spf.conf" mailConfig.spf.policydConfig;
|
|
||||||
in [
|
|
||||||
"user=nobody"
|
|
||||||
"argv=${pkgs.pypolicyd-spf}/bin/policyd-spf"
|
|
||||||
"${policydConfig}"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
"submission-header-cleanup" = {
|
|
||||||
type = "unix";
|
|
||||||
private = false;
|
|
||||||
chroot = false;
|
|
||||||
maxproc = 0;
|
|
||||||
command = "cleanup";
|
|
||||||
args = let
|
|
||||||
submissionHeaderCleanupRules = toFile "submission_header_cleanup_rules" ''
|
|
||||||
/^Received:/ IGNORE
|
|
||||||
/^X-Originating-IP:/ IGNORE
|
|
||||||
/^X-Mailer:/ IGNORE
|
|
||||||
/^User-Agent:/ IGNORE
|
|
||||||
/^X-Enigmail:/ IGNORE
|
|
||||||
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${mailConfig.fqdn}>
|
|
||||||
'';
|
|
||||||
in ["-o" "header_checks=pcre:${submissionHeaderCleanupRules}"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit (lib.modules) mkIf;
|
|
||||||
|
|
||||||
mailConfig = config.services.mailserver;
|
|
||||||
acmeRoot = "/var/lib/acme/acme-challenge";
|
|
||||||
in {
|
|
||||||
config = mkIf (mailConfig.enable && mailConfig.sslConfig.useACME) {
|
|
||||||
services.nginx = {
|
|
||||||
enable = true;
|
|
||||||
virtualHosts."${mailConfig.fqdn}" = {
|
|
||||||
serverName = mailConfig.fqdn;
|
|
||||||
serverAliases = mailConfig.domains;
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
inherit acmeRoot;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
security.acme.certs."${mailConfig.fqdn}" = {
|
|
||||||
reloadServices = ["postfix.service" "dovecot2.service"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit (lib.modules) mkIf;
|
|
||||||
inherit (lib.strings) concatStringsSep;
|
|
||||||
inherit (lib.attrsets) mapAttrsToList;
|
|
||||||
|
|
||||||
mailConfig = config.services.mailserver;
|
|
||||||
|
|
||||||
inherit (mailConfig) vmail;
|
|
||||||
vmailUser = vmail.user;
|
|
||||||
vmailGroup = vmail.group;
|
|
||||||
|
|
||||||
inherit (mailConfig) sieveDirectory;
|
|
||||||
|
|
||||||
scriptForUser = name: config:
|
|
||||||
if builtins.isString config.sieveScript
|
|
||||||
then ''
|
|
||||||
cat ${builtins.toFile "default.sieve" config.sieveScript} > "${sieveDirectory}/${name}/default.sieve"
|
|
||||||
chown "${vmailUser}:${vmailGroup}" "${sieveDirectory}/${name}/default.sieve"
|
|
||||||
''
|
|
||||||
else ''
|
|
||||||
if [ -f "${sieveDirectory}/${name}/default.sieve" ]; then
|
|
||||||
rm "${sieveDirectory}/${name}/default.sieve"
|
|
||||||
fi
|
|
||||||
if [ -f "${sieveDirectory}/${name}.svbin" ]; then
|
|
||||||
rm "${sieveDirectory}/${name}/default.svbin"
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
virtualMailUsersActivationScript = pkgs.writeScript "activate-virtual-mail-users" ''
|
|
||||||
#!${pkgs.stdenv.shell}
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
${concatStringsSep "\n" (mapAttrsToList (name: config: scriptForUser name config) mailConfig.accounts)}
|
|
||||||
'';
|
|
||||||
in {
|
|
||||||
config = mkIf mailConfig.enable {
|
|
||||||
users.users."${vmailUser}" = {
|
|
||||||
isSystemUser = true;
|
|
||||||
|
|
||||||
home = vmail.directory;
|
|
||||||
createHome = true;
|
|
||||||
|
|
||||||
uid = vmail.userID;
|
|
||||||
group = "${vmailGroup}";
|
|
||||||
};
|
|
||||||
|
|
||||||
users.groups."${vmailGroup}" = {
|
|
||||||
gid = vmail.groupID;
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.tmpfiles.rules =
|
|
||||||
[
|
|
||||||
"d '${sieveDirectory}' - ${vmailUser} ${vmailGroup} - -"
|
|
||||||
]
|
|
||||||
++ (map (
|
|
||||||
email: "d '${sieveDirectory}/${email}' 770 ${vmailUser} ${vmailGroup} - -"
|
|
||||||
) (builtins.attrNames mailConfig.accounts));
|
|
||||||
|
|
||||||
systemd.services.activate-virtual-mail-users = {
|
|
||||||
wantedBy = ["multi-user.target"];
|
|
||||||
before = ["dovecot2.service"];
|
|
||||||
serviceConfig.ExecStart = virtualMailUsersActivationScript;
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
{config, ...}: let
|
|
||||||
inherit (config.services.secrets) secrets;
|
|
||||||
in {
|
|
||||||
services.mailserver = {
|
|
||||||
enable = true;
|
|
||||||
fqdn = "mail.owo.monster";
|
|
||||||
domains = ["owo.monster"];
|
|
||||||
debugMode = true;
|
|
||||||
|
|
||||||
sslConfig = {
|
|
||||||
useACME = false;
|
|
||||||
cert = "/var/lib/acme/mail.owo.monster/fullchain.pem";
|
|
||||||
key = "/var/lib/acme/mail.owo.monster/key.pem";
|
|
||||||
};
|
|
||||||
|
|
||||||
spf.enable = false;
|
|
||||||
|
|
||||||
accounts = {
|
|
||||||
"chaos@owo.monster" = {
|
|
||||||
passwordHashFile = "${secrets.chaos_mail_passwd.path}";
|
|
||||||
aliases = [
|
|
||||||
"all@owo.monster"
|
|
||||||
"chaoticryptidz@owo.monster"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
"system@owo.monster" = {
|
|
||||||
passwordHashFile = "${secrets.system_mail_passwd.path}";
|
|
||||||
};
|
|
||||||
|
|
||||||
"gotosocial@owo.monster" = {
|
|
||||||
passwordHashFile = "${secrets.gotosocial_mail_passwd.path}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
extraAliasesFile = "${secrets.private_mail_aliases.path}";
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
self,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
backupSchedules = import "${self}/data/backupSchedules.nix";
|
|
||||||
inherit (config.services.secrets) secrets;
|
|
||||||
|
|
||||||
mailConfig = config.services.mailserver;
|
|
||||||
in {
|
|
||||||
services.restic.backups.mail = {
|
|
||||||
user = "root";
|
|
||||||
paths = [
|
|
||||||
mailConfig.vmail.directory
|
|
||||||
mailConfig.sieveDirectory
|
|
||||||
mailConfig.dkim.directory
|
|
||||||
];
|
|
||||||
|
|
||||||
repository = "s3:s3.eu-central-003.backblazeb2.com/Chaos-Restic/Mail";
|
|
||||||
passwordFile = "${secrets.restic_password.path}";
|
|
||||||
environmentFile = "${secrets.restic_env.path}";
|
|
||||||
createWrapper = true;
|
|
||||||
|
|
||||||
pruneOpts = ["--keep-last 60"];
|
|
||||||
timerConfig = backupSchedules.restic.medium;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
{pkgs, ...}: {
|
|
||||||
services.secrets = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
vaultLogin = {
|
|
||||||
enable = true;
|
|
||||||
loginUsername = "hetzner-arm-container-mail";
|
|
||||||
};
|
|
||||||
|
|
||||||
requiredVaultPaths = [
|
|
||||||
"api-keys/data/backblaze/Backblaze"
|
|
||||||
"api-keys/data/chaos_mail/system"
|
|
||||||
"api-keys/data/chaos_mail/gotosocial"
|
|
||||||
"passwords/data/mail"
|
|
||||||
"private-public-keys/data/restic/Mail"
|
|
||||||
"infra/data/private-mail-aliases"
|
|
||||||
];
|
|
||||||
|
|
||||||
packages = with pkgs; [
|
|
||||||
apacheHttpd
|
|
||||||
];
|
|
||||||
|
|
||||||
secrets = {
|
|
||||||
vault_password = {
|
|
||||||
manual = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
restic_password = {
|
|
||||||
fetchScript = ''
|
|
||||||
simple_get "/private-public-keys/restic/Mail" .password > "$secretFile"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
restic_env = {
|
|
||||||
fetchScript = ''
|
|
||||||
cat << EOF > "$secretFile"
|
|
||||||
AWS_ACCESS_KEY_ID=$(simple_get "/api-keys/backblaze/Backblaze" .keyID)
|
|
||||||
AWS_SECRET_ACCESS_KEY=$(simple_get "/api-keys/backblaze/Backblaze" .applicationKey)
|
|
||||||
EOF
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
private_mail_aliases = {
|
|
||||||
fetchScript = ''
|
|
||||||
kv_get "/infra/private-mail-aliases" | jq .data.data | jq -r 'to_entries|map("\(.key) \(.value.to)")[]' > "$secretFile"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
chaos_mail_passwd = {
|
|
||||||
user = "dovecot2";
|
|
||||||
group = "dovecot2";
|
|
||||||
fetchScript = ''
|
|
||||||
password=$(simple_get "/passwords/mail" .password)
|
|
||||||
htpasswd -nbB "" "$password" 2>/dev/null | cut -d: -f2 > "$secretFile"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
system_mail_passwd = {
|
|
||||||
user = "dovecot2";
|
|
||||||
group = "dovecot2";
|
|
||||||
fetchScript = ''
|
|
||||||
password=$(simple_get "/api-keys/chaos_mail/system" .password)
|
|
||||||
htpasswd -nbB "" "$password" 2>/dev/null | cut -d: -f2 > "$secretFile"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
gotosocial_mail_passwd = {
|
|
||||||
user = "dovecot2";
|
|
||||||
group = "dovecot2";
|
|
||||||
fetchScript = ''
|
|
||||||
password=$(simple_get "/api-keys/chaos_mail/gotosocial" .password)
|
|
||||||
htpasswd -nbB "" "$password" 2>/dev/null | cut -d: -f2 > "$secretFile"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -17,7 +17,6 @@ in {
|
||||||
|
|
||||||
(forEach [
|
(forEach [
|
||||||
"storage"
|
"storage"
|
||||||
"mail"
|
|
||||||
] (name: ./containers + "/${name}/${name}.nix"))
|
] (name: ./containers + "/${name}/${name}.nix"))
|
||||||
|
|
||||||
(with hosts.hetzner-arm.profiles; [
|
(with hosts.hetzner-arm.profiles; [
|
||||||
|
|
Loading…
Reference in a new issue