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 [
|
||||
"storage"
|
||||
"mail"
|
||||
] (name: ./containers + "/${name}/${name}.nix"))
|
||||
|
||||
(with hosts.hetzner-arm.profiles; [
|
||||
|
|
Loading…
Reference in a new issue