treewide: reformat with nixfmt-rfc-style
This commit is contained in:
parent
03433d472f
commit
1a7f3d718c
21 changed files with 2086 additions and 1680 deletions
|
@ -4,8 +4,8 @@ let
|
||||||
pkgs = import nixpkgs { };
|
pkgs = import nixpkgs { };
|
||||||
|
|
||||||
prs = builtins.fromJSON (builtins.readFile pulls);
|
prs = builtins.fromJSON (builtins.readFile pulls);
|
||||||
prJobsets = pkgs.lib.mapAttrs (num: info:
|
prJobsets = pkgs.lib.mapAttrs (num: info: {
|
||||||
{ enabled = 1;
|
enabled = 1;
|
||||||
hidden = false;
|
hidden = false;
|
||||||
description = "PR ${num}: ${info.title}";
|
description = "PR ${num}: ${info.title}";
|
||||||
checkinterval = 300;
|
checkinterval = 300;
|
||||||
|
@ -15,8 +15,7 @@ let
|
||||||
keepnr = 1;
|
keepnr = 1;
|
||||||
type = 1;
|
type = 1;
|
||||||
flake = "gitlab:simple-nixos-mailserver/nixos-mailserver/merge-requests/${info.iid}/head";
|
flake = "gitlab:simple-nixos-mailserver/nixos-mailserver/merge-requests/${info.iid}/head";
|
||||||
}
|
}) prs;
|
||||||
) prs;
|
|
||||||
mkFlakeJobset = branch: {
|
mkFlakeJobset = branch: {
|
||||||
description = "Build ${branch} branch of Simple NixOS MailServer";
|
description = "Build ${branch} branch of Simple NixOS MailServer";
|
||||||
checkinterval = 300;
|
checkinterval = 300;
|
||||||
|
@ -41,7 +40,8 @@ let
|
||||||
jobsets = desc;
|
jobsets = desc;
|
||||||
};
|
};
|
||||||
|
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
jobsets = pkgs.runCommand "spec-jobsets.json" { } ''
|
jobsets = pkgs.runCommand "spec-jobsets.json" { } ''
|
||||||
cat >$out <<EOF
|
cat >$out <<EOF
|
||||||
${builtins.toJSON desc}
|
${builtins.toJSON desc}
|
||||||
|
|
124
default.nix
124
default.nix
|
@ -14,7 +14,12 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, lib, pkgs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
|
@ -62,7 +67,10 @@ in
|
||||||
|
|
||||||
certificateDomains = mkOption {
|
certificateDomains = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
example = [ "imap.example.com" "pop3.example.com" ];
|
example = [
|
||||||
|
"imap.example.com"
|
||||||
|
"pop3.example.com"
|
||||||
|
];
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = ''
|
description = ''
|
||||||
({option}`mailserver.certificateScheme` == `acme-nginx`)
|
({option}`mailserver.certificateScheme` == `acme-nginx`)
|
||||||
|
@ -79,7 +87,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
loginAccounts = mkOption {
|
loginAccounts = mkOption {
|
||||||
type = types.attrsOf (types.submodule ({ name, ... }: {
|
type = types.attrsOf (
|
||||||
|
types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
options = {
|
options = {
|
||||||
name = mkOption {
|
name = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
|
@ -118,7 +129,10 @@ in
|
||||||
|
|
||||||
aliases = mkOption {
|
aliases = mkOption {
|
||||||
type = with types; listOf types.str;
|
type = with types; listOf types.str;
|
||||||
example = ["abuse@example.com" "postmaster@example.com"];
|
example = [
|
||||||
|
"abuse@example.com"
|
||||||
|
"postmaster@example.com"
|
||||||
|
];
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = ''
|
description = ''
|
||||||
A list of aliases of this login account.
|
A list of aliases of this login account.
|
||||||
|
@ -138,7 +152,10 @@ in
|
||||||
|
|
||||||
catchAll = mkOption {
|
catchAll = mkOption {
|
||||||
type = with types; listOf (enum cfg.domains);
|
type = with types; listOf (enum cfg.domains);
|
||||||
example = ["example.com" "example2.com"];
|
example = [
|
||||||
|
"example.com"
|
||||||
|
"example2.com"
|
||||||
|
];
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = ''
|
description = ''
|
||||||
For which domains should this account act as a catch all?
|
For which domains should this account act as a catch all?
|
||||||
|
@ -202,7 +219,9 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
config.name = mkDefault name;
|
config.name = mkDefault name;
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
example = {
|
example = {
|
||||||
user1 = {
|
user1 = {
|
||||||
hashedPassword = "$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/";
|
hashedPassword = "$6$evQJs5CFQyPAW09S$Cn99Y8.QjZ2IBnSu4qf1vBxDRWkaIZWOtmu1Ddsm3.H3CFpeVc0JU4llIq8HQXgeatvYhh5O33eWG3TSpjzu6/";
|
||||||
|
@ -284,7 +303,11 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
searchScope = mkOption {
|
searchScope = mkOption {
|
||||||
type = types.enum [ "sub" "base" "one" ];
|
type = types.enum [
|
||||||
|
"sub"
|
||||||
|
"base"
|
||||||
|
"one"
|
||||||
|
];
|
||||||
default = "sub";
|
default = "sub";
|
||||||
description = ''
|
description = ''
|
||||||
Search scope below which users accounts are looked for.
|
Search scope below which users accounts are looked for.
|
||||||
|
@ -419,14 +442,22 @@ in
|
||||||
autoIndexExclude = mkOption {
|
autoIndexExclude = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
example = [ "\\Trash" "SomeFolder" "Other/*" ];
|
example = [
|
||||||
|
"\\Trash"
|
||||||
|
"SomeFolder"
|
||||||
|
"Other/*"
|
||||||
|
];
|
||||||
description = ''
|
description = ''
|
||||||
Mailboxes to exclude from automatic indexing.
|
Mailboxes to exclude from automatic indexing.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
enforced = mkOption {
|
enforced = mkOption {
|
||||||
type = types.enum [ "yes" "no" "body" ];
|
type = types.enum [
|
||||||
|
"yes"
|
||||||
|
"no"
|
||||||
|
"body"
|
||||||
|
];
|
||||||
default = "no";
|
default = "no";
|
||||||
description = ''
|
description = ''
|
||||||
Fail searches when no index is available. If set to
|
Fail searches when no index is available. If set to
|
||||||
|
@ -439,7 +470,10 @@ in
|
||||||
languages = mkOption {
|
languages = mkOption {
|
||||||
type = types.nonEmptyListOf types.str;
|
type = types.nonEmptyListOf types.str;
|
||||||
default = [ "en" ];
|
default = [ "en" ];
|
||||||
example = [ "en" "de" ];
|
example = [
|
||||||
|
"en"
|
||||||
|
"de"
|
||||||
|
];
|
||||||
description = ''
|
description = ''
|
||||||
A list of languages that the full text search should detect.
|
A list of languages that the full text search should detect.
|
||||||
At least one language must be specified.
|
At least one language must be specified.
|
||||||
|
@ -488,7 +522,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
lmtpSaveToDetailMailbox = mkOption {
|
lmtpSaveToDetailMailbox = mkOption {
|
||||||
type = types.enum ["yes" "no"];
|
type = types.enum [
|
||||||
|
"yes"
|
||||||
|
"no"
|
||||||
|
];
|
||||||
default = "yes";
|
default = "yes";
|
||||||
description = ''
|
description = ''
|
||||||
If an email address is delimited by a "+", should it be filed into a
|
If an email address is delimited by a "+", should it be filed into a
|
||||||
|
@ -514,17 +551,23 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
extraVirtualAliases = mkOption {
|
extraVirtualAliases = mkOption {
|
||||||
type = let
|
type =
|
||||||
|
let
|
||||||
loginAccount = mkOptionType {
|
loginAccount = mkOptionType {
|
||||||
name = "Login Account";
|
name = "Login Account";
|
||||||
check = account: builtins.elem account (builtins.attrNames cfg.loginAccounts);
|
check = account: builtins.elem account (builtins.attrNames cfg.loginAccounts);
|
||||||
};
|
};
|
||||||
in with types; attrsOf (either loginAccount (nonEmptyListOf loginAccount));
|
in
|
||||||
|
with types;
|
||||||
|
attrsOf (either loginAccount (nonEmptyListOf loginAccount));
|
||||||
example = {
|
example = {
|
||||||
"info@example.com" = "user1@example.com";
|
"info@example.com" = "user1@example.com";
|
||||||
"postmaster@example.com" = "user1@example.com";
|
"postmaster@example.com" = "user1@example.com";
|
||||||
"abuse@example.com" = "user1@example.com";
|
"abuse@example.com" = "user1@example.com";
|
||||||
"multi@example.com" = [ "user1@example.com" "user2@example.com" ];
|
"multi@example.com" = [
|
||||||
|
"user1@example.com"
|
||||||
|
"user2@example.com"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
description = ''
|
description = ''
|
||||||
Virtual Aliases. A virtual alias `"info@example.com" = "user1@example.com"` means that
|
Virtual Aliases. A virtual alias `"info@example.com" = "user1@example.com"` means that
|
||||||
|
@ -559,7 +602,10 @@ in
|
||||||
|
|
||||||
rejectSender = mkOption {
|
rejectSender = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
example = [ "example.com" "spammer@example.net" ];
|
example = [
|
||||||
|
"example.com"
|
||||||
|
"spammer@example.net"
|
||||||
|
];
|
||||||
description = ''
|
description = ''
|
||||||
Reject emails from these addresses from unauthorized senders.
|
Reject emails from these addresses from unauthorized senders.
|
||||||
Use if a spammer is using the same domain or the same sender over and over.
|
Use if a spammer is using the same domain or the same sender over and over.
|
||||||
|
@ -569,7 +615,10 @@ in
|
||||||
|
|
||||||
rejectRecipients = mkOption {
|
rejectRecipients = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
example = [ "sales@example.com" "info@example.com" ];
|
example = [
|
||||||
|
"sales@example.com"
|
||||||
|
"info@example.com"
|
||||||
|
];
|
||||||
description = ''
|
description = ''
|
||||||
Reject emails addressed to these local addresses from unauthorized senders.
|
Reject emails addressed to these local addresses from unauthorized senders.
|
||||||
Use if a spammer has found email addresses in a catchall domain but you do
|
Use if a spammer has found email addresses in a catchall domain but you do
|
||||||
|
@ -673,12 +722,30 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
certificateScheme = let
|
certificateScheme =
|
||||||
schemes = [ "manual" "selfsigned" "acme-nginx" "acme" ];
|
let
|
||||||
translate = i: warn "Setting mailserver.certificateScheme by number is deprecated, please use names instead: 'mailserver.certificateScheme = ${builtins.toString i}' can be replaced by 'mailserver.certificateScheme = \"${(builtins.elemAt schemes (i - 1))}\"'."
|
schemes = [
|
||||||
|
"manual"
|
||||||
|
"selfsigned"
|
||||||
|
"acme-nginx"
|
||||||
|
"acme"
|
||||||
|
];
|
||||||
|
translate =
|
||||||
|
i:
|
||||||
|
warn
|
||||||
|
"Setting mailserver.certificateScheme by number is deprecated, please use names instead: 'mailserver.certificateScheme = ${builtins.toString i}' can be replaced by 'mailserver.certificateScheme = \"${
|
||||||
|
(builtins.elemAt schemes (i - 1))
|
||||||
|
}\"'."
|
||||||
(builtins.elemAt schemes (i - 1));
|
(builtins.elemAt schemes (i - 1));
|
||||||
in mkOption {
|
in
|
||||||
type = with types; coercedTo (enum [ 1 2 3 ]) translate (enum schemes);
|
mkOption {
|
||||||
|
type =
|
||||||
|
with types;
|
||||||
|
coercedTo (enum [
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
]) translate (enum schemes);
|
||||||
default = "selfsigned";
|
default = "selfsigned";
|
||||||
description = ''
|
description = ''
|
||||||
The scheme to use for managing TLS certificates:
|
The scheme to use for managing TLS certificates:
|
||||||
|
@ -851,7 +918,10 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
dkimKeyType = mkOption {
|
dkimKeyType = mkOption {
|
||||||
type = types.enum [ "rsa" "ed25519" ];
|
type = types.enum [
|
||||||
|
"rsa"
|
||||||
|
"ed25519"
|
||||||
|
];
|
||||||
default = "rsa";
|
default = "rsa";
|
||||||
description = ''
|
description = ''
|
||||||
The key type used for generating DKIM keys. ED25519 was introduced in RFC6376 (2018).
|
The key type used for generating DKIM keys. ED25519 was introduced in RFC6376 (2018).
|
||||||
|
@ -1150,7 +1220,15 @@ in
|
||||||
|
|
||||||
compression = {
|
compression = {
|
||||||
method = mkOption {
|
method = mkOption {
|
||||||
type = types.nullOr (types.enum ["none" "lz4" "zstd" "zlib" "lzma"]);
|
type = types.nullOr (
|
||||||
|
types.enum [
|
||||||
|
"none"
|
||||||
|
"lz4"
|
||||||
|
"zstd"
|
||||||
|
"zlib"
|
||||||
|
"lzma"
|
||||||
|
]
|
||||||
|
);
|
||||||
default = null;
|
default = null;
|
||||||
description = "Leaving this unset allows borg to choose. The default for borg 1.1.4 is lz4.";
|
description = "Leaving this unset allows borg to choose. The default for borg 1.1.4 is lz4.";
|
||||||
};
|
};
|
||||||
|
|
64
flake.nix
64
flake.nix
|
@ -20,7 +20,16 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, blobs, git-hooks, nixpkgs, nixpkgs-25_05, ... }: let
|
outputs =
|
||||||
|
{
|
||||||
|
self,
|
||||||
|
blobs,
|
||||||
|
git-hooks,
|
||||||
|
nixpkgs,
|
||||||
|
nixpkgs-25_05,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
lib = nixpkgs.lib;
|
lib = nixpkgs.lib;
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
@ -44,12 +53,15 @@
|
||||||
"multiple"
|
"multiple"
|
||||||
];
|
];
|
||||||
|
|
||||||
genTest = testName: release: let
|
genTest =
|
||||||
|
testName: release:
|
||||||
|
let
|
||||||
pkgs = release.pkgs;
|
pkgs = release.pkgs;
|
||||||
nixos-lib = import (release.nixpkgs + "/nixos/lib") {
|
nixos-lib = import (release.nixpkgs + "/nixos/lib") {
|
||||||
inherit (pkgs) lib;
|
inherit (pkgs) lib;
|
||||||
};
|
};
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
name = "${testName}-${builtins.replaceStrings [ "." ] [ "_" ] release.name}";
|
name = "${testName}-${builtins.replaceStrings [ "." ] [ "_" ] release.name}";
|
||||||
value = nixos-lib.runTest {
|
value = nixos-lib.runTest {
|
||||||
hostPkgs = pkgs;
|
hostPkgs = pkgs;
|
||||||
|
@ -65,13 +77,13 @@
|
||||||
# external-21_05 = <derivation>;
|
# external-21_05 = <derivation>;
|
||||||
# ...
|
# ...
|
||||||
# }
|
# }
|
||||||
allTests = lib.listToAttrs (
|
allTests = lib.listToAttrs (lib.flatten (map (t: map (r: genTest t r) releases) testNames));
|
||||||
lib.flatten (map (t: map (r: genTest t r) releases) testNames));
|
|
||||||
|
|
||||||
mailserverModule = import ./.;
|
mailserverModule = import ./.;
|
||||||
|
|
||||||
# Generate a MarkDown file describing the options of the NixOS mailserver module
|
# Generate a MarkDown file describing the options of the NixOS mailserver module
|
||||||
optionsDoc = let
|
optionsDoc =
|
||||||
|
let
|
||||||
eval = lib.evalModules {
|
eval = lib.evalModules {
|
||||||
modules = [
|
modules = [
|
||||||
mailserverModule
|
mailserverModule
|
||||||
|
@ -90,10 +102,15 @@
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
options = builtins.toFile "options.json" (builtins.toJSON
|
options = builtins.toFile "options.json" (
|
||||||
(lib.filter (opt: opt.visible && !opt.internal && lib.head opt.loc == "mailserver")
|
builtins.toJSON (
|
||||||
(lib.optionAttrSetToDocList eval.options)));
|
lib.filter (opt: opt.visible && !opt.internal && lib.head opt.loc == "mailserver") (
|
||||||
in pkgs.runCommand "options.md" { buildInputs = [pkgs.python3Minimal]; } ''
|
lib.optionAttrSetToDocList eval.options
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
in
|
||||||
|
pkgs.runCommand "options.md" { buildInputs = [ pkgs.python3Minimal ]; } ''
|
||||||
echo "Generating options.md from ${options}"
|
echo "Generating options.md from ${options}"
|
||||||
python ${./scripts/generate-options.py} ${options} > $out
|
python ${./scripts/generate-options.py} ${options} > $out
|
||||||
echo $out
|
echo $out
|
||||||
|
@ -101,15 +118,22 @@
|
||||||
|
|
||||||
documentation = pkgs.stdenv.mkDerivation {
|
documentation = pkgs.stdenv.mkDerivation {
|
||||||
name = "documentation";
|
name = "documentation";
|
||||||
src = lib.sourceByRegex ./docs ["logo\\.png" "conf\\.py" "Makefile" ".*\\.rst"];
|
src = lib.sourceByRegex ./docs [
|
||||||
buildInputs = [(
|
"logo\\.png"
|
||||||
pkgs.python3.withPackages (p: with p; [
|
"conf\\.py"
|
||||||
|
"Makefile"
|
||||||
|
".*\\.rst"
|
||||||
|
];
|
||||||
|
buildInputs = [
|
||||||
|
(pkgs.python3.withPackages (
|
||||||
|
p: with p; [
|
||||||
sphinx
|
sphinx
|
||||||
sphinx_rtd_theme
|
sphinx_rtd_theme
|
||||||
myst-parser
|
myst-parser
|
||||||
linkify-it-py
|
linkify-it-py
|
||||||
])
|
]
|
||||||
)];
|
))
|
||||||
|
];
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
cp ${optionsDoc} options.md
|
cp ${optionsDoc} options.md
|
||||||
# Workaround for https://github.com/sphinx-doc/sphinx/issues/3451
|
# Workaround for https://github.com/sphinx-doc/sphinx/issues/3451
|
||||||
|
@ -121,7 +145,8 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
nixosModules = rec {
|
nixosModules = rec {
|
||||||
mailserver = mailserverModule;
|
mailserver = mailserverModule;
|
||||||
default = mailserver;
|
default = mailserver;
|
||||||
|
@ -184,9 +209,12 @@
|
||||||
};
|
};
|
||||||
devShells.${system}.default = pkgs.mkShellNoCC {
|
devShells.${system}.default = pkgs.mkShellNoCC {
|
||||||
inputsFrom = [ documentation ];
|
inputsFrom = [ documentation ];
|
||||||
packages = with pkgs; [
|
packages =
|
||||||
|
with pkgs;
|
||||||
|
[
|
||||||
glab
|
glab
|
||||||
] ++ self.checks.${system}.pre-commit.enabledPackages;
|
]
|
||||||
|
++ self.checks.${system}.pre-commit.enabledPackages;
|
||||||
shellHook = self.checks.${system}.pre-commit.shellHook;
|
shellHook = self.checks.${system}.pre-commit.shellHook;
|
||||||
};
|
};
|
||||||
devShell.${system} = self.devShells.${system}.default; # compatibility
|
devShell.${system} = self.devShells.${system}.default; # compatibility
|
||||||
|
|
|
@ -14,28 +14,44 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.mailserver.borgbackup;
|
cfg = config.mailserver.borgbackup;
|
||||||
|
|
||||||
methodFragment = lib.optional (cfg.compression.method != null) cfg.compression.method;
|
methodFragment = lib.optional (cfg.compression.method != null) cfg.compression.method;
|
||||||
autoFragment =
|
autoFragment =
|
||||||
if cfg.compression.auto && cfg.compression.method == null
|
if cfg.compression.auto && cfg.compression.method == null then
|
||||||
then throw "compression.method must be set when using auto."
|
throw "compression.method must be set when using auto."
|
||||||
else lib.optional cfg.compression.auto "auto";
|
else
|
||||||
|
lib.optional cfg.compression.auto "auto";
|
||||||
levelFragment =
|
levelFragment =
|
||||||
if cfg.compression.level != null && cfg.compression.method == null
|
if cfg.compression.level != null && cfg.compression.method == null then
|
||||||
then throw "compression.method must be set when using compression.level."
|
throw "compression.method must be set when using compression.level."
|
||||||
else lib.optional (cfg.compression.level != null) (toString cfg.compression.level);
|
else
|
||||||
compressionFragment = lib.concatStringsSep "," (lib.flatten [autoFragment methodFragment levelFragment]);
|
lib.optional (cfg.compression.level != null) (toString cfg.compression.level);
|
||||||
|
compressionFragment = lib.concatStringsSep "," (
|
||||||
|
lib.flatten [
|
||||||
|
autoFragment
|
||||||
|
methodFragment
|
||||||
|
levelFragment
|
||||||
|
]
|
||||||
|
);
|
||||||
compression = lib.optionalString (compressionFragment != "") "--compression ${compressionFragment}";
|
compression = lib.optionalString (compressionFragment != "") "--compression ${compressionFragment}";
|
||||||
|
|
||||||
encryptionFragment = cfg.encryption.method;
|
encryptionFragment = cfg.encryption.method;
|
||||||
passphraseFile = lib.escapeShellArg cfg.encryption.passphraseFile;
|
passphraseFile = lib.escapeShellArg cfg.encryption.passphraseFile;
|
||||||
passphraseFragment = lib.optionalString (cfg.encryption.method != "none")
|
passphraseFragment = lib.optionalString (cfg.encryption.method != "none") (
|
||||||
(if cfg.encryption.passphraseFile != null then ''env BORG_PASSPHRASE="$(cat ${passphraseFile})"''
|
if cfg.encryption.passphraseFile != null then
|
||||||
else throw "passphraseFile must be set when using encryption.");
|
''env BORG_PASSPHRASE="$(cat ${passphraseFile})"''
|
||||||
|
else
|
||||||
|
throw "passphraseFile must be set when using encryption."
|
||||||
|
);
|
||||||
|
|
||||||
locations = lib.escapeShellArgs cfg.locations;
|
locations = lib.escapeShellArgs cfg.locations;
|
||||||
name = lib.escapeShellArg cfg.name;
|
name = lib.escapeShellArg cfg.name;
|
||||||
|
@ -55,7 +71,8 @@ let
|
||||||
${passphraseFragment} ${pkgs.borgbackup}/bin/borg create ${extraCreateArgs} ${compression} ::${name} ${locations}
|
${passphraseFragment} ${pkgs.borgbackup}/bin/borg create ${extraCreateArgs} ${compression} ::${name} ${locations}
|
||||||
${cmdPostexec}
|
${cmdPostexec}
|
||||||
'';
|
'';
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
config = lib.mkIf (config.mailserver.enable && cfg.enable) {
|
config = lib.mkIf (config.mailserver.enable && cfg.enable) {
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
borgbackup
|
borgbackup
|
||||||
|
|
|
@ -14,43 +14,62 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# cert :: PATH
|
# cert :: PATH
|
||||||
certificatePath = if cfg.certificateScheme == "manual"
|
certificatePath =
|
||||||
then cfg.certificateFile
|
if cfg.certificateScheme == "manual" then
|
||||||
else if cfg.certificateScheme == "selfsigned"
|
cfg.certificateFile
|
||||||
then "${cfg.certificateDirectory}/cert-${cfg.fqdn}.pem"
|
else if cfg.certificateScheme == "selfsigned" then
|
||||||
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
|
"${cfg.certificateDirectory}/cert-${cfg.fqdn}.pem"
|
||||||
then "${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/fullchain.pem"
|
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx" then
|
||||||
else throw "unknown certificate scheme";
|
"${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/fullchain.pem"
|
||||||
|
else
|
||||||
|
throw "unknown certificate scheme";
|
||||||
|
|
||||||
# key :: PATH
|
# key :: PATH
|
||||||
keyPath = if cfg.certificateScheme == "manual"
|
keyPath =
|
||||||
then cfg.keyFile
|
if cfg.certificateScheme == "manual" then
|
||||||
else if cfg.certificateScheme == "selfsigned"
|
cfg.keyFile
|
||||||
then "${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
|
else if cfg.certificateScheme == "selfsigned" then
|
||||||
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
|
"${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
|
||||||
then "${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/key.pem"
|
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx" then
|
||||||
else throw "unknown certificate scheme";
|
"${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/key.pem"
|
||||||
|
else
|
||||||
|
throw "unknown certificate scheme";
|
||||||
|
|
||||||
passwordFiles = let
|
passwordFiles =
|
||||||
|
let
|
||||||
mkHashFile = name: hash: pkgs.writeText "${builtins.hashString "sha256" name}-password-hash" hash;
|
mkHashFile = name: hash: pkgs.writeText "${builtins.hashString "sha256" name}-password-hash" hash;
|
||||||
in
|
in
|
||||||
lib.mapAttrs (name: value:
|
lib.mapAttrs (
|
||||||
|
name: value:
|
||||||
if value.hashedPasswordFile == null then
|
if value.hashedPasswordFile == null then
|
||||||
builtins.toString (mkHashFile name value.hashedPassword)
|
builtins.toString (mkHashFile name value.hashedPassword)
|
||||||
else value.hashedPasswordFile) cfg.loginAccounts;
|
else
|
||||||
|
value.hashedPasswordFile
|
||||||
|
) cfg.loginAccounts;
|
||||||
|
|
||||||
# Appends the LDAP bind password to files to avoid writing this
|
# Appends the LDAP bind password to files to avoid writing this
|
||||||
# password into the Nix store.
|
# password into the Nix store.
|
||||||
appendLdapBindPwd = {
|
appendLdapBindPwd =
|
||||||
name, file, prefix, suffix ? "", passwordFile, destination
|
{
|
||||||
}: pkgs.writeScript "append-ldap-bind-pwd-in-${name}" ''
|
name,
|
||||||
|
file,
|
||||||
|
prefix,
|
||||||
|
suffix ? "",
|
||||||
|
passwordFile,
|
||||||
|
destination,
|
||||||
|
}:
|
||||||
|
pkgs.writeScript "append-ldap-bind-pwd-in-${name}" ''
|
||||||
#!${pkgs.stdenv.shell}
|
#!${pkgs.stdenv.shell}
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,12 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
with (import ./common.nix { inherit config pkgs lib; });
|
with (import ./common.nix { inherit config pkgs lib; });
|
||||||
|
|
||||||
|
@ -28,10 +33,14 @@ let
|
||||||
ldapConfFile = "${passwdDir}/dovecot-ldap.conf.ext";
|
ldapConfFile = "${passwdDir}/dovecot-ldap.conf.ext";
|
||||||
boolToYesNo = x: if x then "yes" else "no";
|
boolToYesNo = x: if x then "yes" else "no";
|
||||||
listToLine = lib.concatStringsSep " ";
|
listToLine = lib.concatStringsSep " ";
|
||||||
listToMultiAttrs = keyPrefix: attrs: lib.listToAttrs (lib.imap1 (n: x: {
|
listToMultiAttrs =
|
||||||
|
keyPrefix: attrs:
|
||||||
|
lib.listToAttrs (
|
||||||
|
lib.imap1 (n: x: {
|
||||||
name = "${keyPrefix}${if n == 1 then "" else toString n}";
|
name = "${keyPrefix}${if n == 1 then "" else toString n}";
|
||||||
value = x;
|
value = x;
|
||||||
}) attrs);
|
}) attrs
|
||||||
|
);
|
||||||
|
|
||||||
maildirLayoutAppendix = lib.optionalString cfg.useFsLayout ":LAYOUT=fs";
|
maildirLayoutAppendix = lib.optionalString cfg.useFsLayout ":LAYOUT=fs";
|
||||||
maildirUTF8FolderNames = lib.optionalString cfg.useUTF8FolderNames ":UTF-8";
|
maildirUTF8FolderNames = lib.optionalString cfg.useUTF8FolderNames ":UTF-8";
|
||||||
|
@ -39,9 +48,7 @@ let
|
||||||
# maildir in format "/${domain}/${user}"
|
# maildir in format "/${domain}/${user}"
|
||||||
dovecotMaildir =
|
dovecotMaildir =
|
||||||
"maildir:${cfg.mailDirectory}/%{domain}/%{username}${maildirLayoutAppendix}${maildirUTF8FolderNames}"
|
"maildir:${cfg.mailDirectory}/%{domain}/%{username}${maildirLayoutAppendix}${maildirUTF8FolderNames}"
|
||||||
+ (lib.optionalString (cfg.indexDir != null)
|
+ (lib.optionalString (cfg.indexDir != null) ":INDEX=${cfg.indexDir}/%{domain}/%{username}");
|
||||||
":INDEX=${cfg.indexDir}/%{domain}/%{username}"
|
|
||||||
);
|
|
||||||
|
|
||||||
postfixCfg = config.services.postfix;
|
postfixCfg = config.services.postfix;
|
||||||
|
|
||||||
|
@ -93,7 +100,9 @@ let
|
||||||
# Prevent world-readable password files, even temporarily.
|
# Prevent world-readable password files, even temporarily.
|
||||||
umask 077
|
umask 077
|
||||||
|
|
||||||
for f in ${builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts)}; do
|
for f in ${
|
||||||
|
builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts)
|
||||||
|
}; do
|
||||||
if [ ! -f "$f" ]; then
|
if [ ! -f "$f" ]; then
|
||||||
echo "Expected password hash file $f does not exist!"
|
echo "Expected password hash file $f does not exist!"
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -101,34 +110,49 @@ let
|
||||||
done
|
done
|
||||||
|
|
||||||
cat <<EOF > ${passwdFile}
|
cat <<EOF > ${passwdFile}
|
||||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: _:
|
${lib.concatStringsSep "\n" (
|
||||||
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
|
lib.mapAttrsToList (
|
||||||
) cfg.loginAccounts)}
|
name: _: "${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
|
||||||
|
) cfg.loginAccounts
|
||||||
|
)}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat <<EOF > ${userdbFile}
|
cat <<EOF > ${userdbFile}
|
||||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value:
|
${lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
name: value:
|
||||||
"${name}:::::::"
|
"${name}:::::::"
|
||||||
+ lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}"
|
+ lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}"
|
||||||
) cfg.loginAccounts)}
|
) cfg.loginAccounts
|
||||||
|
)}
|
||||||
EOF
|
EOF
|
||||||
'';
|
'';
|
||||||
|
|
||||||
junkMailboxes = builtins.attrNames (lib.filterAttrs (_: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes);
|
junkMailboxes = builtins.attrNames (
|
||||||
|
lib.filterAttrs (_: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes
|
||||||
|
);
|
||||||
junkMailboxNumber = builtins.length junkMailboxes;
|
junkMailboxNumber = builtins.length junkMailboxes;
|
||||||
# The assertion garantees there is exactly one Junk mailbox.
|
# The assertion garantees there is exactly one Junk mailbox.
|
||||||
junkMailboxName = if junkMailboxNumber == 1 then builtins.elemAt junkMailboxes 0 else "";
|
junkMailboxName = if junkMailboxNumber == 1 then builtins.elemAt junkMailboxes 0 else "";
|
||||||
|
|
||||||
mkLdapSearchScope = scope: (
|
mkLdapSearchScope =
|
||||||
if scope == "sub" then "subtree"
|
scope:
|
||||||
else if scope == "one" then "onelevel"
|
(
|
||||||
else scope
|
if scope == "sub" then
|
||||||
|
"subtree"
|
||||||
|
else if scope == "one" then
|
||||||
|
"onelevel"
|
||||||
|
else
|
||||||
|
scope
|
||||||
);
|
);
|
||||||
|
|
||||||
ftsPluginSettings = {
|
ftsPluginSettings = {
|
||||||
fts = "flatcurve";
|
fts = "flatcurve";
|
||||||
fts_languages = listToLine cfg.fullTextSearch.languages;
|
fts_languages = listToLine cfg.fullTextSearch.languages;
|
||||||
fts_tokenizers = listToLine [ "generic" "email-address" ];
|
fts_tokenizers = listToLine [
|
||||||
|
"generic"
|
||||||
|
"email-address"
|
||||||
|
];
|
||||||
fts_tokenizer_email_address = "maxlen=100"; # default 254 too large for Xapian
|
fts_tokenizer_email_address = "maxlen=100"; # default 254 too large for Xapian
|
||||||
fts_flatcurve_substring_search = boolToYesNo cfg.fullTextSearch.substringSearch;
|
fts_flatcurve_substring_search = boolToYesNo cfg.fullTextSearch.substringSearch;
|
||||||
fts_filters = listToLine cfg.fullTextSearch.filters;
|
fts_filters = listToLine cfg.fullTextSearch.filters;
|
||||||
|
@ -139,7 +163,9 @@ let
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf enable {
|
config =
|
||||||
|
with cfg;
|
||||||
|
lib.mkIf enable {
|
||||||
assertions = [
|
assertions = [
|
||||||
{
|
{
|
||||||
assertion = junkMailboxNumber == 1;
|
assertion = junkMailboxNumber == 1;
|
||||||
|
@ -148,18 +174,19 @@ in
|
||||||
];
|
];
|
||||||
|
|
||||||
warnings =
|
warnings =
|
||||||
lib.optional (
|
lib.optional
|
||||||
(builtins.length cfg.fullTextSearch.languages > 1) &&
|
(
|
||||||
(builtins.elem "stopwords" cfg.fullTextSearch.filters)
|
(builtins.length cfg.fullTextSearch.languages > 1)
|
||||||
) ''
|
&& (builtins.elem "stopwords" cfg.fullTextSearch.filters)
|
||||||
|
)
|
||||||
|
''
|
||||||
Using stopwords in `mailserver.fullTextSearch.filters` with multiple
|
Using stopwords in `mailserver.fullTextSearch.filters` with multiple
|
||||||
languages in `mailserver.fullTextSearch.languages` configured WILL
|
languages in `mailserver.fullTextSearch.languages` configured WILL
|
||||||
cause some searches to fail.
|
cause some searches to fail.
|
||||||
|
|
||||||
The recommended solution is to NOT use the stopword filter when
|
The recommended solution is to NOT use the stopword filter when
|
||||||
multiple languages are present in the configuration.
|
multiple languages are present in the configuration.
|
||||||
''
|
'';
|
||||||
;
|
|
||||||
|
|
||||||
# for sieve-test. Shelling it in on demand usually doesnt' work, as it reads
|
# for sieve-test. Shelling it in on demand usually doesnt' work, as it reads
|
||||||
# the global config and tries to open shared libraries configured in there,
|
# the global config and tries to open shared libraries configured in there,
|
||||||
|
@ -211,17 +238,18 @@ in
|
||||||
'';
|
'';
|
||||||
|
|
||||||
pipeBins = map lib.getExe [
|
pipeBins = map lib.getExe [
|
||||||
(pkgs.writeShellScriptBin "rspamd-learn-ham.sh"
|
(pkgs.writeShellScriptBin "rspamd-learn-ham.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_ham")
|
||||||
"exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_ham")
|
(pkgs.writeShellScriptBin "rspamd-learn-spam.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_spam")
|
||||||
(pkgs.writeShellScriptBin "rspamd-learn-spam.sh"
|
|
||||||
"exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_spam")
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
imapsieve.mailbox = [
|
imapsieve.mailbox = [
|
||||||
{
|
{
|
||||||
name = junkMailboxName;
|
name = junkMailboxName;
|
||||||
causes = [ "COPY" "APPEND" ];
|
causes = [
|
||||||
|
"COPY"
|
||||||
|
"APPEND"
|
||||||
|
];
|
||||||
before = ./dovecot/imap_sieve/report-spam.sieve;
|
before = ./dovecot/imap_sieve/report-spam.sieve;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -245,42 +273,62 @@ in
|
||||||
${lib.optionalString (cfg.enableImap || cfg.enableImapSsl) ''
|
${lib.optionalString (cfg.enableImap || cfg.enableImapSsl) ''
|
||||||
service imap-login {
|
service imap-login {
|
||||||
inet_listener imap {
|
inet_listener imap {
|
||||||
${if cfg.enableImap then ''
|
${
|
||||||
|
if cfg.enableImap then
|
||||||
|
''
|
||||||
port = 143
|
port = 143
|
||||||
'' else ''
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||||
port = 0
|
port = 0
|
||||||
''}
|
''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inet_listener imaps {
|
inet_listener imaps {
|
||||||
${if cfg.enableImapSsl then ''
|
${
|
||||||
|
if cfg.enableImapSsl then
|
||||||
|
''
|
||||||
port = 993
|
port = 993
|
||||||
ssl = yes
|
ssl = yes
|
||||||
'' else ''
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||||
port = 0
|
port = 0
|
||||||
''}
|
''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
''}
|
''}
|
||||||
${lib.optionalString (cfg.enablePop3 || cfg.enablePop3Ssl) ''
|
${lib.optionalString (cfg.enablePop3 || cfg.enablePop3Ssl) ''
|
||||||
service pop3-login {
|
service pop3-login {
|
||||||
inet_listener pop3 {
|
inet_listener pop3 {
|
||||||
${if cfg.enablePop3 then ''
|
${
|
||||||
|
if cfg.enablePop3 then
|
||||||
|
''
|
||||||
port = 110
|
port = 110
|
||||||
'' else ''
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||||
port = 0
|
port = 0
|
||||||
''}
|
''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inet_listener pop3s {
|
inet_listener pop3s {
|
||||||
${if cfg.enablePop3Ssl then ''
|
${
|
||||||
|
if cfg.enablePop3Ssl then
|
||||||
|
''
|
||||||
port = 995
|
port = 995
|
||||||
ssl = yes
|
ssl = yes
|
||||||
'' else ''
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
# see https://dovecot.org/pipermail/dovecot/2010-March/047479.html
|
||||||
port = 0
|
port = 0
|
||||||
''}
|
''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
''}
|
''}
|
||||||
|
@ -383,11 +431,15 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.dovecot2 = {
|
systemd.services.dovecot2 = {
|
||||||
preStart = ''
|
preStart =
|
||||||
|
''
|
||||||
${genPasswdScript}
|
${genPasswdScript}
|
||||||
'' + (lib.optionalString cfg.ldap.enable setPwdInLdapConfFile);
|
''
|
||||||
|
+ (lib.optionalString cfg.ldap.enable setPwdInLdapConfFile);
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.postfix.restartTriggers = [ genPasswdScript ] ++ (lib.optional cfg.ldap.enable [setPwdInLdapConfFile]);
|
systemd.services.postfix.restartTriggers = [
|
||||||
|
genPasswdScript
|
||||||
|
] ++ (lib.optional cfg.ldap.enable [ setPwdInLdapConfFile ]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,15 +14,28 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf enable {
|
config =
|
||||||
environment.systemPackages = with pkgs; [
|
with cfg;
|
||||||
dovecot openssh postfix rspamd
|
lib.mkIf enable {
|
||||||
] ++ (if certificateScheme == "selfsigned" then [ openssl ] else []);
|
environment.systemPackages =
|
||||||
|
with pkgs;
|
||||||
|
[
|
||||||
|
dovecot
|
||||||
|
openssh
|
||||||
|
postfix
|
||||||
|
rspamd
|
||||||
|
]
|
||||||
|
++ (if certificateScheme == "selfsigned" then [ openssl ] else [ ]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,4 +24,3 @@ in
|
||||||
services.kresd.enable = true;
|
services.kresd.enable = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,13 @@ let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf (enable && openFirewall) {
|
config =
|
||||||
|
with cfg;
|
||||||
|
lib.mkIf (enable && openFirewall) {
|
||||||
|
|
||||||
networking.firewall = {
|
networking.firewall = {
|
||||||
allowedTCPPorts = [ 25 ]
|
allowedTCPPorts =
|
||||||
|
[ 25 ]
|
||||||
++ lib.optional enableSubmission 587
|
++ lib.optional enableSubmission 587
|
||||||
++ lib.optional enableSubmissionSsl 465
|
++ lib.optional enableSubmissionSsl 465
|
||||||
++ lib.optional enableImap 143
|
++ lib.optional enableImap 143
|
||||||
|
|
|
@ -14,8 +14,12 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
{
|
||||||
{ config, pkgs, lib, ... }:
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
with (import ./common.nix { inherit config lib pkgs; });
|
with (import ./common.nix { inherit config lib pkgs; });
|
||||||
|
|
||||||
|
@ -23,7 +27,9 @@ let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = lib.mkIf (cfg.enable && (cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx")) {
|
config =
|
||||||
|
lib.mkIf (cfg.enable && (cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"))
|
||||||
|
{
|
||||||
services.nginx = lib.mkIf (cfg.certificateScheme == "acme-nginx") {
|
services.nginx = lib.mkIf (cfg.certificateScheme == "acme-nginx") {
|
||||||
enable = true;
|
enable = true;
|
||||||
virtualHosts."${cfg.fqdn}" = {
|
virtualHosts."${cfg.fqdn}" = {
|
||||||
|
|
|
@ -14,7 +14,12 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
with (import ./common.nix { inherit config pkgs lib; });
|
with (import ./common.nix { inherit config pkgs lib; });
|
||||||
|
|
||||||
|
@ -28,31 +33,55 @@ let
|
||||||
mergeLookupTables = tables: lib.zipAttrsWith (_: v: lib.flatten v) tables;
|
mergeLookupTables = tables: lib.zipAttrsWith (_: v: lib.flatten v) tables;
|
||||||
|
|
||||||
# valiases_postfix :: Map String [String]
|
# valiases_postfix :: Map String [String]
|
||||||
valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
valiases_postfix = mergeLookupTables (
|
||||||
(name: value:
|
lib.flatten (
|
||||||
let to = name;
|
lib.mapAttrsToList (
|
||||||
in map (from: {"${from}" = to;}) (value.aliases ++ lib.singleton name))
|
name: value:
|
||||||
cfg.loginAccounts));
|
let
|
||||||
regex_valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
to = name;
|
||||||
(name: value:
|
in
|
||||||
let to = name;
|
map (from: { "${from}" = to; }) (value.aliases ++ lib.singleton name)
|
||||||
in map (from: {"${from}" = to;}) value.aliasesRegexp)
|
) cfg.loginAccounts
|
||||||
cfg.loginAccounts));
|
)
|
||||||
|
);
|
||||||
|
regex_valiases_postfix = mergeLookupTables (
|
||||||
|
lib.flatten (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
name: value:
|
||||||
|
let
|
||||||
|
to = name;
|
||||||
|
in
|
||||||
|
map (from: { "${from}" = to; }) value.aliasesRegexp
|
||||||
|
) cfg.loginAccounts
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
# catchAllPostfix :: Map String [String]
|
# catchAllPostfix :: Map String [String]
|
||||||
catchAllPostfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
catchAllPostfix = mergeLookupTables (
|
||||||
(name: value:
|
lib.flatten (
|
||||||
let to = name;
|
lib.mapAttrsToList (
|
||||||
in map (from: {"@${from}" = to;}) value.catchAll)
|
name: value:
|
||||||
cfg.loginAccounts));
|
let
|
||||||
|
to = name;
|
||||||
|
in
|
||||||
|
map (from: { "@${from}" = to; }) value.catchAll
|
||||||
|
) cfg.loginAccounts
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
# all_valiases_postfix :: Map String [String]
|
# all_valiases_postfix :: Map String [String]
|
||||||
all_valiases_postfix = mergeLookupTables [valiases_postfix extra_valiases_postfix];
|
all_valiases_postfix = mergeLookupTables [
|
||||||
|
valiases_postfix
|
||||||
|
extra_valiases_postfix
|
||||||
|
];
|
||||||
|
|
||||||
# attrsToLookupTable :: Map String (Either String [ String ]) -> Map String [String]
|
# attrsToLookupTable :: Map String (Either String [ String ]) -> Map String [String]
|
||||||
attrsToLookupTable = aliases: let
|
attrsToLookupTable =
|
||||||
|
aliases:
|
||||||
|
let
|
||||||
lookupTables = lib.mapAttrsToList (from: to: { "${from}" = to; }) aliases;
|
lookupTables = lib.mapAttrsToList (from: to: { "${from}" = to; }) aliases;
|
||||||
in mergeLookupTables lookupTables;
|
in
|
||||||
|
mergeLookupTables lookupTables;
|
||||||
|
|
||||||
# extra_valiases_postfix :: Map String [String]
|
# extra_valiases_postfix :: Map String [String]
|
||||||
extra_valiases_postfix = attrsToLookupTable cfg.extraVirtualAliases;
|
extra_valiases_postfix = attrsToLookupTable cfg.extraVirtualAliases;
|
||||||
|
@ -61,37 +90,49 @@ let
|
||||||
forwards = attrsToLookupTable cfg.forwards;
|
forwards = attrsToLookupTable cfg.forwards;
|
||||||
|
|
||||||
# lookupTableToString :: Map String [String] -> String
|
# lookupTableToString :: Map String [String] -> String
|
||||||
lookupTableToString = attrs: let
|
lookupTableToString =
|
||||||
|
attrs:
|
||||||
|
let
|
||||||
valueToString = value: lib.concatStringsSep ", " value;
|
valueToString = value: lib.concatStringsSep ", " value;
|
||||||
in lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs);
|
in
|
||||||
|
lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs
|
||||||
|
);
|
||||||
|
|
||||||
# valiases_file :: Path
|
# valiases_file :: Path
|
||||||
valiases_file = let
|
valiases_file =
|
||||||
content = lookupTableToString (mergeLookupTables [all_valiases_postfix catchAllPostfix]);
|
let
|
||||||
in builtins.toFile "valias" content;
|
content = lookupTableToString (mergeLookupTables [
|
||||||
|
all_valiases_postfix
|
||||||
|
catchAllPostfix
|
||||||
|
]);
|
||||||
|
in
|
||||||
|
builtins.toFile "valias" content;
|
||||||
|
|
||||||
regex_valiases_file = let
|
regex_valiases_file =
|
||||||
|
let
|
||||||
content = lookupTableToString regex_valiases_postfix;
|
content = lookupTableToString regex_valiases_postfix;
|
||||||
in builtins.toFile "regex_valias" content;
|
in
|
||||||
|
builtins.toFile "regex_valias" content;
|
||||||
|
|
||||||
# denied_recipients_postfix :: [ String ]
|
# denied_recipients_postfix :: [ String ]
|
||||||
denied_recipients_postfix = map
|
denied_recipients_postfix = map (acct: "${acct.name} REJECT ${acct.sendOnlyRejectMessage}") (
|
||||||
(acct: "${acct.name} REJECT ${acct.sendOnlyRejectMessage}")
|
lib.filter (acct: acct.sendOnly) (lib.attrValues cfg.loginAccounts)
|
||||||
(lib.filter (acct: acct.sendOnly) (lib.attrValues cfg.loginAccounts));
|
);
|
||||||
denied_recipients_file = builtins.toFile "denied_recipients" (lib.concatStringsSep "\n" denied_recipients_postfix);
|
denied_recipients_file = builtins.toFile "denied_recipients" (
|
||||||
|
lib.concatStringsSep "\n" denied_recipients_postfix
|
||||||
|
);
|
||||||
|
|
||||||
reject_senders_postfix = map
|
reject_senders_postfix = map (sender: "${sender} REJECT") cfg.rejectSender;
|
||||||
(sender:
|
reject_senders_file = builtins.toFile "reject_senders" (
|
||||||
"${sender} REJECT")
|
lib.concatStringsSep "\n" reject_senders_postfix
|
||||||
cfg.rejectSender;
|
);
|
||||||
reject_senders_file = builtins.toFile "reject_senders" (lib.concatStringsSep "\n" reject_senders_postfix) ;
|
|
||||||
|
|
||||||
reject_recipients_postfix = map
|
reject_recipients_postfix = map (recipient: "${recipient} REJECT") cfg.rejectRecipients;
|
||||||
(recipient:
|
|
||||||
"${recipient} REJECT")
|
|
||||||
cfg.rejectRecipients;
|
|
||||||
# rejectRecipients :: [ Path ]
|
# rejectRecipients :: [ Path ]
|
||||||
reject_recipients_file = builtins.toFile "reject_recipients" (lib.concatStringsSep "\n" reject_recipients_postfix) ;
|
reject_recipients_file = builtins.toFile "reject_recipients" (
|
||||||
|
lib.concatStringsSep "\n" reject_recipients_postfix
|
||||||
|
);
|
||||||
|
|
||||||
# vhosts_file :: Path
|
# vhosts_file :: Path
|
||||||
vhosts_file = builtins.toFile "vhosts" (concatStringsSep "\n" cfg.domains);
|
vhosts_file = builtins.toFile "vhosts" (concatStringsSep "\n" cfg.domains);
|
||||||
|
@ -103,9 +144,12 @@ let
|
||||||
# every alias is owned (uniquely) by its user.
|
# every alias is owned (uniquely) by its user.
|
||||||
# The user's own address is already in all_valiases_postfix.
|
# The user's own address is already in all_valiases_postfix.
|
||||||
vaccounts_file = builtins.toFile "vaccounts" (lookupTableToString all_valiases_postfix);
|
vaccounts_file = builtins.toFile "vaccounts" (lookupTableToString all_valiases_postfix);
|
||||||
regex_vaccounts_file = builtins.toFile "regex_vaccounts" (lookupTableToString regex_valiases_postfix);
|
regex_vaccounts_file = builtins.toFile "regex_vaccounts" (
|
||||||
|
lookupTableToString regex_valiases_postfix
|
||||||
|
);
|
||||||
|
|
||||||
submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules" (''
|
submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules" (
|
||||||
|
''
|
||||||
# Removes sensitive headers from mails handed in via the submission port.
|
# Removes sensitive headers from mails handed in via the submission port.
|
||||||
# See https://thomas-leister.de/mailserver-debian-stretch/
|
# See https://thomas-leister.de/mailserver-debian-stretch/
|
||||||
# Uses "pcre" style regex.
|
# Uses "pcre" style regex.
|
||||||
|
@ -115,21 +159,22 @@ let
|
||||||
/^X-Mailer:/ IGNORE
|
/^X-Mailer:/ IGNORE
|
||||||
/^User-Agent:/ IGNORE
|
/^User-Agent:/ IGNORE
|
||||||
/^X-Enigmail:/ IGNORE
|
/^X-Enigmail:/ IGNORE
|
||||||
'' + lib.optionalString cfg.rewriteMessageId ''
|
''
|
||||||
|
+ lib.optionalString cfg.rewriteMessageId ''
|
||||||
|
|
||||||
# Replaces the user submitted hostname with the server's FQDN to hide the
|
# Replaces the user submitted hostname with the server's FQDN to hide the
|
||||||
# user's host or network.
|
# user's host or network.
|
||||||
|
|
||||||
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.fqdn}>
|
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.fqdn}>
|
||||||
'');
|
''
|
||||||
|
);
|
||||||
|
|
||||||
smtpdMilters = [ "unix:/run/rspamd/rspamd-milter.sock" ];
|
smtpdMilters = [ "unix:/run/rspamd/rspamd-milter.sock" ];
|
||||||
|
|
||||||
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
||||||
mappedRegexFile = name: "pcre:/var/lib/postfix/conf/${name}";
|
mappedRegexFile = name: "pcre:/var/lib/postfix/conf/${name}";
|
||||||
|
|
||||||
submissionOptions =
|
submissionOptions = {
|
||||||
{
|
|
||||||
smtpd_tls_security_level = "encrypt";
|
smtpd_tls_security_level = "encrypt";
|
||||||
smtpd_sasl_auth_enable = "yes";
|
smtpd_sasl_auth_enable = "yes";
|
||||||
smtpd_sasl_type = "dovecot";
|
smtpd_sasl_type = "dovecot";
|
||||||
|
@ -137,7 +182,9 @@ let
|
||||||
smtpd_sasl_security_options = "noanonymous";
|
smtpd_sasl_security_options = "noanonymous";
|
||||||
smtpd_sasl_local_domain = "$myhostname";
|
smtpd_sasl_local_domain = "$myhostname";
|
||||||
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
||||||
smtpd_sender_login_maps = "hash:/etc/postfix/vaccounts${lib.optionalString cfg.ldap.enable ",ldap:${ldapSenderLoginMapFile}"}${lib.optionalString (regex_valiases_postfix != {}) ",pcre:/etc/postfix/regex_vaccounts"}";
|
smtpd_sender_login_maps = "hash:/etc/postfix/vaccounts${lib.optionalString cfg.ldap.enable ",ldap:${ldapSenderLoginMapFile}"}${
|
||||||
|
lib.optionalString (regex_valiases_postfix != { }) ",pcre:/etc/postfix/regex_vaccounts"
|
||||||
|
}";
|
||||||
smtpd_sender_restrictions = "reject_sender_login_mismatch";
|
smtpd_sender_restrictions = "reject_sender_login_mismatch";
|
||||||
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
||||||
cleanup_service_name = "submission-header-cleanup";
|
cleanup_service_name = "submission-header-cleanup";
|
||||||
|
@ -186,14 +233,19 @@ let
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf enable {
|
config =
|
||||||
|
with cfg;
|
||||||
|
lib.mkIf enable {
|
||||||
|
|
||||||
systemd.services.postfix-setup = lib.mkIf cfg.ldap.enable {
|
systemd.services.postfix-setup = lib.mkIf cfg.ldap.enable {
|
||||||
preStart = ''
|
preStart = ''
|
||||||
${appendPwdInVirtualMailboxMap}
|
${appendPwdInVirtualMailboxMap}
|
||||||
${appendPwdInSenderLoginMap}
|
${appendPwdInSenderLoginMap}
|
||||||
'';
|
'';
|
||||||
restartTriggers = [ appendPwdInVirtualMailboxMap appendPwdInSenderLoginMap ];
|
restartTriggers = [
|
||||||
|
appendPwdInVirtualMailboxMap
|
||||||
|
appendPwdInSenderLoginMap
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
services.postfix = {
|
services.postfix = {
|
||||||
|
@ -209,7 +261,11 @@ in
|
||||||
mapFiles."reject_recipients" = reject_recipients_file;
|
mapFiles."reject_recipients" = reject_recipients_file;
|
||||||
enableSubmission = cfg.enableSubmission;
|
enableSubmission = cfg.enableSubmission;
|
||||||
enableSubmissions = cfg.enableSubmissionSsl;
|
enableSubmissions = cfg.enableSubmissionSsl;
|
||||||
virtual = lookupTableToString (mergeLookupTables [all_valiases_postfix catchAllPostfix forwards]);
|
virtual = lookupTableToString (mergeLookupTables [
|
||||||
|
all_valiases_postfix
|
||||||
|
catchAllPostfix
|
||||||
|
forwards
|
||||||
|
]);
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
smtpd_tls_chain_files = [
|
smtpd_tls_chain_files = [
|
||||||
|
@ -229,16 +285,21 @@ in
|
||||||
virtual_gid_maps = "static:5000";
|
virtual_gid_maps = "static:5000";
|
||||||
virtual_mailbox_base = mailDirectory;
|
virtual_mailbox_base = mailDirectory;
|
||||||
virtual_mailbox_domains = vhosts_file;
|
virtual_mailbox_domains = vhosts_file;
|
||||||
virtual_mailbox_maps = [
|
virtual_mailbox_maps =
|
||||||
|
[
|
||||||
(mappedFile "valias")
|
(mappedFile "valias")
|
||||||
] ++ lib.optionals cfg.ldap.enable [
|
]
|
||||||
|
++ lib.optionals cfg.ldap.enable [
|
||||||
"ldap:${ldapVirtualMailboxMapFile}"
|
"ldap:${ldapVirtualMailboxMapFile}"
|
||||||
] ++ lib.optionals (regex_valiases_postfix != {}) [
|
]
|
||||||
|
++ lib.optionals (regex_valiases_postfix != { }) [
|
||||||
(mappedRegexFile "regex_valias")
|
(mappedRegexFile "regex_valias")
|
||||||
];
|
];
|
||||||
virtual_alias_maps = lib.mkAfter (lib.optionals (regex_valiases_postfix != {}) [
|
virtual_alias_maps = lib.mkAfter (
|
||||||
|
lib.optionals (regex_valiases_postfix != { }) [
|
||||||
(mappedRegexFile "regex_valias")
|
(mappedRegexFile "regex_valias")
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
||||||
# Avoid leakage of X-Original-To, X-Delivered-To headers between recipients
|
# Avoid leakage of X-Original-To, X-Delivered-To headers between recipients
|
||||||
lmtp_destination_recipient_limit = "1";
|
lmtp_destination_recipient_limit = "1";
|
||||||
|
@ -248,7 +309,9 @@ in
|
||||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
smtpd_sasl_path = "/run/dovecot2/auth";
|
||||||
smtpd_sasl_auth_enable = true;
|
smtpd_sasl_auth_enable = true;
|
||||||
smtpd_relay_restrictions = [
|
smtpd_relay_restrictions = [
|
||||||
"permit_mynetworks" "permit_sasl_authenticated" "reject_unauth_destination"
|
"permit_mynetworks"
|
||||||
|
"permit_sasl_authenticated"
|
||||||
|
"reject_unauth_destination"
|
||||||
];
|
];
|
||||||
|
|
||||||
# reject selected senders
|
# reject selected senders
|
||||||
|
@ -341,7 +404,10 @@ in
|
||||||
chroot = false;
|
chroot = false;
|
||||||
maxproc = 0;
|
maxproc = 0;
|
||||||
command = "cleanup";
|
command = "cleanup";
|
||||||
args = ["-o" "header_checks=pcre:${submissionHeaderCleanupRules}"];
|
args = [
|
||||||
|
"-o"
|
||||||
|
"header_checks=pcre:${submissionHeaderCleanupRules}"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,12 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
|
@ -38,7 +43,8 @@ let
|
||||||
${cfg.backup.cmdPostexec}
|
${cfg.backup.cmdPostexec}
|
||||||
'';
|
'';
|
||||||
postexecString = optionalString postexecDefined "cmd_postexec ${postexecWrapped}";
|
postexecString = optionalString postexecDefined "cmd_postexec ${postexecWrapped}";
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
config = mkIf (cfg.enable && cfg.backup.enable) {
|
config = mkIf (cfg.enable && cfg.backup.enable) {
|
||||||
services.rsnapshot = {
|
services.rsnapshot = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
|
@ -14,7 +14,12 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
|
@ -26,10 +31,13 @@ let
|
||||||
rspamdUser = config.services.rspamd.user;
|
rspamdUser = config.services.rspamd.user;
|
||||||
rspamdGroup = config.services.rspamd.group;
|
rspamdGroup = config.services.rspamd.group;
|
||||||
|
|
||||||
createDkimKeypair = domain: let
|
createDkimKeypair =
|
||||||
|
domain:
|
||||||
|
let
|
||||||
privateKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.key";
|
privateKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.key";
|
||||||
publicKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.txt";
|
publicKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.txt";
|
||||||
in pkgs.writeShellScript "dkim-keygen-${domain}" ''
|
in
|
||||||
|
pkgs.writeShellScript "dkim-keygen-${domain}" ''
|
||||||
if [ ! -f "${privateKey}" ]
|
if [ ! -f "${privateKey}" ]
|
||||||
then
|
then
|
||||||
${lib.getExe' pkgs.rspamd "rspamadm"} dkim_keygen \
|
${lib.getExe' pkgs.rspamd "rspamadm"} dkim_keygen \
|
||||||
|
@ -44,38 +52,53 @@ let
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf enable {
|
config =
|
||||||
|
with cfg;
|
||||||
|
lib.mkIf enable {
|
||||||
environment.systemPackages = lib.mkBefore [
|
environment.systemPackages = lib.mkBefore [
|
||||||
(pkgs.runCommand "rspamc-wrapped" {
|
(pkgs.runCommand "rspamc-wrapped"
|
||||||
|
{
|
||||||
nativeBuildInputs = with pkgs; [ makeWrapper ];
|
nativeBuildInputs = with pkgs; [ makeWrapper ];
|
||||||
}''
|
}
|
||||||
|
''
|
||||||
makeWrapper ${pkgs.rspamd}/bin/rspamc $out/bin/rspamc \
|
makeWrapper ${pkgs.rspamd}/bin/rspamc $out/bin/rspamc \
|
||||||
--add-flags "-h /run/rspamd/worker-controller.sock"
|
--add-flags "-h /run/rspamd/worker-controller.sock"
|
||||||
'')
|
''
|
||||||
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
services.rspamd = {
|
services.rspamd = {
|
||||||
enable = true;
|
enable = true;
|
||||||
inherit debug;
|
inherit debug;
|
||||||
locals = {
|
locals = {
|
||||||
"milter_headers.conf" = { text = ''
|
"milter_headers.conf" = {
|
||||||
|
text = ''
|
||||||
extended_spam_headers = true;
|
extended_spam_headers = true;
|
||||||
''; };
|
'';
|
||||||
"redis.conf" = { text = ''
|
};
|
||||||
servers = "${if cfg.redis.port == null
|
"redis.conf" = {
|
||||||
then
|
text =
|
||||||
|
''
|
||||||
|
servers = "${
|
||||||
|
if cfg.redis.port == null then
|
||||||
cfg.redis.address
|
cfg.redis.address
|
||||||
else
|
else
|
||||||
"${cfg.redis.address}:${toString cfg.redis.port}"}";
|
"${cfg.redis.address}:${toString cfg.redis.port}"
|
||||||
'' + (lib.optionalString (cfg.redis.password != null) ''
|
}";
|
||||||
|
''
|
||||||
|
+ (lib.optionalString (cfg.redis.password != null) ''
|
||||||
password = "${cfg.redis.password}";
|
password = "${cfg.redis.password}";
|
||||||
''); };
|
'');
|
||||||
"classifier-bayes.conf" = { text = ''
|
};
|
||||||
|
"classifier-bayes.conf" = {
|
||||||
|
text = ''
|
||||||
cache {
|
cache {
|
||||||
backend = "redis";
|
backend = "redis";
|
||||||
}
|
}
|
||||||
''; };
|
'';
|
||||||
"antivirus.conf" = lib.mkIf cfg.virusScanning { text = ''
|
};
|
||||||
|
"antivirus.conf" = lib.mkIf cfg.virusScanning {
|
||||||
|
text = ''
|
||||||
clamav {
|
clamav {
|
||||||
action = "reject";
|
action = "reject";
|
||||||
symbol = "CLAM_VIRUS";
|
symbol = "CLAM_VIRUS";
|
||||||
|
@ -84,15 +107,19 @@ in
|
||||||
servers = "/run/clamav/clamd.ctl";
|
servers = "/run/clamav/clamd.ctl";
|
||||||
scan_mime_parts = false; # scan mail as a whole unit, not parts. seems to be needed to work at all
|
scan_mime_parts = false; # scan mail as a whole unit, not parts. seems to be needed to work at all
|
||||||
}
|
}
|
||||||
''; };
|
'';
|
||||||
"dkim_signing.conf" = { text = ''
|
};
|
||||||
|
"dkim_signing.conf" = {
|
||||||
|
text = ''
|
||||||
enabled = ${lib.boolToString cfg.dkimSigning};
|
enabled = ${lib.boolToString cfg.dkimSigning};
|
||||||
path = "${cfg.dkimKeyDirectory}/$domain.$selector.key";
|
path = "${cfg.dkimKeyDirectory}/$domain.$selector.key";
|
||||||
selector = "${cfg.dkimSelector}";
|
selector = "${cfg.dkimSelector}";
|
||||||
# Allow for usernames w/o domain part
|
# Allow for usernames w/o domain part
|
||||||
allow_username_mismatch = true
|
allow_username_mismatch = true
|
||||||
''; };
|
'';
|
||||||
"dmarc.conf" = { text = ''
|
};
|
||||||
|
"dmarc.conf" = {
|
||||||
|
text = ''
|
||||||
${lib.optionalString cfg.dmarcReporting.enable ''
|
${lib.optionalString cfg.dmarcReporting.enable ''
|
||||||
reporting {
|
reporting {
|
||||||
enabled = true;
|
enabled = true;
|
||||||
|
@ -105,15 +132,18 @@ in
|
||||||
exclude_domains = ${builtins.toJSON cfg.dmarcReporting.excludeDomains};
|
exclude_domains = ${builtins.toJSON cfg.dmarcReporting.excludeDomains};
|
||||||
''}
|
''}
|
||||||
}''}
|
}''}
|
||||||
''; };
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
workers.rspamd_proxy = {
|
workers.rspamd_proxy = {
|
||||||
type = "rspamd_proxy";
|
type = "rspamd_proxy";
|
||||||
bindSockets = [{
|
bindSockets = [
|
||||||
|
{
|
||||||
socket = "/run/rspamd/rspamd-milter.sock";
|
socket = "/run/rspamd/rspamd-milter.sock";
|
||||||
mode = "0664";
|
mode = "0664";
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
count = 1; # Do not spawn too many processes of this type
|
count = 1; # Do not spawn too many processes of this type
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
milter = yes; # Enable milter mode
|
milter = yes; # Enable milter mode
|
||||||
|
@ -128,10 +158,12 @@ in
|
||||||
workers.controller = {
|
workers.controller = {
|
||||||
type = "controller";
|
type = "controller";
|
||||||
count = 1;
|
count = 1;
|
||||||
bindSockets = [{
|
bindSockets = [
|
||||||
|
{
|
||||||
socket = "/run/rspamd/worker-controller.sock";
|
socket = "/run/rspamd/worker-controller.sock";
|
||||||
mode = "0666";
|
mode = "0666";
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
includes = [ ];
|
includes = [ ];
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
static_dir = "''${WWWDIR}"; # Serve the web UI static assets
|
static_dir = "''${WWWDIR}"; # Serve the web UI static assets
|
||||||
|
@ -203,7 +235,10 @@ in
|
||||||
ProcSubset = "pid";
|
ProcSubset = "pid";
|
||||||
ProtectSystem = "strict";
|
ProtectSystem = "strict";
|
||||||
RemoveIPC = true;
|
RemoveIPC = true;
|
||||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
RestrictAddressFamilies = [
|
||||||
|
"AF_INET"
|
||||||
|
"AF_INET6"
|
||||||
|
];
|
||||||
RestrictNamespaces = true;
|
RestrictNamespaces = true;
|
||||||
RestrictRealtime = true;
|
RestrictRealtime = true;
|
||||||
RestrictSUIDSGID = true;
|
RestrictSUIDSGID = true;
|
||||||
|
@ -237,4 +272,3 @@ in
|
||||||
users.extraUsers.${postfixCfg.user}.extraGroups = [ rspamdCfg.group ];
|
users.extraUsers.${postfixCfg.user}.extraGroups = [ rspamdCfg.group ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,12 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
|
@ -27,9 +32,13 @@ let
|
||||||
[ "acme-finished-${cfg.fqdn}.target" ];
|
[ "acme-finished-${cfg.fqdn}.target" ];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf enable {
|
config =
|
||||||
|
with cfg;
|
||||||
|
lib.mkIf enable {
|
||||||
# Create self signed certificate
|
# Create self signed certificate
|
||||||
systemd.services.mailserver-selfsigned-certificate = lib.mkIf (cfg.certificateScheme == "selfsigned") {
|
systemd.services.mailserver-selfsigned-certificate =
|
||||||
|
lib.mkIf (cfg.certificateScheme == "selfsigned")
|
||||||
|
{
|
||||||
after = [ "local-fs.target" ];
|
after = [ "local-fs.target" ];
|
||||||
script = ''
|
script = ''
|
||||||
# Create certificates if they do not exist yet
|
# Create certificates if they do not exist yet
|
||||||
|
@ -56,12 +65,13 @@ in
|
||||||
systemd.services.dovecot2 = {
|
systemd.services.dovecot2 = {
|
||||||
wants = certificatesDeps;
|
wants = certificatesDeps;
|
||||||
after = certificatesDeps;
|
after = certificatesDeps;
|
||||||
preStart = let
|
preStart =
|
||||||
|
let
|
||||||
directories = lib.strings.escapeShellArgs (
|
directories = lib.strings.escapeShellArgs (
|
||||||
[ mailDirectory ]
|
[ mailDirectory ] ++ lib.optional (cfg.indexDir != null) cfg.indexDir
|
||||||
++ lib.optional (cfg.indexDir != null) cfg.indexDir
|
|
||||||
);
|
);
|
||||||
in ''
|
in
|
||||||
|
''
|
||||||
# Create mail directory and set permissions. See
|
# Create mail directory and set permissions. See
|
||||||
# <https://doc.dovecot.org/main/core/config/shared_mailboxes.html#filesystem-permissions-1>.
|
# <https://doc.dovecot.org/main/core/config/shared_mailboxes.html#filesystem-permissions-1>.
|
||||||
# Prevent world-readable paths, even temporarily.
|
# Prevent world-readable paths, even temporarily.
|
||||||
|
@ -75,11 +85,8 @@ in
|
||||||
# Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work
|
# Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work
|
||||||
systemd.services.postfix = {
|
systemd.services.postfix = {
|
||||||
wants = certificatesDeps;
|
wants = certificatesDeps;
|
||||||
after = [ "dovecot2.service" ]
|
after = [ "dovecot2.service" ] ++ lib.optional cfg.dkimSigning "rspamd.service" ++ certificatesDeps;
|
||||||
++ lib.optional cfg.dkimSigning "rspamd.service"
|
requires = [ "dovecot2.service" ] ++ lib.optional cfg.dkimSigning "rspamd.service";
|
||||||
++ certificatesDeps;
|
|
||||||
requires = [ "dovecot2.service" ]
|
|
||||||
++ lib.optional cfg.dkimSigning "rspamd.service";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,12 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
with config.mailserver;
|
with config.mailserver;
|
||||||
|
|
||||||
|
@ -28,7 +33,6 @@ let
|
||||||
group = vmailGroupName;
|
group = vmailGroupName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
virtualMailUsersActivationScript = pkgs.writeScript "activate-virtual-mail-users" ''
|
virtualMailUsersActivationScript = pkgs.writeScript "activate-virtual-mail-users" ''
|
||||||
#!${pkgs.stdenv.shell}
|
#!${pkgs.stdenv.shell}
|
||||||
|
|
||||||
|
@ -46,8 +50,10 @@ let
|
||||||
|
|
||||||
# Copy user's sieve script to the correct location (if it exists). If it
|
# Copy user's sieve script to the correct location (if it exists). If it
|
||||||
# is null, remove the file.
|
# is null, remove the file.
|
||||||
${lib.concatMapStringsSep "\n" ({ name, sieveScript }:
|
${lib.concatMapStringsSep "\n" (
|
||||||
if lib.isString sieveScript then ''
|
{ name, sieveScript }:
|
||||||
|
if lib.isString sieveScript then
|
||||||
|
''
|
||||||
if (! test -d "${sieveDirectory}/${name}"); then
|
if (! test -d "${sieveDirectory}/${name}"); then
|
||||||
mkdir -p "${sieveDirectory}/${name}"
|
mkdir -p "${sieveDirectory}/${name}"
|
||||||
chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}"
|
chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}"
|
||||||
|
@ -57,17 +63,20 @@ let
|
||||||
${sieveScript}
|
${sieveScript}
|
||||||
EOF
|
EOF
|
||||||
chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}/default.sieve"
|
chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}/default.sieve"
|
||||||
'' else ''
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
if (test -f "${sieveDirectory}/${name}/default.sieve"); then
|
if (test -f "${sieveDirectory}/${name}/default.sieve"); then
|
||||||
rm "${sieveDirectory}/${name}/default.sieve"
|
rm "${sieveDirectory}/${name}/default.sieve"
|
||||||
fi
|
fi
|
||||||
if (test -f "${sieveDirectory}/${name}.svbin"); then
|
if (test -f "${sieveDirectory}/${name}.svbin"); then
|
||||||
rm "${sieveDirectory}/${name}/default.svbin"
|
rm "${sieveDirectory}/${name}/default.svbin"
|
||||||
fi
|
fi
|
||||||
'') (map (user: { inherit (user) name sieveScript; })
|
''
|
||||||
(lib.attrValues loginAccounts))}
|
) (map (user: { inherit (user) name sieveScript; }) (lib.attrValues loginAccounts))}
|
||||||
'';
|
'';
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
config = lib.mkIf enable {
|
config = lib.mkIf enable {
|
||||||
# assert that all accounts provide a password
|
# assert that all accounts provide a password
|
||||||
assertions = map (acct: {
|
assertions = map (acct: {
|
||||||
|
@ -76,15 +85,19 @@ in {
|
||||||
}) (lib.attrValues loginAccounts);
|
}) (lib.attrValues loginAccounts);
|
||||||
|
|
||||||
# warn for accounts that specify both password and file
|
# warn for accounts that specify both password and file
|
||||||
warnings = map
|
warnings =
|
||||||
(acct: "${acct.name} specifies both a password hash and hash file; hash file will be used")
|
map (acct: "${acct.name} specifies both a password hash and hash file; hash file will be used")
|
||||||
(lib.filter
|
(
|
||||||
(acct: (acct.hashedPassword != null && acct.hashedPasswordFile != null))
|
lib.filter (acct: (acct.hashedPassword != null && acct.hashedPasswordFile != null)) (
|
||||||
(lib.attrValues loginAccounts));
|
lib.attrValues loginAccounts
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
# set the vmail gid to a specific value
|
# set the vmail gid to a specific value
|
||||||
users.groups = {
|
users.groups = {
|
||||||
"${vmailGroupName}" = { gid = vmailUID; };
|
"${vmailGroupName}" = {
|
||||||
|
gid = vmailUID;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# define all users
|
# define all users
|
||||||
|
|
11
shell.nix
11
shell.nix
|
@ -1,10 +1,9 @@
|
||||||
(import
|
(import (
|
||||||
(
|
let
|
||||||
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
|
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||||
|
in
|
||||||
fetchTarball {
|
fetchTarball {
|
||||||
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
||||||
sha256 = lock.nodes.flake-compat.locked.narHash;
|
sha256 = lock.nodes.flake-compat.locked.narHash;
|
||||||
}
|
}
|
||||||
)
|
) { src = ./.; }).shellNix
|
||||||
{ src = ./.; }
|
|
||||||
).shellNix
|
|
||||||
|
|
|
@ -24,7 +24,8 @@
|
||||||
name = "clamav";
|
name = "clamav";
|
||||||
|
|
||||||
nodes = {
|
nodes = {
|
||||||
server = { pkgs, ... }:
|
server =
|
||||||
|
{ pkgs, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../default.nix
|
../default.nix
|
||||||
|
@ -70,7 +71,10 @@
|
||||||
mailserver = {
|
mailserver = {
|
||||||
enable = true;
|
enable = true;
|
||||||
fqdn = "mail.example.com";
|
fqdn = "mail.example.com";
|
||||||
domains = [ "example.com" "example2.com" ];
|
domains = [
|
||||||
|
"example.com"
|
||||||
|
"example2.com"
|
||||||
|
];
|
||||||
virusScanning = true;
|
virusScanning = true;
|
||||||
|
|
||||||
loginAccounts = {
|
loginAccounts = {
|
||||||
|
@ -90,7 +94,9 @@
|
||||||
"root/eicar.com.txt".text = "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*";
|
"root/eicar.com.txt".text = "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
client = { nodes, pkgs, ... }: let
|
client =
|
||||||
|
{ nodes, pkgs, ... }:
|
||||||
|
let
|
||||||
serverIP = nodes.server.networking.primaryIPAddress;
|
serverIP = nodes.server.networking.primaryIPAddress;
|
||||||
clientIP = nodes.client.networking.primaryIPAddress;
|
clientIP = nodes.client.networking.primaryIPAddress;
|
||||||
grep-ip = pkgs.writeScriptBin "grep-ip" ''
|
grep-ip = pkgs.writeScriptBin "grep-ip" ''
|
||||||
|
@ -98,13 +104,18 @@
|
||||||
echo grep '${clientIP}' "$@" >&2
|
echo grep '${clientIP}' "$@" >&2
|
||||||
exec grep '${clientIP}' "$@"
|
exec grep '${clientIP}' "$@"
|
||||||
'';
|
'';
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./lib/config.nix
|
./lib/config.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
fetchmail msmtp procmail findutils grep-ip
|
fetchmail
|
||||||
|
msmtp
|
||||||
|
procmail
|
||||||
|
findutils
|
||||||
|
grep-ip
|
||||||
];
|
];
|
||||||
environment.etc = {
|
environment.etc = {
|
||||||
"root/.fetchmailrc" = {
|
"root/.fetchmailrc" = {
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
name = "external";
|
name = "external";
|
||||||
|
|
||||||
nodes = {
|
nodes = {
|
||||||
server = { pkgs, ... }:
|
server =
|
||||||
|
{ pkgs, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../default.nix
|
../default.nix
|
||||||
|
@ -36,12 +37,14 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
mailserver = {
|
mailserver = {
|
||||||
enable = true;
|
enable = true;
|
||||||
debug = true;
|
debug = true;
|
||||||
fqdn = "mail.example.com";
|
fqdn = "mail.example.com";
|
||||||
domains = [ "example.com" "example2.com" ];
|
domains = [
|
||||||
|
"example.com"
|
||||||
|
"example2.com"
|
||||||
|
];
|
||||||
rewriteMessageId = true;
|
rewriteMessageId = true;
|
||||||
dkimKeyBits = 1535;
|
dkimKeyBits = 1535;
|
||||||
dmarcReporting = {
|
dmarcReporting = {
|
||||||
|
@ -71,7 +74,10 @@
|
||||||
|
|
||||||
extraVirtualAliases = {
|
extraVirtualAliases = {
|
||||||
"single-alias@example.com" = "user1@example.com";
|
"single-alias@example.com" = "user1@example.com";
|
||||||
"multi-alias@example.com" = [ "user1@example.com" "user2@example.com" ];
|
"multi-alias@example.com" = [
|
||||||
|
"user1@example.com"
|
||||||
|
"user2@example.com"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
enableImap = true;
|
enableImap = true;
|
||||||
|
@ -80,12 +86,16 @@
|
||||||
enable = true;
|
enable = true;
|
||||||
autoIndex = true;
|
autoIndex = true;
|
||||||
# special use depends on https://github.com/NixOS/nixpkgs/pull/93201
|
# special use depends on https://github.com/NixOS/nixpkgs/pull/93201
|
||||||
autoIndexExclude = [ (if (pkgs.lib.versionAtLeast pkgs.lib.version "21") then "\\Junk" else "Junk") ];
|
autoIndexExclude = [
|
||||||
|
(if (pkgs.lib.versionAtLeast pkgs.lib.version "21") then "\\Junk" else "Junk")
|
||||||
|
];
|
||||||
enforced = "yes";
|
enforced = "yes";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
client = { nodes, pkgs, ... }: let
|
client =
|
||||||
|
{ nodes, pkgs, ... }:
|
||||||
|
let
|
||||||
serverIP = nodes.server.networking.primaryIPAddress;
|
serverIP = nodes.server.networking.primaryIPAddress;
|
||||||
clientIP = nodes.client.networking.primaryIPAddress;
|
clientIP = nodes.client.networking.primaryIPAddress;
|
||||||
grep-ip = pkgs.writeScriptBin "grep-ip" ''
|
grep-ip = pkgs.writeScriptBin "grep-ip" ''
|
||||||
|
@ -172,12 +182,21 @@
|
||||||
assert needle in repr(response)
|
assert needle in repr(response)
|
||||||
imap.close()
|
imap.close()
|
||||||
'';
|
'';
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./lib/config.nix
|
./lib/config.nix
|
||||||
];
|
];
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
fetchmail msmtp procmail findutils grep-ip check-mail-id test-imap-spam test-imap-ham search
|
fetchmail
|
||||||
|
msmtp
|
||||||
|
procmail
|
||||||
|
findutils
|
||||||
|
grep-ip
|
||||||
|
check-mail-id
|
||||||
|
test-imap-spam
|
||||||
|
test-imap-ham
|
||||||
|
search
|
||||||
];
|
];
|
||||||
environment.etc = {
|
environment.etc = {
|
||||||
"root/.fetchmailrc" = {
|
"root/.fetchmailrc" = {
|
||||||
|
|
|
@ -30,9 +30,14 @@ let
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
hashPassword = password: pkgs.runCommand
|
hashPassword =
|
||||||
"password-${password}-hashed"
|
password:
|
||||||
{ buildInputs = [ pkgs.mkpasswd ]; inherit password; } ''
|
pkgs.runCommand "password-${password}-hashed"
|
||||||
|
{
|
||||||
|
buildInputs = [ pkgs.mkpasswd ];
|
||||||
|
inherit password;
|
||||||
|
}
|
||||||
|
''
|
||||||
mkpasswd -sm bcrypt <<<"$password" > $out
|
mkpasswd -sm bcrypt <<<"$password" > $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
@ -43,7 +48,9 @@ in
|
||||||
name = "internal";
|
name = "internal";
|
||||||
|
|
||||||
nodes = {
|
nodes = {
|
||||||
machine = { pkgs, ... }: {
|
machine =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./../default.nix
|
./../default.nix
|
||||||
./lib/config.nix
|
./lib/config.nix
|
||||||
|
@ -51,11 +58,13 @@ in
|
||||||
|
|
||||||
virtualisation.memorySize = 1024;
|
virtualisation.memorySize = 1024;
|
||||||
|
|
||||||
environment.systemPackages = [
|
environment.systemPackages =
|
||||||
|
[
|
||||||
(pkgs.writeScriptBin "mail-check" ''
|
(pkgs.writeScriptBin "mail-check" ''
|
||||||
${pkgs.python3}/bin/python ${../scripts/mail-check.py} $@
|
${pkgs.python3}/bin/python ${../scripts/mail-check.py} $@
|
||||||
'')
|
'')
|
||||||
] ++ (with pkgs; [
|
]
|
||||||
|
++ (with pkgs; [
|
||||||
curl
|
curl
|
||||||
openssl
|
openssl
|
||||||
netcat
|
netcat
|
||||||
|
@ -64,7 +73,10 @@ in
|
||||||
mailserver = {
|
mailserver = {
|
||||||
enable = true;
|
enable = true;
|
||||||
fqdn = "mail.example.com";
|
fqdn = "mail.example.com";
|
||||||
domains = [ "example.com" "domain.com" ];
|
domains = [
|
||||||
|
"example.com"
|
||||||
|
"domain.com"
|
||||||
|
];
|
||||||
localDnsResolver = false;
|
localDnsResolver = false;
|
||||||
|
|
||||||
loginAccounts = {
|
loginAccounts = {
|
||||||
|
|
|
@ -7,7 +7,9 @@ in
|
||||||
name = "ldap";
|
name = "ldap";
|
||||||
|
|
||||||
nodes = {
|
nodes = {
|
||||||
machine = { pkgs, ... }: {
|
machine =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./../default.nix
|
./../default.nix
|
||||||
./lib/config.nix
|
./lib/config.nix
|
||||||
|
@ -23,7 +25,8 @@ in
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
(pkgs.writeScriptBin "mail-check" ''
|
(pkgs.writeScriptBin "mail-check" ''
|
||||||
${pkgs.python3}/bin/python ${../scripts/mail-check.py} $@
|
${pkgs.python3}/bin/python ${../scripts/mail-check.py} $@
|
||||||
'')];
|
'')
|
||||||
|
];
|
||||||
|
|
||||||
environment.etc.bind-password.text = bindPassword;
|
environment.etc.bind-password.text = bindPassword;
|
||||||
|
|
||||||
|
|
|
@ -6,16 +6,23 @@
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
hashPassword = password: pkgs.runCommand
|
hashPassword =
|
||||||
"password-${password}-hashed"
|
password:
|
||||||
{ buildInputs = [ pkgs.mkpasswd ]; inherit password; }
|
pkgs.runCommand "password-${password}-hashed"
|
||||||
|
{
|
||||||
|
buildInputs = [ pkgs.mkpasswd ];
|
||||||
|
inherit password;
|
||||||
|
}
|
||||||
''
|
''
|
||||||
mkpasswd -sm bcrypt <<<"$password" > $out
|
mkpasswd -sm bcrypt <<<"$password" > $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
password = pkgs.writeText "password" "password";
|
password = pkgs.writeText "password" "password";
|
||||||
|
|
||||||
domainGenerator = domain: { pkgs, ... }: {
|
domainGenerator =
|
||||||
|
domain:
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../default.nix
|
../default.nix
|
||||||
./lib/config.nix
|
./lib/config.nix
|
||||||
|
@ -37,7 +44,10 @@ let
|
||||||
};
|
};
|
||||||
services.dnsmasq = {
|
services.dnsmasq = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings.mx-host = [ "domain1.com,domain1,10" "domain2.com,domain2,10" ];
|
settings.mx-host = [
|
||||||
|
"domain1.com,domain1,10"
|
||||||
|
"domain2.com,domain2,10"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,22 +57,33 @@ in
|
||||||
name = "multiple";
|
name = "multiple";
|
||||||
|
|
||||||
nodes = {
|
nodes = {
|
||||||
domain1 = {...}: {
|
domain1 =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../default.nix
|
../default.nix
|
||||||
(domainGenerator "domain1.com")
|
(domainGenerator "domain1.com")
|
||||||
];
|
];
|
||||||
mailserver.forwards = {
|
mailserver.forwards = {
|
||||||
"non-local@domain1.com" = ["user@domain2.com" "user@domain1.com"];
|
"non-local@domain1.com" = [
|
||||||
"non@domain1.com" = ["user@domain2.com" "user@domain1.com"];
|
"user@domain2.com"
|
||||||
|
"user@domain1.com"
|
||||||
|
];
|
||||||
|
"non@domain1.com" = [
|
||||||
|
"user@domain2.com"
|
||||||
|
"user@domain1.com"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
domain2 = domainGenerator "domain2.com";
|
domain2 = domainGenerator "domain2.com";
|
||||||
client = { pkgs, ... }: {
|
client =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
(pkgs.writeScriptBin "mail-check" ''
|
(pkgs.writeScriptBin "mail-check" ''
|
||||||
${pkgs.python3}/bin/python ${../scripts/mail-check.py} $@
|
${pkgs.python3}/bin/python ${../scripts/mail-check.py} $@
|
||||||
'')];
|
'')
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
testScript = ''
|
testScript = ''
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue