move mailserver to module
This commit is contained in:
parent
5a3628dac0
commit
ca7f4f5811
|
@ -4,20 +4,22 @@
|
|||
imports = with tree; [
|
||||
users.root
|
||||
|
||||
hosts.hetzner-vm.modules.mailserver
|
||||
|
||||
profiles.base
|
||||
profiles.sshd
|
||||
profiles.nginx
|
||||
profiles.nix-gc
|
||||
|
||||
hosts.hetzner-vm.services.restic
|
||||
hosts.hetzner-vm.services.invidious
|
||||
hosts.hetzner-vm.services.quassel
|
||||
hosts.hetzner-vm.services.mpd
|
||||
hosts.hetzner-vm.services.mail
|
||||
hosts.hetzner-vm.services.gitlab-static-sites
|
||||
hosts.hetzner-vm.services.lappy-dev
|
||||
hosts.hetzner-vm.services.misskey
|
||||
hosts.hetzner-vm.services.wireguard
|
||||
hosts.hetzner-vm.profiles.restic
|
||||
hosts.hetzner-vm.profiles.invidious
|
||||
hosts.hetzner-vm.profiles.quassel
|
||||
hosts.hetzner-vm.profiles.mpd
|
||||
hosts.hetzner-vm.profiles.mailserver
|
||||
hosts.hetzner-vm.profiles.gitlab-static-sites
|
||||
hosts.hetzner-vm.profiles.lappy-dev
|
||||
hosts.hetzner-vm.profiles.misskey
|
||||
hosts.hetzner-vm.profiles.wireguard
|
||||
|
||||
./networking.nix
|
||||
./hardware.nix
|
||||
|
|
85
hosts/hetzner-vm/modules/mailserver/default.nix
Normal file
85
hosts/hetzner-vm/modules/mailserver/default.nix
Normal file
|
@ -0,0 +1,85 @@
|
|||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let cfg = config.mailserver;
|
||||
in {
|
||||
options.mailserver = {
|
||||
enable = mkEnableOption "mailserver";
|
||||
|
||||
fqdn = mkOption { type = types.str; };
|
||||
|
||||
domains = mkOption { type = types.listOf types.str; };
|
||||
|
||||
ssl_config = mkOption {
|
||||
type = (types.submodule {
|
||||
options = {
|
||||
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";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = { };
|
||||
};
|
||||
debug_mode = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
accounts = mkOption {
|
||||
# where name = email for login
|
||||
type = types.attrsOf (types.submodule ({ config, name, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
passwordFile = mkOption { type = types.str; };
|
||||
aliases = mkOption { type = types.listOf types.str; };
|
||||
sieveScript = mkOption { type = types.nullOr types.lines; };
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
sieve_directory = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/sieve";
|
||||
};
|
||||
dkim_directory = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/dkim";
|
||||
};
|
||||
|
||||
policyd_config = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
};
|
||||
|
||||
vmail_config = mkOption {
|
||||
type = (types.submodule {
|
||||
options = {
|
||||
user_group_name = mkOption {
|
||||
type = types.str;
|
||||
default = "vmail";
|
||||
};
|
||||
user_group_id = mkOption {
|
||||
type = types.number;
|
||||
default = 5000;
|
||||
};
|
||||
directory = mkOption {
|
||||
type = types.str;
|
||||
default = "/home/${cfg.vmail_config.user_group_name}";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
}
|
217
hosts/hetzner-vm/modules/mailserver/dovecot.nix
Normal file
217
hosts/hetzner-vm/modules/mailserver/dovecot.nix
Normal file
|
@ -0,0 +1,217 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
mail_config = config.mailserver;
|
||||
passwdDir = "/run/dovecot2";
|
||||
passwdFile = "${passwdDir}/passwd";
|
||||
|
||||
bool2int = x: if x then "1" else "0";
|
||||
|
||||
# maildir in format "/${domain}/${user}"
|
||||
dovecotMaildir = "maildir:${mail_config.vmail_config.directory}/%d/%n";
|
||||
|
||||
postfixCfg = config.services.postfix;
|
||||
dovecot2Cfg = config.services.dovecot2;
|
||||
|
||||
stateDir = "/var/lib/dovecot";
|
||||
|
||||
passwordFiles =
|
||||
lib.mapAttrs (name: value: value.passwordFile) mail_config.accounts;
|
||||
|
||||
genPasswdScript = pkgs.writeScript "generate-password-file" ''
|
||||
#!${pkgs.stdenv.shell}
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if (! test -d "${passwdDir}"); then
|
||||
mkdir "${passwdDir}"
|
||||
chmod 755 "${passwdDir}"
|
||||
fi
|
||||
|
||||
for f in ${
|
||||
builtins.toString
|
||||
(lib.mapAttrsToList (name: value: passwordFiles."${name}")
|
||||
mail_config.accounts)
|
||||
}; do
|
||||
if [ ! -f "$f" ]; then
|
||||
echo "Expected password hash file $f does not exist!"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
cat <<EOF > ${passwdFile}
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value:
|
||||
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}:${
|
||||
builtins.toString mail_config.vmail_config.user_group_id
|
||||
}:${
|
||||
builtins.toString mail_config.vmail_config.user_group_id
|
||||
}::${mail_config.vmail_config.directory}:/run/current-system/sw/bin/nologin:")
|
||||
mail_config.accounts)}
|
||||
EOF
|
||||
|
||||
chmod 600 ${passwdFile}
|
||||
'';
|
||||
|
||||
pipeBin = pkgs.stdenv.mkDerivation {
|
||||
name = "pipe_bin";
|
||||
src = ./pipe_bin;
|
||||
buildInputs = with pkgs; [ makeWrapper coreutils bash rspamd ];
|
||||
buildCommand = ''
|
||||
mkdir -p $out/pipe/bin
|
||||
cp $src/* $out/pipe/bin/
|
||||
chmod a+x $out/pipe/bin/*
|
||||
patchShebangs $out/pipe/bin
|
||||
|
||||
for file in $out/pipe/bin/*; do
|
||||
wrapProgram $file \
|
||||
--set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin"
|
||||
done
|
||||
'';
|
||||
};
|
||||
in {
|
||||
config = (lib.mkIf (mail_config.enable) {
|
||||
services.dovecot2 = {
|
||||
enable = true;
|
||||
enableImap = true;
|
||||
enablePop3 = false;
|
||||
enablePAM = false;
|
||||
enableQuota = true;
|
||||
mailGroup = mail_config.vmail_config.user_group_name;
|
||||
mailUser = mail_config.vmail_config.user_group_name;
|
||||
mailLocation = dovecotMaildir;
|
||||
sslServerCert = mail_config.ssl_config.cert;
|
||||
sslServerKey = mail_config.ssl_config.key;
|
||||
enableLmtp = true;
|
||||
modules = [ pkgs.dovecot_pigeonhole ];
|
||||
protocols = [ "sieve" ];
|
||||
|
||||
sieveScripts = {
|
||||
after = builtins.toFile "spam.sieve" ''
|
||||
require "fileinto";
|
||||
|
||||
if header :is "X-Spam" "Yes" {
|
||||
fileinto "Junk";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
mailboxes = {
|
||||
Trash = {
|
||||
auto = "no";
|
||||
specialUse = "Trash";
|
||||
};
|
||||
Junk = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Junk";
|
||||
};
|
||||
Drafts = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Drafts";
|
||||
};
|
||||
Sent = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Sent";
|
||||
};
|
||||
};
|
||||
|
||||
extraConfig = ''
|
||||
${lib.optionalString mail_config.debug_mode ''
|
||||
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
|
||||
}
|
||||
|
||||
mail_access_groups = "${mail_config.vmail_config.user_group_name}"
|
||||
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
|
||||
}
|
||||
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = ${passwdFile}
|
||||
}
|
||||
|
||||
userdb {
|
||||
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:${mail_config.sieve_directory}/%u/scripts;active=${mail_config.sieve_directory}/%u/active.sieve
|
||||
sieve_default = file:${mail_config.sieve_directory}/%u/default.sieve
|
||||
sieve_default_name = default
|
||||
|
||||
# From elsewhere to Spam folder
|
||||
imapsieve_mailbox1_name = Junk
|
||||
imapsieve_mailbox1_causes = COPY
|
||||
imapsieve_mailbox1_before = file:${./spam_sieve/report-spam.sieve}
|
||||
|
||||
# From Spam folder to elsewhere
|
||||
imapsieve_mailbox2_name = *
|
||||
imapsieve_mailbox2_from = Junk
|
||||
imapsieve_mailbox2_causes = COPY
|
||||
imapsieve_mailbox2_before = file:${./spam_sieve/report-ham.sieve}
|
||||
|
||||
sieve_pipe_bin_dir = ${pipeBin}/pipe/bin
|
||||
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
|
||||
}
|
||||
lda_mailbox_autosubscribe = yes
|
||||
lda_mailbox_autocreate = yes
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.dovecot2 = {
|
||||
preStart = ''
|
||||
${genPasswdScript}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.postfix.restartTriggers = [ genPasswdScript ];
|
||||
});
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
mail_config = (import ./config.nix { config = config; });
|
||||
mail_config = config.mailserver;
|
||||
dkimUser = config.services.opendkim.user;
|
||||
dkimGroup = config.services.opendkim.group;
|
||||
|
||||
|
@ -40,35 +40,38 @@ let
|
|||
args = [ "-f" "-l" ]
|
||||
++ lib.optionals (dkim.configFile != null) [ "-x" dkim.configFile ];
|
||||
in {
|
||||
services.opendkim = {
|
||||
enable = true;
|
||||
selector = selector;
|
||||
keyPath = keyDir;
|
||||
domains = "csl:${builtins.concatStringsSep "," domains}";
|
||||
configFile = pkgs.writeText "opendkim.conf" (''
|
||||
Canonicalization relaxed/relaxed
|
||||
UMask 0002
|
||||
Socket ${dkim.socket}
|
||||
KeyTable file:${keyTable}
|
||||
SigningTable file:${signingTable}
|
||||
'' + (lib.optionalString mail_config.debug_mode ''
|
||||
Syslog yes
|
||||
SyslogSuccess yes
|
||||
LogWhy yes
|
||||
''));
|
||||
};
|
||||
|
||||
users.users = lib.optionalAttrs (config.services.postfix.user == "postfix") {
|
||||
postfix.extraGroups = [ "${dkimGroup}" ];
|
||||
};
|
||||
|
||||
systemd.services.opendkim = {
|
||||
preStart = lib.mkForce createAllCerts;
|
||||
serviceConfig = {
|
||||
ExecStart =
|
||||
lib.mkForce "${pkgs.opendkim}/bin/opendkim ${lib.escapeShellArgs args}";
|
||||
PermissionsStartOnly = lib.mkForce false;
|
||||
config = (lib.mkIf (mail_config.enable) {
|
||||
services.opendkim = {
|
||||
enable = true;
|
||||
selector = selector;
|
||||
keyPath = keyDir;
|
||||
domains = "csl:${builtins.concatStringsSep "," domains}";
|
||||
configFile = pkgs.writeText "opendkim.conf" (''
|
||||
Canonicalization relaxed/relaxed
|
||||
UMask 0002
|
||||
Socket ${dkim.socket}
|
||||
KeyTable file:${keyTable}
|
||||
SigningTable file:${signingTable}
|
||||
'' + (lib.optionalString mail_config.debug_mode ''
|
||||
Syslog yes
|
||||
SyslogSuccess yes
|
||||
LogWhy yes
|
||||
''));
|
||||
};
|
||||
};
|
||||
systemd.tmpfiles.rules = [ "d '${keyDir}' - ${dkimUser} ${dkimGroup} - -" ];
|
||||
|
||||
users.users =
|
||||
lib.optionalAttrs (config.services.postfix.user == "postfix") {
|
||||
postfix.extraGroups = [ "${dkimGroup}" ];
|
||||
};
|
||||
|
||||
systemd.services.opendkim = {
|
||||
preStart = lib.mkForce createAllCerts;
|
||||
serviceConfig = {
|
||||
ExecStart = lib.mkForce
|
||||
"${pkgs.opendkim}/bin/opendkim ${lib.escapeShellArgs args}";
|
||||
PermissionsStartOnly = lib.mkForce false;
|
||||
};
|
||||
};
|
||||
systemd.tmpfiles.rules = [ "d '${keyDir}' - ${dkimUser} ${dkimGroup} - -" ];
|
||||
});
|
||||
}
|
195
hosts/hetzner-vm/modules/mailserver/postfix.nix
Normal file
195
hosts/hetzner-vm/modules/mailserver/postfix.nix
Normal file
|
@ -0,0 +1,195 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
mail_config = config.mailserver;
|
||||
submissionHeaderCleanupRules =
|
||||
pkgs.writeText "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@${mail_config.fqdn}>
|
||||
'');
|
||||
|
||||
inetSocket = addr: port: "inet:[${toString port}@${addr}]";
|
||||
unixSocket = sock: "unix:${sock}";
|
||||
|
||||
# Merge several lookup tables. A lookup table is a attribute set where
|
||||
# - the key is an address (user@example.com) or a domain (@example.com)
|
||||
# - the value is a list of addresses
|
||||
mergeLookupTables = tables: lib.zipAttrsWith (n: v: lib.flatten v) tables;
|
||||
|
||||
# valiases_postfix :: Map String [String]
|
||||
valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
||||
(name: value:
|
||||
let to = name;
|
||||
in map (from: { "${from}" = to; }) (value.aliases ++ lib.singleton name))
|
||||
mail_config.accounts));
|
||||
|
||||
# all_valiases_postfix :: Map String [String]
|
||||
all_valiases_postfix = mergeLookupTables [ valiases_postfix ];
|
||||
|
||||
# lookupTableToString :: Map String [String] -> String
|
||||
lookupTableToString = attrs:
|
||||
let valueToString = value: lib.concatStringsSep ", " value;
|
||||
in lib.concatStringsSep "\n"
|
||||
(lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs);
|
||||
|
||||
# valiases_file :: Path
|
||||
valiases_file = let
|
||||
content = lookupTableToString (mergeLookupTables [ all_valiases_postfix ]);
|
||||
in builtins.toFile "valias" content;
|
||||
|
||||
# vhosts_file :: Path
|
||||
vhosts_file =
|
||||
builtins.toFile "vhosts" (lib.concatStringsSep "\n" mail_config.domains);
|
||||
vaccounts_file =
|
||||
builtins.toFile "vaccounts" (lookupTableToString all_valiases_postfix);
|
||||
|
||||
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
||||
|
||||
policyd-spf = pkgs.writeText "policyd-spf.conf" mail_config.policyd_config;
|
||||
|
||||
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 = "hash:/etc/postfix/vaccounts";
|
||||
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";
|
||||
};
|
||||
|
||||
tls_allowed = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
|
||||
tls_disallow = "MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL";
|
||||
in {
|
||||
config = (lib.mkIf (mail_config.enable) {
|
||||
services.postfix = {
|
||||
enable = true;
|
||||
hostname = "${mail_config.fqdn}";
|
||||
networksStyle = "host";
|
||||
mapFiles."valias" = valiases_file;
|
||||
mapFiles."vaccounts" = vaccounts_file;
|
||||
sslCert = mail_config.ssl_config.cert;
|
||||
sslKey = mail_config.ssl_config.key;
|
||||
enableSubmission = true;
|
||||
enableSubmissions = true;
|
||||
virtual =
|
||||
lookupTableToString (mergeLookupTables [ all_valiases_postfix ]);
|
||||
|
||||
config = {
|
||||
# Extra Config
|
||||
mydestination = "";
|
||||
recipient_delimiter = "+";
|
||||
smtpd_banner = "${mail_config.fqdn} ESMTP NO UCE";
|
||||
disable_vrfy_command = true;
|
||||
message_size_limit = "20971520";
|
||||
|
||||
virtual_uid_maps =
|
||||
"static:${toString mail_config.vmail_config.user_group_id}";
|
||||
virtual_gid_maps =
|
||||
"static:${toString mail_config.vmail_config.user_group_id}";
|
||||
virtual_mailbox_base = "${mail_config.vmail_config.directory}";
|
||||
virtual_mailbox_domains = vhosts_file;
|
||||
virtual_mailbox_maps = mappedFile "valias";
|
||||
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 = "3600s";
|
||||
|
||||
smtpd_recipient_restrictions = [
|
||||
#"check_recipient_access ${mappedFile "denied_recipients"}"
|
||||
#"check_recipient_access ${mappedFile "reject_recipients"}"
|
||||
"check_policy_service unix:private/policy-spf"
|
||||
];
|
||||
|
||||
# TLS settings, inspired by https://github.com/jeaye/nix-files
|
||||
# Submission by mail clients is handled in submissionOptions
|
||||
smtpd_tls_security_level = "may";
|
||||
|
||||
# strong might suffice and is computationally less expensive
|
||||
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";
|
||||
|
||||
smtpd_milters = [
|
||||
"unix:/run/opendkim/opendkim.sock"
|
||||
"unix:/run/rspamd/rspamd-milter.sock"
|
||||
];
|
||||
non_smtpd_milters = [ "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}";
|
||||
|
||||
};
|
||||
|
||||
submissionOptions = 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" = {
|
||||
type = "unix";
|
||||
privileged = true;
|
||||
chroot = false;
|
||||
command = "spawn";
|
||||
args = [
|
||||
"user=nobody"
|
||||
"argv=${pkgs.pypolicyd-spf}/bin/policyd-spf"
|
||||
"${policyd-spf}"
|
||||
];
|
||||
};
|
||||
"submission-header-cleanup" = {
|
||||
type = "unix";
|
||||
private = false;
|
||||
chroot = false;
|
||||
maxproc = 0;
|
||||
command = "cleanup";
|
||||
args = [ "-o" "header_checks=pcre:${submissionHeaderCleanupRules}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
99
hosts/hetzner-vm/modules/mailserver/rspamd.nix
Normal file
99
hosts/hetzner-vm/modules/mailserver/rspamd.nix
Normal file
|
@ -0,0 +1,99 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
mail_config = config.mailserver;
|
||||
|
||||
ports = (import ../../ports.nix { });
|
||||
|
||||
postfixCfg = config.services.postfix;
|
||||
rspamdCfg = config.services.rspamd;
|
||||
rspamdSocket = "rspamd.service";
|
||||
in {
|
||||
config = (lib.mkIf (mail_config.enable) {
|
||||
|
||||
services.rspamd = {
|
||||
enable = true;
|
||||
debug = mail_config.debug_mode;
|
||||
locals = {
|
||||
"milter_headers.conf" = {
|
||||
text = ''
|
||||
extended_spam_headers = yes;
|
||||
'';
|
||||
};
|
||||
"redis.conf" = {
|
||||
text = ''
|
||||
servers = "127.0.0.1:${toString ports.rspamd-redis}";
|
||||
'';
|
||||
};
|
||||
"classifier-bayes.conf" = {
|
||||
text = ''
|
||||
cache {
|
||||
backend = "redis";
|
||||
}
|
||||
min_learns = 5;
|
||||
'';
|
||||
};
|
||||
"dkim_signing.conf" = {
|
||||
text = ''
|
||||
# opendkim does this
|
||||
enabled = false;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
overrides = {
|
||||
"milter_headers.conf" = {
|
||||
text = ''
|
||||
extended_spam_headers = true;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
workers.rspamd_proxy = {
|
||||
type = "rspamd_proxy";
|
||||
bindSockets = [{
|
||||
socket = "/run/rspamd/rspamd-milter.sock";
|
||||
mode = "0664";
|
||||
}];
|
||||
count = 1;
|
||||
extraConfig = ''
|
||||
milter = yes;
|
||||
timeout = 120s;
|
||||
|
||||
upstream "local" {
|
||||
default = yes;
|
||||
self_scan = yes;
|
||||
}
|
||||
'';
|
||||
};
|
||||
workers.controller = {
|
||||
type = "controller";
|
||||
count = 1;
|
||||
bindSockets = [{
|
||||
socket = "/run/rspamd/worker-controller.sock";
|
||||
mode = "0666";
|
||||
}];
|
||||
includes = [ ];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
services.redis.servers.rspamd = {
|
||||
enable = true;
|
||||
port = ports.rspamd-redis;
|
||||
};
|
||||
|
||||
systemd.services.rspamd = {
|
||||
requires = [ "redis-rspamd.service" ];
|
||||
after = [ "redis-rspamd.service" ];
|
||||
};
|
||||
|
||||
systemd.services.postfix = {
|
||||
after = [ rspamdSocket ];
|
||||
requires = [ rspamdSocket ];
|
||||
};
|
||||
|
||||
users.extraUsers.${postfixCfg.user}.extraGroups = [ rspamdCfg.group ];
|
||||
});
|
||||
}
|
||||
|
23
hosts/hetzner-vm/modules/mailserver/ssl.nix
Normal file
23
hosts/hetzner-vm/modules/mailserver/ssl.nix
Normal file
|
@ -0,0 +1,23 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
mail_config = config.mailserver;
|
||||
acmeRoot = "/var/lib/acme/acme-challenge";
|
||||
|
||||
in {
|
||||
config = (lib.mkIf (mail_config.enable && mail_config.ssl_config.useACME) {
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts."${mail_config.fqdn}" = {
|
||||
serverName = mail_config.fqdn;
|
||||
serverAliases = mail_config.domains;
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = acmeRoot;
|
||||
};
|
||||
};
|
||||
|
||||
security.acme.certs."${mail_config.fqdn}" = {
|
||||
reloadServices = [ "postfix.service" "dovecot2.service" ];
|
||||
};
|
||||
});
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
mail_config = (import ./config.nix { config = config; });
|
||||
mail_config = config.mailserver;
|
||||
|
||||
v = mail_config.vmail_config;
|
||||
sieve_directory = mail_config.sieve_directory;
|
||||
|
@ -43,19 +43,21 @@ let
|
|||
'';
|
||||
|
||||
in {
|
||||
users.users."${v.user_group_name}" = {
|
||||
name = "${v.user_group_name}";
|
||||
isSystemUser = true;
|
||||
uid = v.user_group_id;
|
||||
home = v.directory;
|
||||
createHome = true;
|
||||
group = "${v.user_group_name}";
|
||||
};
|
||||
users.groups."${v.user_group_name}" = { gid = v.user_group_id; };
|
||||
systemd.services.activate-virtual-mail-users = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "dovecot2.service" ];
|
||||
serviceConfig = { ExecStart = virtualMailUsersActivationScript; };
|
||||
enable = true;
|
||||
};
|
||||
config = (lib.mkIf (mail_config.enable) {
|
||||
users.users."${v.user_group_name}" = {
|
||||
name = "${v.user_group_name}";
|
||||
isSystemUser = true;
|
||||
uid = v.user_group_id;
|
||||
home = v.directory;
|
||||
createHome = true;
|
||||
group = "${v.user_group_name}";
|
||||
};
|
||||
users.groups."${v.user_group_name}" = { gid = v.user_group_id; };
|
||||
systemd.services.activate-virtual-mail-users = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "dovecot2.service" ];
|
||||
serviceConfig = { ExecStart = virtualMailUsersActivationScript; };
|
||||
enable = true;
|
||||
};
|
||||
});
|
||||
}
|
19
hosts/hetzner-vm/modules/mailserver/webmail.nix
Normal file
19
hosts/hetzner-vm/modules/mailserver/webmail.nix
Normal file
|
@ -0,0 +1,19 @@
|
|||
{ config, lib, ... }:
|
||||
let mail_config = config.mailserver;
|
||||
in {
|
||||
config = (lib.mkIf (mail_config.enable) {
|
||||
services.roundcube = {
|
||||
enable = true;
|
||||
hostName = "mail.owo.monster";
|
||||
extraConfig = ''
|
||||
$config['smtp_server'] = "tls://${mail_config.fqdn}";
|
||||
$config['smtp_user'] = "%u";
|
||||
$config['smtp_pass'] = "%p";
|
||||
$config['plugins'] = ["managesieve"];
|
||||
$config['managesieve_host'] = 'tls://${mail_config.fqdn}';
|
||||
$config['session_lifetime'] = 168;
|
||||
$config['product_name'] = 'Chaos Mail';
|
||||
'';
|
||||
};
|
||||
});
|
||||
}
|
28
hosts/hetzner-vm/profiles/mailserver.nix
Normal file
28
hosts/hetzner-vm/profiles/mailserver.nix
Normal file
|
@ -0,0 +1,28 @@
|
|||
{ config, ... }:
|
||||
let secrets = config.services.secrets.secrets;
|
||||
in {
|
||||
config.mailserver = {
|
||||
enable = true;
|
||||
fqdn = "mail.owo.monster";
|
||||
domains = [ "owo.monster" "kitteh.pw" ];
|
||||
|
||||
debug_mode = false;
|
||||
|
||||
accounts = {
|
||||
"chaoticryptidz@owo.monster" = {
|
||||
name = "chaoticryptidz@owo.monster";
|
||||
passwordFile = "${secrets.chaos_mail_passwd.path}";
|
||||
aliases = [
|
||||
"all@owo.monster"
|
||||
# for sending from
|
||||
"chaos@owo.monster"
|
||||
|
||||
# TODO: legacy - to be deprecated by 2023-01-01
|
||||
"kitteh@owo.monster"
|
||||
"kitteh@kitteh.pw"
|
||||
];
|
||||
sieveScript = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
{ lib, stdenv, fetchFromGitHub }:
|
||||
|
||||
let
|
|
@ -1,7 +1,7 @@
|
|||
{ lib, config, pkgs, ... }:
|
||||
let
|
||||
secrets = config.services.secrets.secrets;
|
||||
mail_config = (import ./mailserver/config.nix { config = config; });
|
||||
mail_config = config.mailserver;
|
||||
|
||||
backupPrepareCommand = "${
|
||||
(pkgs.writeShellScriptBin "backupPrepareCommand" ''
|
|
@ -1,12 +0,0 @@
|
|||
{ ... }: {
|
||||
imports = [
|
||||
./mailserver/postfix.nix
|
||||
./mailserver/vmail.nix
|
||||
./mailserver/ssl.nix
|
||||
./mailserver/dovecot.nix
|
||||
./mailserver/firewall.nix
|
||||
./mailserver/webmail.nix
|
||||
./mailserver/opendkim.nix
|
||||
./mailserver/rspamd.nix
|
||||
];
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
{ config }:
|
||||
let secrets = config.services.secrets.secrets;
|
||||
in rec {
|
||||
fqdn = "mail.owo.monster";
|
||||
domains = [
|
||||
"owo.monster"
|
||||
"kitteh.pw"
|
||||
# "mailchaos.net"
|
||||
];
|
||||
|
||||
debug_mode = false;
|
||||
|
||||
ssl_config = {
|
||||
cert = "/var/lib/acme/${fqdn}/fullchain.pem";
|
||||
key = "/var/lib/acme/${fqdn}/key.pem";
|
||||
};
|
||||
|
||||
# generate password files with:
|
||||
# nix run nixpkgs.apacheHttpd -c htpasswd -nbB "" "password" | cut -d: -f2
|
||||
|
||||
accounts = {
|
||||
"chaoticryptidz@owo.monster" = {
|
||||
name = "chaoticryptidz@owo.monster";
|
||||
passwordFile = "${secrets.chaos_mail_passwd.path}";
|
||||
aliases = [
|
||||
"all@owo.monster"
|
||||
# for sending from
|
||||
"chaos@owo.monster"
|
||||
|
||||
# TODO: legacy - to be deprecated by 2023-01-01
|
||||
"kitteh@owo.monster"
|
||||
"kitteh@kitteh.pw"
|
||||
];
|
||||
sieveScript = null;
|
||||
};
|
||||
};
|
||||
|
||||
sieve_directory = "/var/sieve";
|
||||
dkim_directory = "/var/dkim";
|
||||
|
||||
policyd_config = "";
|
||||
|
||||
vmail_config = {
|
||||
user_group_name = "vmail";
|
||||
user_group_id = 5000;
|
||||
directory = "/home/vmail";
|
||||
};
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
mail_config = (import ./config.nix { config = config; });
|
||||
passwdDir = "/run/dovecot2";
|
||||
passwdFile = "${passwdDir}/passwd";
|
||||
|
||||
bool2int = x: if x then "1" else "0";
|
||||
|
||||
# maildir in format "/${domain}/${user}"
|
||||
dovecotMaildir = "maildir:${mail_config.vmail_config.directory}/%d/%n";
|
||||
|
||||
postfixCfg = config.services.postfix;
|
||||
dovecot2Cfg = config.services.dovecot2;
|
||||
|
||||
stateDir = "/var/lib/dovecot";
|
||||
|
||||
passwordFiles =
|
||||
lib.mapAttrs (name: value: value.passwordFile) mail_config.accounts;
|
||||
|
||||
genPasswdScript = pkgs.writeScript "generate-password-file" ''
|
||||
#!${pkgs.stdenv.shell}
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if (! test -d "${passwdDir}"); then
|
||||
mkdir "${passwdDir}"
|
||||
chmod 755 "${passwdDir}"
|
||||
fi
|
||||
|
||||
for f in ${
|
||||
builtins.toString
|
||||
(lib.mapAttrsToList (name: value: passwordFiles."${name}")
|
||||
mail_config.accounts)
|
||||
}; do
|
||||
if [ ! -f "$f" ]; then
|
||||
echo "Expected password hash file $f does not exist!"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
cat <<EOF > ${passwdFile}
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value:
|
||||
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}:${
|
||||
builtins.toString mail_config.vmail_config.user_group_id
|
||||
}:${
|
||||
builtins.toString mail_config.vmail_config.user_group_id
|
||||
}::${mail_config.vmail_config.directory}:/run/current-system/sw/bin/nologin:")
|
||||
mail_config.accounts)}
|
||||
EOF
|
||||
|
||||
chmod 600 ${passwdFile}
|
||||
'';
|
||||
|
||||
pipeBin = pkgs.stdenv.mkDerivation {
|
||||
name = "pipe_bin";
|
||||
src = ./pipe_bin;
|
||||
buildInputs = with pkgs; [ makeWrapper coreutils bash rspamd ];
|
||||
buildCommand = ''
|
||||
mkdir -p $out/pipe/bin
|
||||
cp $src/* $out/pipe/bin/
|
||||
chmod a+x $out/pipe/bin/*
|
||||
patchShebangs $out/pipe/bin
|
||||
|
||||
for file in $out/pipe/bin/*; do
|
||||
wrapProgram $file \
|
||||
--set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin"
|
||||
done
|
||||
'';
|
||||
};
|
||||
in {
|
||||
services.dovecot2 = {
|
||||
enable = true;
|
||||
enableImap = true;
|
||||
enablePop3 = false;
|
||||
enablePAM = false;
|
||||
enableQuota = true;
|
||||
mailGroup = mail_config.vmail_config.user_group_name;
|
||||
mailUser = mail_config.vmail_config.user_group_name;
|
||||
mailLocation = dovecotMaildir;
|
||||
sslServerCert = mail_config.ssl_config.cert;
|
||||
sslServerKey = mail_config.ssl_config.key;
|
||||
enableLmtp = true;
|
||||
modules = [ pkgs.dovecot_pigeonhole ];
|
||||
protocols = [ "sieve" ];
|
||||
|
||||
sieveScripts = {
|
||||
after = builtins.toFile "spam.sieve" ''
|
||||
require "fileinto";
|
||||
|
||||
if header :is "X-Spam" "Yes" {
|
||||
fileinto "Junk";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
mailboxes = {
|
||||
Trash = {
|
||||
auto = "no";
|
||||
specialUse = "Trash";
|
||||
};
|
||||
Junk = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Junk";
|
||||
};
|
||||
Drafts = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Drafts";
|
||||
};
|
||||
Sent = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Sent";
|
||||
};
|
||||
};
|
||||
|
||||
extraConfig = ''
|
||||
${lib.optionalString mail_config.debug_mode ''
|
||||
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
|
||||
}
|
||||
|
||||
mail_access_groups = "${mail_config.vmail_config.user_group_name}"
|
||||
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
|
||||
}
|
||||
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = ${passwdFile}
|
||||
}
|
||||
|
||||
userdb {
|
||||
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:${mail_config.sieve_directory}/%u/scripts;active=${mail_config.sieve_directory}/%u/active.sieve
|
||||
sieve_default = file:${mail_config.sieve_directory}/%u/default.sieve
|
||||
sieve_default_name = default
|
||||
|
||||
# From elsewhere to Spam folder
|
||||
imapsieve_mailbox1_name = Junk
|
||||
imapsieve_mailbox1_causes = COPY
|
||||
imapsieve_mailbox1_before = file:${./spam_sieve/report-spam.sieve}
|
||||
|
||||
# From Spam folder to elsewhere
|
||||
imapsieve_mailbox2_name = *
|
||||
imapsieve_mailbox2_from = Junk
|
||||
imapsieve_mailbox2_causes = COPY
|
||||
imapsieve_mailbox2_before = file:${./spam_sieve/report-ham.sieve}
|
||||
|
||||
sieve_pipe_bin_dir = ${pipeBin}/pipe/bin
|
||||
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
|
||||
}
|
||||
lda_mailbox_autosubscribe = yes
|
||||
lda_mailbox_autocreate = yes
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.dovecot2 = {
|
||||
preStart = ''
|
||||
${genPasswdScript}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.postfix.restartTriggers = [ genPasswdScript ];
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
mail_config = (import ./config.nix { config = config; });
|
||||
submissionHeaderCleanupRules =
|
||||
pkgs.writeText "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@${mail_config.fqdn}>
|
||||
'');
|
||||
|
||||
inetSocket = addr: port: "inet:[${toString port}@${addr}]";
|
||||
unixSocket = sock: "unix:${sock}";
|
||||
|
||||
# Merge several lookup tables. A lookup table is a attribute set where
|
||||
# - the key is an address (user@example.com) or a domain (@example.com)
|
||||
# - the value is a list of addresses
|
||||
mergeLookupTables = tables: lib.zipAttrsWith (n: v: lib.flatten v) tables;
|
||||
|
||||
# valiases_postfix :: Map String [String]
|
||||
valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
||||
(name: value:
|
||||
let to = name;
|
||||
in map (from: { "${from}" = to; }) (value.aliases ++ lib.singleton name))
|
||||
mail_config.accounts));
|
||||
|
||||
# all_valiases_postfix :: Map String [String]
|
||||
all_valiases_postfix = mergeLookupTables [ valiases_postfix ];
|
||||
|
||||
# lookupTableToString :: Map String [String] -> String
|
||||
lookupTableToString = attrs:
|
||||
let valueToString = value: lib.concatStringsSep ", " value;
|
||||
in lib.concatStringsSep "\n"
|
||||
(lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs);
|
||||
|
||||
# valiases_file :: Path
|
||||
valiases_file = let
|
||||
content = lookupTableToString (mergeLookupTables [ all_valiases_postfix ]);
|
||||
in builtins.toFile "valias" content;
|
||||
|
||||
# vhosts_file :: Path
|
||||
vhosts_file =
|
||||
builtins.toFile "vhosts" (lib.concatStringsSep "\n" mail_config.domains);
|
||||
vaccounts_file =
|
||||
builtins.toFile "vaccounts" (lookupTableToString all_valiases_postfix);
|
||||
|
||||
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
||||
|
||||
policyd-spf = pkgs.writeText "policyd-spf.conf" mail_config.policyd_config;
|
||||
|
||||
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 = "hash:/etc/postfix/vaccounts";
|
||||
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";
|
||||
};
|
||||
|
||||
tls_allowed = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
|
||||
tls_disallow = "MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL";
|
||||
in {
|
||||
|
||||
services.postfix = {
|
||||
enable = true;
|
||||
hostname = "${mail_config.fqdn}";
|
||||
networksStyle = "host";
|
||||
mapFiles."valias" = valiases_file;
|
||||
mapFiles."vaccounts" = vaccounts_file;
|
||||
sslCert = mail_config.ssl_config.cert;
|
||||
sslKey = mail_config.ssl_config.key;
|
||||
enableSubmission = true;
|
||||
enableSubmissions = true;
|
||||
virtual = lookupTableToString (mergeLookupTables [ all_valiases_postfix ]);
|
||||
|
||||
config = {
|
||||
# Extra Config
|
||||
mydestination = "";
|
||||
recipient_delimiter = "+";
|
||||
smtpd_banner = "${mail_config.fqdn} ESMTP NO UCE";
|
||||
disable_vrfy_command = true;
|
||||
message_size_limit = "20971520";
|
||||
|
||||
virtual_uid_maps =
|
||||
"static:${toString mail_config.vmail_config.user_group_id}";
|
||||
virtual_gid_maps =
|
||||
"static:${toString mail_config.vmail_config.user_group_id}";
|
||||
virtual_mailbox_base = "${mail_config.vmail_config.directory}";
|
||||
virtual_mailbox_domains = vhosts_file;
|
||||
virtual_mailbox_maps = mappedFile "valias";
|
||||
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 = "3600s";
|
||||
|
||||
smtpd_recipient_restrictions = [
|
||||
#"check_recipient_access ${mappedFile "denied_recipients"}"
|
||||
#"check_recipient_access ${mappedFile "reject_recipients"}"
|
||||
"check_policy_service unix:private/policy-spf"
|
||||
];
|
||||
|
||||
# TLS settings, inspired by https://github.com/jeaye/nix-files
|
||||
# Submission by mail clients is handled in submissionOptions
|
||||
smtpd_tls_security_level = "may";
|
||||
|
||||
# strong might suffice and is computationally less expensive
|
||||
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";
|
||||
|
||||
smtpd_milters = [
|
||||
"unix:/run/opendkim/opendkim.sock"
|
||||
"unix:/run/rspamd/rspamd-milter.sock"
|
||||
];
|
||||
non_smtpd_milters = [ "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}";
|
||||
|
||||
};
|
||||
|
||||
submissionOptions = 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" = {
|
||||
type = "unix";
|
||||
privileged = true;
|
||||
chroot = false;
|
||||
command = "spawn";
|
||||
args = [
|
||||
"user=nobody"
|
||||
"argv=${pkgs.pypolicyd-spf}/bin/policyd-spf"
|
||||
"${policyd-spf}"
|
||||
];
|
||||
};
|
||||
"submission-header-cleanup" = {
|
||||
type = "unix";
|
||||
private = false;
|
||||
chroot = false;
|
||||
maxproc = 0;
|
||||
command = "cleanup";
|
||||
args = [ "-o" "header_checks=pcre:${submissionHeaderCleanupRules}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
mail_config = (import ./config.nix { config = config; });
|
||||
|
||||
ports = (import ../../ports.nix { });
|
||||
|
||||
postfixCfg = config.services.postfix;
|
||||
rspamdCfg = config.services.rspamd;
|
||||
rspamdSocket = "rspamd.service";
|
||||
in {
|
||||
|
||||
services.rspamd = {
|
||||
enable = true;
|
||||
debug = mail_config.debug_mode;
|
||||
locals = {
|
||||
"milter_headers.conf" = {
|
||||
text = ''
|
||||
extended_spam_headers = yes;
|
||||
'';
|
||||
};
|
||||
"redis.conf" = {
|
||||
text = ''
|
||||
servers = "127.0.0.1:${toString ports.rspamd-redis}";
|
||||
'';
|
||||
};
|
||||
"classifier-bayes.conf" = {
|
||||
text = ''
|
||||
cache {
|
||||
backend = "redis";
|
||||
}
|
||||
min_learns = 5;
|
||||
'';
|
||||
};
|
||||
"dkim_signing.conf" = {
|
||||
text = ''
|
||||
# opendkim does this
|
||||
enabled = false;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
overrides = {
|
||||
"milter_headers.conf" = {
|
||||
text = ''
|
||||
extended_spam_headers = true;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
workers.rspamd_proxy = {
|
||||
type = "rspamd_proxy";
|
||||
bindSockets = [{
|
||||
socket = "/run/rspamd/rspamd-milter.sock";
|
||||
mode = "0664";
|
||||
}];
|
||||
count = 1;
|
||||
extraConfig = ''
|
||||
milter = yes;
|
||||
timeout = 120s;
|
||||
|
||||
upstream "local" {
|
||||
default = yes;
|
||||
self_scan = yes;
|
||||
}
|
||||
'';
|
||||
};
|
||||
workers.controller = {
|
||||
type = "controller";
|
||||
count = 1;
|
||||
bindSockets = [{
|
||||
socket = "/run/rspamd/worker-controller.sock";
|
||||
mode = "0666";
|
||||
}];
|
||||
includes = [ ];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
services.redis.servers.rspamd = {
|
||||
enable = true;
|
||||
port = ports.rspamd-redis;
|
||||
};
|
||||
|
||||
systemd.services.rspamd = {
|
||||
requires = [ "redis-rspamd.service" ];
|
||||
after = [ "redis-rspamd.service" ];
|
||||
};
|
||||
|
||||
systemd.services.postfix = {
|
||||
after = [ rspamdSocket ];
|
||||
requires = [ rspamdSocket ];
|
||||
};
|
||||
|
||||
users.extraUsers.${postfixCfg.user}.extraGroups = [ rspamdCfg.group ];
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
{ config, pkgs, ... }:
|
||||
let
|
||||
mail_config = (import ./config.nix { config = config; });
|
||||
acmeRoot = "/var/lib/acme/acme-challenge";
|
||||
|
||||
in {
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts."${mail_config.fqdn}" = {
|
||||
serverName = mail_config.fqdn;
|
||||
serverAliases = mail_config.domains;
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = acmeRoot;
|
||||
};
|
||||
};
|
||||
|
||||
security.acme.certs."${mail_config.fqdn}" = {
|
||||
reloadServices = [ "postfix.service" "dovecot2.service" ];
|
||||
};
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{ config, ... }:
|
||||
let mail_config = (import ./config.nix { config = config; });
|
||||
in {
|
||||
services.roundcube = {
|
||||
enable = true;
|
||||
hostName = "mail.owo.monster";
|
||||
extraConfig = ''
|
||||
$config['smtp_server'] = "tls://${mail_config.fqdn}";
|
||||
$config['smtp_user'] = "%u";
|
||||
$config['smtp_pass'] = "%p";
|
||||
$config['plugins'] = ["managesieve"];
|
||||
$config['managesieve_host'] = 'tls://${mail_config.fqdn}';
|
||||
$config['session_lifetime'] = 168;
|
||||
$config['product_name'] = 'Chaos Mail';
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -24,8 +24,21 @@ in {
|
|||
systemd.tmpfiles.rules = [
|
||||
"d /caches - storage storage"
|
||||
"d /caches/main_webdav_serve - storage storage"
|
||||
|
||||
"d /root/.config - root root"
|
||||
"d /root/.config/rclone - root root"
|
||||
"L /root/.config/rclone/rclone.conf - - - - ${secrets.rclone_config.path}"
|
||||
|
||||
"d /home/storage/.config - storage storage"
|
||||
"d /home/storage/.config/rclone - storage storage"
|
||||
"L /home/storage/.config/rclone/rclone.conf - - - - ${secrets.rclone_config.path}"
|
||||
];
|
||||
|
||||
home-manager.users.root = {
|
||||
imports = with tree; [ home.base home.dev.small ];
|
||||
home.stateVersion = "22.05";
|
||||
};
|
||||
|
||||
users.groups.storage = { };
|
||||
users.users.storage = {
|
||||
isNormalUser = true;
|
||||
|
@ -46,10 +59,6 @@ in {
|
|||
VAULT_ADDR="https://vault.owo.monster" \
|
||||
vault login -no-print -method=userpass username=${vault_username} password=$(cat ${vault_password_file})
|
||||
/run/current-system/sw/bin/secrets-init
|
||||
|
||||
mkdir -p ${config_dir}
|
||||
rm ${config_file} || true
|
||||
ln -s ${secrets.rclone_config.path} ${config_file}
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -63,7 +72,7 @@ in {
|
|||
set -e
|
||||
umount /storage -fl || true
|
||||
sleep 2
|
||||
rclone --config /home/storage/.config/rclone/rclone.conf mount StorageBox: /storage --allow-non-empty
|
||||
rclone --config ${secrets.rclone_config.path} mount StorageBox: /storage --allow-non-empty
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -74,11 +83,6 @@ in {
|
|||
restic
|
||||
];
|
||||
|
||||
home-manager.users.root = {
|
||||
imports = with tree; [ home.base home.dev.small ];
|
||||
home.stateVersion = "22.05";
|
||||
};
|
||||
|
||||
networking.hostName = "storage";
|
||||
time.timeZone = "Europe/London";
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"extras/*".functor.enable = true;
|
||||
|
||||
"hosts/*/services".functor.enable = true;
|
||||
"hosts/hetzner-vm/modules/mailserver".functor.enable = true;
|
||||
"hosts/raspberry/services/music-friend".functor.enable = true;
|
||||
"hosts/*/home".functor.enable = true;
|
||||
"hosts/*/profiles".functor.enable = true;
|
||||
|
|
Loading…
Reference in a new issue