style: format tree

This commit is contained in:
nhnn
2025-06-18 19:53:44 +03:00
parent ed990906bd
commit 86233cac27
44 changed files with 2523 additions and 2253 deletions

View File

@@ -1,8 +1,14 @@
{ mailserver-service-account-name
, mailserver-service-account-token-name
, mailserver-service-account-token-fp
{
mailserver-service-account-name,
mailserver-service-account-token-name,
mailserver-service-account-token-fp,
}:
{ config, lib, pkgs, ... }@nixos-args:
{
config,
lib,
pkgs,
...
}@nixos-args:
let
inherit (import ./common.nix nixos-args)
appendSetting
@@ -17,67 +23,70 @@ let
keysPath = auth-passthru.keys-path;
# create service account token, needed for LDAP
kanidmExecStartPostScript = pkgs.writeShellScript
"mailserver-kanidm-ExecStartPost-script.sh"
''
export HOME=$RUNTIME_DIRECTORY/client_home
readonly KANIDM="${pkgs.kanidm}/bin/kanidm"
kanidmExecStartPostScript = pkgs.writeShellScript "mailserver-kanidm-ExecStartPost-script.sh" ''
export HOME=$RUNTIME_DIRECTORY/client_home
readonly KANIDM="${pkgs.kanidm}/bin/kanidm"
# get Kanidm service account for mailserver
KANIDM_SERVICE_ACCOUNT="$($KANIDM service-account list --name idm_admin | grep -E "^name: ${mailserver-service-account-name}$")"
echo KANIDM_SERVICE_ACCOUNT: "$KANIDM_SERVICE_ACCOUNT"
if [ -n "$KANIDM_SERVICE_ACCOUNT" ]
then
echo "kanidm service account \"${mailserver-service-account-name}\" is found"
else
echo "kanidm service account \"${mailserver-service-account-name}\" is not found"
echo "creating new kanidm service account \"${mailserver-service-account-name}\""
if $KANIDM service-account create --name idm_admin ${mailserver-service-account-name} ${mailserver-service-account-name} idm_admin
then
"kanidm service account \"${mailserver-service-account-name}\" created"
else
echo "error: cannot create kanidm service account \"${mailserver-service-account-name}\""
exit 1
fi
fi
# get Kanidm service account for mailserver
KANIDM_SERVICE_ACCOUNT="$($KANIDM service-account list --name idm_admin | grep -E "^name: ${mailserver-service-account-name}$")"
echo KANIDM_SERVICE_ACCOUNT: "$KANIDM_SERVICE_ACCOUNT"
if [ -n "$KANIDM_SERVICE_ACCOUNT" ]
then
echo "kanidm service account \"${mailserver-service-account-name}\" is found"
else
echo "kanidm service account \"${mailserver-service-account-name}\" is not found"
echo "creating new kanidm service account \"${mailserver-service-account-name}\""
if $KANIDM service-account create --name idm_admin ${mailserver-service-account-name} ${mailserver-service-account-name} idm_admin
then
"kanidm service account \"${mailserver-service-account-name}\" created"
else
echo "error: cannot create kanidm service account \"${mailserver-service-account-name}\""
exit 1
fi
fi
# add Kanidm service account to `idm_mail_servers` group
$KANIDM group add-members idm_mail_servers ${mailserver-service-account-name}
# add Kanidm service account to `idm_mail_servers` group
$KANIDM group add-members idm_mail_servers ${mailserver-service-account-name}
# create a new read-only token for mailserver
if ! KANIDM_SERVICE_ACCOUNT_TOKEN_JSON="$($KANIDM service-account api-token generate --name idm_admin ${mailserver-service-account-name} ${mailserver-service-account-token-name} --output json)"
then
echo "error: kanidm CLI returns an error when trying to generate service-account api-token"
exit 1
fi
if ! KANIDM_SERVICE_ACCOUNT_TOKEN="$(echo "$KANIDM_SERVICE_ACCOUNT_TOKEN_JSON" | ${lib.getExe pkgs.jq} -r .result)"
then
echo "error: cannot get service-account API token from JSON"
exit 1
fi
# create a new read-only token for mailserver
if ! KANIDM_SERVICE_ACCOUNT_TOKEN_JSON="$($KANIDM service-account api-token generate --name idm_admin ${mailserver-service-account-name} ${mailserver-service-account-token-name} --output json)"
then
echo "error: kanidm CLI returns an error when trying to generate service-account api-token"
exit 1
fi
if ! KANIDM_SERVICE_ACCOUNT_TOKEN="$(echo "$KANIDM_SERVICE_ACCOUNT_TOKEN_JSON" | ${lib.getExe pkgs.jq} -r .result)"
then
echo "error: cannot get service-account API token from JSON"
exit 1
fi
if ! install --mode=640 \
<(printf "%s" "$KANIDM_SERVICE_ACCOUNT_TOKEN") \
${mailserver-service-account-token-fp}
then
echo "error: cannot write token to \"${mailserver-service-account-token-fp}\""
exit 1
fi
'';
if ! install --mode=640 \
<(printf "%s" "$KANIDM_SERVICE_ACCOUNT_TOKEN") \
${mailserver-service-account-token-fp}
then
echo "error: cannot write token to \"${mailserver-service-account-token-fp}\""
exit 1
fi
'';
ldapConfFile = "/run/${runtime-folder}/dovecot-ldap.conf.ext";
mkLdapSearchScope = scope: (
if scope == "sub" then "subtree"
else if scope == "one" then "onelevel"
else scope
);
mkLdapSearchScope =
scope:
(
if scope == "sub" then
"subtree"
else if scope == "one" then
"onelevel"
else
scope
);
dovecot-ldap-config = pkgs.writeTextFile {
name = "dovecot-ldap.conf.ext.template";
text = ''
ldap_version = 3
uris = ${lib.concatStringsSep " " config.mailserver.ldap.uris}
${lib.optionalString config.mailserver.ldap.startTls ''
tls = yes
tls = yes
''}
tls_require_cert = hard
tls_ca_cert_file = ${config.mailserver.ldap.tlsCAFile}
@@ -87,7 +96,7 @@ let
base = ${config.mailserver.ldap.searchBase}
scope = ${mkLdapSearchScope config.mailserver.ldap.searchScope}
${lib.optionalString (config.mailserver.ldap.dovecot.userAttrs != null) ''
user_attrs = ${config.mailserver.ldap.dovecot.userAttrs}
user_attrs = ${config.mailserver.ldap.dovecot.userAttrs}
''}
user_filter = ${config.mailserver.ldap.dovecot.userFilter}
'';
@@ -101,10 +110,8 @@ let
destination = ldapConfFile;
};
oauth-client-id = "mailserver";
oauth-client-secret-fp =
"${keysPath}/${group}/kanidm-oauth-client-secret";
oauth-secret-ExecStartPreScript = pkgs.writeShellScript
"${oauth-client-id}-kanidm-ExecStartPre-script.sh" ''
oauth-client-secret-fp = "${keysPath}/${group}/kanidm-oauth-client-secret";
oauth-secret-ExecStartPreScript = pkgs.writeShellScript "${oauth-client-id}-kanidm-ExecStartPre-script.sh" ''
set -o xtrace
[ -f "${oauth-client-secret-fp}" ] || \
"${lib.getExe pkgs.openssl}" rand -base64 32 | tr "\n:@/+=" "012345" > "${oauth-client-secret-fp}"
@@ -122,8 +129,8 @@ let
openid_configuration_url = ${auth-passthru.oauth2-discovery-url oauth-client-id}
debug = "no"
'';
prefix = ''introspection_url = "'' +
(auth-passthru.oauth2-introspection-url-prefix oauth-client-id);
prefix =
''introspection_url = "'' + (auth-passthru.oauth2-introspection-url-prefix oauth-client-id);
suffix = auth-passthru.oauth2-introspection-url-postfix + ''"'';
passwordFile = oauth-client-secret-fp;
destination = dovecot-oauth2-conf-fp;

View File

@@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }@nixos-args:
{
config,
lib,
pkgs,
...
}@nixos-args:
let
inherit (import ./common.nix nixos-args)
appendSetting
@@ -9,8 +14,7 @@ let
cfg = config.mailserver;
ldapSenderLoginMapFile = "/run/postfix/ldap-sender-login-map.cf";
submissionOptions.smtpd_sender_login_maps =
lib.mkForce "hash:/etc/postfix/vaccounts,ldap:${ldapSenderLoginMapFile}";
submissionOptions.smtpd_sender_login_maps = lib.mkForce "hash:/etc/postfix/vaccounts,ldap:${ldapSenderLoginMapFile}";
commonLdapConfig = ''
server_host = ${lib.concatStringsSep " " cfg.ldap.uris}
start_tls = ${if cfg.ldap.startTls then "yes" else "no"}
@@ -61,8 +65,10 @@ in
${appendPwdInVirtualMailboxMap}
${appendPwdInSenderLoginMap}
'';
restartTriggers =
[ appendPwdInVirtualMailboxMap appendPwdInSenderLoginMap ];
restartTriggers = [
appendPwdInVirtualMailboxMap
appendPwdInSenderLoginMap
];
wants = [ auth-passthru.oauth2-systemd-service ];
after = [ auth-passthru.oauth2-systemd-service ];
};

View File

@@ -4,11 +4,17 @@ rec {
domain = config.selfprivacy.domain;
group = "dovecot2";
is-auth-enabled =
config.selfprivacy.modules.simple-nixos-mailserver.enableSso
&& config.selfprivacy.sso.enable;
config.selfprivacy.modules.simple-nixos-mailserver.enableSso && config.selfprivacy.sso.enable;
appendSetting =
{ name, file, prefix, suffix ? "", passwordFile, destination }:
{
name,
file,
prefix,
suffix ? "",
passwordFile,
destination,
}:
pkgs.writeScript "append-ldap-bind-pwd-in-${name}" ''
#!${pkgs.stdenv.shell}
set -euo pipefail

View File

@@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }@nixos-args:
{
config,
lib,
pkgs,
...
}@nixos-args:
let
sp = config.selfprivacy;
@@ -11,26 +16,22 @@ let
mailserver-service-account = {
mailserver-service-account-name = "sp.mailserver.service-account";
mailserver-service-account-token-name = "mailserver-service-account-token";
mailserver-service-account-token-fp =
"/run/keys/${group}/kanidm-service-account-token"; # FIXME sync with auth module
mailserver-service-account-token-fp = "/run/keys/${group}/kanidm-service-account-token"; # FIXME sync with auth module
};
in
lib.mkIf sp.modules.simple-nixos-mailserver.enable (lib.mkMerge [
{
assertions = [
{
assertion =
config.selfprivacy.modules.simple-nixos-mailserver.enableSso
-> config.selfprivacy.sso.enable;
message =
"SSO cannot be enabled for Mailserver when SSO is disabled globally.";
}
];
fileSystems = lib.mkIf sp.useBinds
{
lib.mkIf sp.modules.simple-nixos-mailserver.enable (
lib.mkMerge [
{
assertions = [
{
assertion =
config.selfprivacy.modules.simple-nixos-mailserver.enableSso -> config.selfprivacy.sso.enable;
message = "SSO cannot be enabled for Mailserver when SSO is disabled globally.";
}
];
fileSystems = lib.mkIf sp.useBinds {
"/var/vmail" = {
device =
"/volumes/${sp.modules.simple-nixos-mailserver.location}/vmail";
device = "/volumes/${sp.modules.simple-nixos-mailserver.location}/vmail";
options = [
"bind"
"x-systemd.required-by=postfix.service"
@@ -38,8 +39,7 @@ lib.mkIf sp.modules.simple-nixos-mailserver.enable (lib.mkMerge [
];
};
"/var/sieve" = {
device =
"/volumes/${sp.modules.simple-nixos-mailserver.location}/sieve";
device = "/volumes/${sp.modules.simple-nixos-mailserver.location}/sieve";
options = [
"bind"
"x-systemd.required-by=dovecot2.service"
@@ -48,114 +48,120 @@ lib.mkIf sp.modules.simple-nixos-mailserver.enable (lib.mkMerge [
};
};
users.users = {
virtualMail = {
isNormalUser = false;
};
};
users.groups.acmereceivers.members = [ "dovecot2" "postfix" "virtualMail" ];
mailserver = {
enable = true;
fqdn = sp.domain;
domains = [ sp.domain ];
localDnsResolver = false;
# A list of all login accounts. To create the password hashes, use
# mkpasswd -m sha-512 "super secret password"
loginAccounts = ({
"${sp.username}@${sp.domain}" = {
hashedPassword = sp.hashedMasterPassword;
sieveScript = ''
require ["fileinto", "mailbox"];
if header :contains "Chat-Version" "1.0"
{
fileinto :create "DeltaChat";
stop;
}
'';
users.users = {
virtualMail = {
isNormalUser = false;
};
} // builtins.listToAttrs (builtins.map
(user: {
name = "${user.username}@${sp.domain}";
value = {
hashedPassword = user.hashedPassword;
sieveScript = ''
require ["fileinto", "mailbox"];
if header :contains "Chat-Version" "1.0"
{
fileinto :create "DeltaChat";
stop;
}
'';
};
})
sp.users));
extraVirtualAliases = {
"admin@${sp.domain}" = "${sp.username}@${sp.domain}";
};
certificateScheme = "manual";
certificateFile = "/var/lib/acme/root-${sp.domain}/fullchain.pem";
keyFile = "/var/lib/acme/root-${sp.domain}/key.pem";
users.groups.acmereceivers.members = [
"dovecot2"
"postfix"
"virtualMail"
];
# Enable IMAP and POP3
enableImap = true;
enableImapSsl = true;
enablePop3 = false;
enablePop3Ssl = false;
dkimSelector = "selector";
mailserver = {
enable = true;
fqdn = sp.domain;
domains = [ sp.domain ];
localDnsResolver = false;
# Enable the ManageSieve protocol
enableManageSieve = true;
# A list of all login accounts. To create the password hashes, use
# mkpasswd -m sha-512 "super secret password"
loginAccounts = (
{
"${sp.username}@${sp.domain}" = {
hashedPassword = sp.hashedMasterPassword;
sieveScript = ''
require ["fileinto", "mailbox"];
if header :contains "Chat-Version" "1.0"
{
fileinto :create "DeltaChat";
stop;
}
'';
};
}
// builtins.listToAttrs (
builtins.map (user: {
name = "${user.username}@${sp.domain}";
value = {
hashedPassword = user.hashedPassword;
sieveScript = ''
require ["fileinto", "mailbox"];
if header :contains "Chat-Version" "1.0"
{
fileinto :create "DeltaChat";
stop;
}
'';
};
}) sp.users
)
);
virusScanning = false;
extraVirtualAliases = {
"admin@${sp.domain}" = "${sp.username}@${sp.domain}";
};
mailDirectory = "/var/vmail";
};
certificateScheme = "manual";
certificateFile = "/var/lib/acme/root-${sp.domain}/fullchain.pem";
keyFile = "/var/lib/acme/root-${sp.domain}/key.pem";
systemd = {
services = {
dovecot2.serviceConfig.Slice = "simple_nixos_mailserver.slice";
postfix.serviceConfig.Slice = "simple_nixos_mailserver.slice";
rspamd.serviceConfig.Slice = "simple_nixos_mailserver.slice";
redis-rspamd.serviceConfig.Slice = "simple_nixos_mailserver.slice";
opendkim.serviceConfig.Slice = "simple_nixos_mailserver.slice";
# Enable IMAP and POP3
enableImap = true;
enableImapSsl = true;
enablePop3 = false;
enablePop3Ssl = false;
dkimSelector = "selector";
# Enable the ManageSieve protocol
enableManageSieve = true;
virusScanning = false;
mailDirectory = "/var/vmail";
};
slices."simple_nixos_mailserver" = {
name = "simple_nixos_mailserver.slice";
description = "Simple NixOS Mailserver service slice";
systemd = {
services = {
dovecot2.serviceConfig.Slice = "simple_nixos_mailserver.slice";
postfix.serviceConfig.Slice = "simple_nixos_mailserver.slice";
rspamd.serviceConfig.Slice = "simple_nixos_mailserver.slice";
redis-rspamd.serviceConfig.Slice = "simple_nixos_mailserver.slice";
opendkim.serviceConfig.Slice = "simple_nixos_mailserver.slice";
};
slices."simple_nixos_mailserver" = {
name = "simple_nixos_mailserver.slice";
description = "Simple NixOS Mailserver service slice";
};
};
};
}
# the following parts are active only when "auth" module is enabled
(lib.mkIf is-auth-enabled {
mailserver = {
extraVirtualAliases = lib.mkForce { };
loginAccounts = lib.mkForce { };
# LDAP is needed for Postfix to query Kanidm about email address ownership.
# LDAP is needed for Dovecot also.
ldap = {
# false; otherwise, simple-nixos-mailserver enables auth via LDAP
enable = false;
}
# the following parts are active only when "auth" module is enabled
(lib.mkIf is-auth-enabled {
mailserver = {
extraVirtualAliases = lib.mkForce { };
loginAccounts = lib.mkForce { };
# LDAP is needed for Postfix to query Kanidm about email address ownership.
# LDAP is needed for Dovecot also.
ldap = {
# false; otherwise, simple-nixos-mailserver enables auth via LDAP
enable = false;
# bind.dn = "uid=mail,ou=persons," + ldap_base_dn;
bind.dn = "dn=token";
# TODO change in this file should trigger system restart dovecot
bind.passwordFile =
mailserver-service-account.mailserver-service-account-token-fp;
# bind.dn = "uid=mail,ou=persons," + ldap_base_dn;
bind.dn = "dn=token";
# TODO change in this file should trigger system restart dovecot
bind.passwordFile = mailserver-service-account.mailserver-service-account-token-fp;
# searchBase = "ou=persons," + ldap_base_dn;
searchBase = auth-passthru.ldap-base-dn; # TODO refine this
# searchBase = "ou=persons," + ldap_base_dn;
searchBase = auth-passthru.ldap-base-dn; # TODO refine this
# NOTE: 127.0.0.1 instead of localhost doesn't work (maybe because of TLS)
uris = [ "ldaps://localhost:${toString auth-passthru.ldap-port}" ];
# NOTE: 127.0.0.1 instead of localhost doesn't work (maybe because of TLS)
uris = [ "ldaps://localhost:${toString auth-passthru.ldap-port}" ];
};
};
};
})
(lib.mkIf is-auth-enabled
(import ./auth-dovecot.nix mailserver-service-account nixos-args))
(lib.mkIf is-auth-enabled (import ./auth-postfix.nix nixos-args))
])
})
(lib.mkIf is-auth-enabled (import ./auth-dovecot.nix mailserver-service-account nixos-args))
(lib.mkIf is-auth-enabled (import ./auth-postfix.nix nixos-args))
]
)

View File

@@ -1,43 +1,45 @@
{
description = "PoC SP module for the simple-nixos-mailserver";
inputs.mailserver.url =
gitlab:simple-nixos-mailserver/nixos-mailserver;
inputs.mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver";
outputs = { self, mailserver }: {
nixosModules.default = _: {
imports = [
mailserver.nixosModules.default
./options.nix
./config.nix
];
};
configPathsNeeded =
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
meta = { lib, ... }: {
spModuleSchemaVersion = 1;
id = "simple-nixos-mailserver";
name = "Mail Server";
description = "E-Mail for company and family.";
svgIcon = builtins.readFile ./icon.svg;
isMovable = true;
isRequired = true;
canBeBackedUp = true;
backupDescription = "Mail boxes and filters.";
systemdServices = [
"dovecot2.service"
"postfix.service"
];
user = "virtualMail";
folders = [
"/var/vmail"
"/var/sieve"
];
supportLevel = "normal";
};
outputs =
{ self, mailserver }:
{
nixosModules.default = _: {
imports = [
mailserver.nixosModules.default
./options.nix
./config.nix
];
};
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
meta =
{ lib, ... }:
{
spModuleSchemaVersion = 1;
id = "simple-nixos-mailserver";
name = "Mail Server";
description = "E-Mail for company and family.";
svgIcon = builtins.readFile ./icon.svg;
isMovable = true;
isRequired = true;
canBeBackedUp = true;
backupDescription = "Mail boxes and filters.";
systemdServices = [
"dovecot2.service"
"postfix.service"
];
user = "virtualMail";
folders = [
"/var/vmail"
"/var/sieve"
];
supportLevel = "normal";
};
# TODO generate json docs from module? something like:
# nix eval --impure --expr 'let flake = builtins.getFlake (builtins.toPath ./.); pkgs = flake.inputs.mailserver.inputs.nixpkgs.legacyPackages.x86_64-linux; in (pkgs.nixosOptionsDoc { inherit (pkgs.lib.evalModules { modules = [ flake.nixosModules.default ]; }) options; }).optionsJSON'
# (doesn't work because of `assertions`)
};
# TODO generate json docs from module? something like:
# nix eval --impure --expr 'let flake = builtins.getFlake (builtins.toPath ./.); pkgs = flake.inputs.mailserver.inputs.nixpkgs.legacyPackages.x86_64-linux; in (pkgs.nixosOptionsDoc { inherit (pkgs.lib.evalModules { modules = [ flake.nixosModules.default ]; }) options; }).optionsJSON'
# (doesn't work because of `assertions`)
};
}

View File

@@ -1,31 +1,37 @@
{ lib, ... }:
{
options.selfprivacy.modules.simple-nixos-mailserver = {
enable = (lib.mkOption {
default = false;
type = lib.types.bool;
description = "Enable mail server";
}) // {
meta = {
type = "enable";
enable =
(lib.mkOption {
default = false;
type = lib.types.bool;
description = "Enable mail server";
})
// {
meta = {
type = "enable";
};
};
};
location = (lib.mkOption {
type = lib.types.str;
description = "Location";
}) // {
meta = {
type = "location";
location =
(lib.mkOption {
type = lib.types.str;
description = "Location";
})
// {
meta = {
type = "location";
};
};
};
enableSso = (lib.mkOption {
default = true;
type = lib.types.bool;
description = "Enable SSO for mail server";
}) // {
meta = {
type = "enable";
enableSso =
(lib.mkOption {
default = true;
type = lib.types.bool;
description = "Enable SSO for mail server";
})
// {
meta = {
type = "enable";
};
};
};
};
}