Compare commits

...

3 commits

Author SHA1 Message Date
9af8f29b48
Revert "Attempt at using stalwart again"
Some checks failed
/ dev-shell (push) Successful in 41s
/ rust-packages (push) Successful in 47s
/ check (push) Failing after 58s
/ terraform-providers (push) Successful in 1m12s
/ systems (push) Successful in 4m0s
This reverts commit 2d3e02ad78.
2025-07-30 11:11:17 +02:00
ad84cfae7e
Revert "Final attempt at getting stalwart working before revert"
This reverts commit fbcd590bfe.
2025-07-30 11:11:14 +02:00
fbcd590bfe
Final attempt at getting stalwart working before revert
Non working parts:

1. OIDC login, stalwart assumes the entire token is base64 encoded,
   which it is not.
2. Apparently there's no support for mixed directories, allowing both
   logins from ldap and from internal database. I want this in order
   to support accounts for services as well as persons.
2025-07-30 11:08:00 +02:00
11 changed files with 162 additions and 649 deletions

View file

@ -1,40 +1,9 @@
{ lib, config, ... }: { lib, config, ... }:
let let
cfg = config.khscodes.infrastructure.mailserver; cfg = config.khscodes.infrastructure.mailserver;
fqdn = config.khscodes.networking.fqdn;
acmeDir = config.security.acme.certs.${fqdn}.directory;
# extraDomainNames = lib.lists.filter (d: d != fqdn) (lib.lists.map (d: "mx.${d}") cfg.domains);
extraDomainNames = [ ];
user = "stalwart-mail";
in in
{ {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
khscodes.services.nginx.virtualHosts."${fqdn}" = { khscodes.services.nginx.virtualHosts."${config.khscodes.networking.fqdn}" = { };
locations."/" = {
proxyPass = "http://127.0.0.1:8080";
proxyWebsockets = true;
recommendedProxySettings = true;
};
};
services.stalwart-mail.settings = {
certificate.default = {
cert = "%{file:${acmeDir}/fullchain.pem}%";
private-key = "%{file:${config.security.acme.certs.${fqdn}.directory}/key.pem}%";
};
};
security.acme.certs.${fqdn} = {
inherit extraDomainNames;
postRun = ''
systemctl restart stalwart-mail.service
'';
};
systemd.services.stalwart-mail = {
after = [ "acme-selfsigned-${fqdn}.service" ];
wants = [ "acme-finished-${fqdn}.service" ];
serviceConfig.ReadOnlyPaths = [ acmeDir ];
};
users.users.${user}.extraGroups = [
config.security.acme.certs.${fqdn}.group
];
}; };
} }

View file

@ -1,67 +0,0 @@
{ config, lib, ... }:
let
cfg = config.khscodes.infrastructure.mailserver;
in
{
config = lib.mkIf cfg.enable {
khscodes.infrastructure.vault-server-approle.policy = {
# TODO: Make this configurable
"mx.kaareskovgaard.net/data/logins/admin" = {
capabilities = [ "read" ];
};
};
khscodes.services.vault-agent.templates = [
{
contents = ''
{{- with secret "mx.kaareskovgaard.net/data/logins/admin" -}}
{{ .Data.data.hashed_password }}
{{- end -}}
'';
destination = "/run/secret/stalwart/users/admin";
owner = "stalwart-mail";
group = "stalwart-mail";
restartUnits = [ "stalwart-mail.service" ];
}
];
systemd.services.stalwart-mail = {
unitConfig.ConditionPathExists = [ "/run/secret/stalwart/users/admin" ];
serviceConfig.ReadOnlyPaths = [ "/run/secret/stalwart/users/admin" ];
};
services.stalwart-mail = {
settings = {
authentication.fallback-admin = {
user = "admin";
secret = "%{file:/run/secret/stalwart/users/admin}%";
};
};
};
khscodes.infrastructure.provisioning.pre.modules = [
{
terraform.required_providers.random = {
source = "hashicorp/random";
version = "3.7.2";
};
provider.random = { };
resource.random_password.stalwart_fallback_admin_password = {
length = 48;
numeric = true;
lower = true;
upper = true;
special = false;
};
resource.vault_kv_secret_v2.stalwart_fallback_admin_password = {
mount = "mx.kaareskovgaard.net";
name = "logins/admin";
data_json = ''
{
"hashed_password": ''${ jsonencode(resource.random_password.stalwart_fallback_admin_password.bcrypt_hash) },
"password": ''${ jsonencode(resource.random_password.stalwart_fallback_admin_password.result) }
}
'';
};
}
];
};
}

View file

@ -1,7 +1,7 @@
{ {
config, config,
lib, lib,
pkgs, inputs,
... ...
}: }:
let let
@ -17,8 +17,7 @@ in
}; };
}; };
imports = [ imports = [
# inputs.simple-nixos-mailserver.nixosModules.mailserver inputs.simple-nixos-mailserver.nixosModules.mailserver
./admin_password.nix
./acme.nix ./acme.nix
./dmarc.nix ./dmarc.nix
./dane.nix ./dane.nix
@ -29,58 +28,8 @@ in
./prometheus.nix ./prometheus.nix
./openid-connect.nix ./openid-connect.nix
./ldap.nix ./ldap.nix
./package/nixos-module.nix
]; ];
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
services.stalwart-mail = {
enable = true;
package = pkgs.callPackage ./package/package.nix { };
settings = {
http = {
url = "https://${fqdn}";
use-x-forwarded = true;
};
server = {
hostname = fqdn;
tls = {
enable = true;
certificate = "default";
implicit = true;
ignore-client-order = true;
};
listener = {
smtp = {
protocol = "smtp";
bind = "[::]:25";
};
submissions = {
bind = "[::]:465";
protocol = "smtp";
tls.implicit = true;
};
imaps = {
bind = "[::]:993";
protocol = "imap";
tls.implicit = true;
};
jmap = {
bind = "[::]:8080";
url = "https://${fqdn}";
protocol = "jmap";
};
management = {
bind = "[::]:8080";
protocol = "http";
};
};
};
lookup.default = {
hostname = fqdn;
domain = "kaareskovgaard.net";
};
spam-filter.resource = "${config.services.stalwart-mail.package.spam-filter}/spam-filter.toml";
};
};
# TODO: Include a similiar rule for openstack # TODO: Include a similiar rule for openstack
khscodes.infrastructure.hetzner-instance.extraFirewallRules = [ khscodes.infrastructure.hetzner-instance.extraFirewallRules = [
{ {
@ -129,6 +78,38 @@ in
} }
) )
]; ];
mailserver = {
enable = true;
enableImap = false;
enableImapSsl = true;
enableSubmission = false;
enableSubmissionSsl = true;
fqdn = config.khscodes.networking.fqdn;
useUTF8FolderNames = true;
domains = cfg.domains;
certificateScheme = "acme";
};
services.fail2ban.jails = {
postfix = {
settings = {
enabled = true;
mode = "aggressive";
findtime = 600;
bantime = "1d";
maxretry = 3;
};
};
dovecot = {
settings = {
enabled = true;
mode = "aggressive";
findtime = 600;
bantime = "1d";
maxretry = 3;
};
};
};
networking.firewall.allowedTCPPorts = [ networking.firewall.allowedTCPPorts = [
25 25
465 465

View file

@ -9,82 +9,28 @@ let
publicKeyEnd = ''"-----END PUBLIC KEY-----\n"''; publicKeyEnd = ''"-----END PUBLIC KEY-----\n"'';
rsaKeyPath = domain: "/run/secret/dkim/${domain}.snm_rsa.key"; rsaKeyPath = domain: "/run/secret/dkim/${domain}.snm_rsa.key";
ed25519KeyPath = domain: "/run/secret/dkim/${domain}.snm_ed25519.key"; ed25519KeyPath = domain: "/run/secret/dkim/${domain}.snm_ed25519.key";
keyFiles = lib.lists.flatten ( domainKeyPaths = lib.lists.flatten (
lib.lists.map (domain: [ lib.lists.map (domain: [
(rsaKeyPath domain) (rsaKeyPath domain)
(ed25519KeyPath domain) (ed25519KeyPath domain)
]) cfg.domains ]) cfg.domains
); );
ifthen = condition: expr: { # Currently (2025-07-25) I canot get rspamd to sign with ed25519 key,
"if" = condition; # it appears it attempts to parse it as an RSA key
"then" = expr; # {
}; # path: "${ed25519KeyPath domain}";
otherwise = expr: { "else" = expr; }; # selector: "snm_ed25519";
authDkimForDomain = domain: [ # }
(ifthen "sender_domain = '${domain}'" "['${domain}_rsa', '${domain}_ed25519']") dkimSigningForDomain = domain: ''
]; ${domain} {
authDkim = lib.lists.flatten (lib.lists.map authDkimForDomain cfg.domains); selectors [
signatureForDomain = domain: [ {
{ path: "${rsaKeyPath domain}";
name = "${domain}_rsa"; selector: "snm_rsa";
value = { }
inherit domain; ]
private-key = "%{file:${rsaKeyPath domain}}%";
selector = "snm_rsa";
headers = [
"From"
"To"
"Cc"
"Date"
"Subject"
"Message-ID"
"Organization"
"MIME-Version"
"Content-Type"
"In-Reply-To"
"References"
"List-Id"
"User-Agent"
"Thread-Topic"
"Thread-Index"
];
algorithm = "rsa-sha256";
canonicalization = "relaxed/relaxed";
report = true;
};
} }
{ '';
name = "${domain}_ed25519";
value = {
inherit domain;
private-key = "%{file:${ed25519KeyPath domain}}%";
selector = "snm_ed25519";
headers = [
"From"
"To"
"Cc"
"Date"
"Subject"
"Message-ID"
"Organization"
"MIME-Version"
"Content-Type"
"In-Reply-To"
"References"
"List-Id"
"User-Agent"
"Thread-Topic"
"Thread-Index"
];
algorithm = "ed25519-sha256";
canonicalization = "relaxed/relaxed";
report = true;
};
}
];
dkimSignatures = {
signature = lib.listToAttrs (lib.lists.flatten (lib.lists.map signatureForDomain cfg.domains));
};
dkimPublicKey = dkimPublicKey =
tls_key: tls_key:
''''${ replace(trimprefix(trimsuffix(${tls_key}.public_key_pem, ${publicKeyEnd}), ${publicKeyBegin}), "\n", "") }''; ''''${ replace(trimprefix(trimsuffix(${tls_key}.public_key_pem, ${publicKeyEnd}), ${publicKeyBegin}), "\n", "") }'';
@ -190,10 +136,10 @@ in
''; '';
destination = rsaKeyPath domain; destination = rsaKeyPath domain;
perms = "0600"; perms = "0600";
owner = "stalwart-mail"; owner = "rspamd";
group = "stalwart-mail"; group = "rspamd";
restartUnits = [ restartUnits = [
"stalwart-mail.service" "rspamd.service"
]; ];
} }
{ {
@ -204,25 +150,47 @@ in
''; '';
destination = ed25519KeyPath domain; destination = ed25519KeyPath domain;
perms = "0600"; perms = "0600";
owner = "stalwart-mail"; owner = "rspamd";
group = "stalwart-mail"; group = "rspamd";
restartUnits = [ restartUnits = [
"stalwart-mail.service" "rspamd.service"
]; ];
} }
]) cfg.domains ]) cfg.domains
); );
services.stalwart-mail.settings = { mailserver = {
auth.dkim = { dkimSigning = false;
sign = authDkim ++ [ };
(otherwise false) services.rspamd.locals."dkim_signing.conf" = lib.mkForce {
]; text = ''
enabled = true;
allow_username_mismatch = true;
domain {
${lib.strings.concatStringsSep "\n " (lib.lists.map dkimSigningForDomain cfg.domains)}
}
'';
};
services.rspamd.locals."arc.conf" = lib.mkForce {
text = ''
enabled = true;
allow_username_mismatch = true;
domain {
${lib.strings.concatStringsSep "\n " (lib.lists.map dkimSigningForDomain cfg.domains)}
}
'';
};
services.postfix.config = {
# Need to include this as I disabled the in built support for dkim signing
# without this postfix won't forward the mails to rspamd to be signed.
non_smtpd_milters = [ "unix:/run/rspamd/rspamd-milter.sock" ];
};
systemd.services.rspamd = {
unitConfig = {
ConditionPathExists = domainKeyPaths;
};
serviceConfig = {
ReadOnlyPaths = domainKeyPaths;
}; };
}
// dkimSignatures;
systemd.services.stalwart-mail = {
unitConfig.ConditionPathExists = keyFiles;
serviceConfig.ReadOnlyPaths = keyFiles;
}; };
}; };
} }

View file

@ -16,43 +16,44 @@ in
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
services.stalwart-mail.settings = { mailserver.ldap = {
storage = { enable = true;
directory = "ldap"; uris = [ "ldaps://login.kaareskovgaard.net" ];
searchBase = "dc=login,dc=kaareskovgaard,dc=net";
searchScope = "sub";
bind = {
dn = "dn=token";
passwordFile = secretFile;
}; };
directory.ldap = { dovecot = {
type = "ldap"; # Map LDAP uid to dovecot user, and ldap userPassword to dovecot password
url = "ldaps://login.kaareskovgaard.net"; passAttrs = "uid=user";
base-dn = "dc=login,dc=kaareskovgaard,dc=net"; passFilter = "(&(class=account)(memberOf=mail_user)(uid=%u))";
bind = { # This filter is used both when receiving mail (thus needing to lookup by mail address, and when authenticating, requriing the lookup by uid.)
dn = "dn=token"; # Note that the pass filter only allows looking up by uid, so should still only be able to authenticate using that.
secret = "%{env:STALWART_LDAP_SECRET}%"; userFilter = "(&(class=account)(memberOf=mail_user)(|(mail=%u)(uid=%u)))";
auth = { userAttrs = "uid=user";
method = "lookup"; };
}; postfix = {
}; filter = "(&(class=account)(memberOf=mail_user)(mail=%s))";
filter = { mailAttribute = "uid";
name = "(&(class=account)(memberOf=mail_user)(uid=?))"; uidAttribute = "uid";
email = "(&(class=account)(memberOf=mail_user)(mail=?))";
};
attributes = {
name = "name";
class = "class";
description = "name";
groups = "memberOf";
email = "mail;primary";
email-alias = "mail;alternative";
quota = "diskQuota";
};
}; };
}; };
systemd.services = { systemd.services = {
stalwart-mail = { dovecot2 = {
unitConfig = { unitConfig = {
ConditionPathExists = [ secretFile ]; ConditionPathExists = [ secretFile ];
}; };
serviceConfig = { };
EnvironmentFile = secretFile; postfix = {
unitConfig = {
ConditionPathExists = [ secretFile ];
};
};
postfix-setup = {
unitConfig = {
ConditionPathExists = [ secretFile ];
}; };
}; };
}; };
@ -61,14 +62,15 @@ in
{ {
contents = '' contents = ''
{{- with secret "${cfg.ldap.mount}/data/${cfg.ldap.path}" -}} {{- with secret "${cfg.ldap.mount}/data/${cfg.ldap.path}" -}}
STALWART_LDAP_SECRET={{ .Data.data.apiToken }} {{ .Data.data.apiToken }}
{{- end -}} {{- end -}}
''; '';
destination = secretFile; destination = secretFile;
owner = "stalwart-mail"; owner = "dovecot";
group = "stalwart-mail"; group = "dovecot";
restartUnits = [ restartUnits = [
"stalwart-mail.service" "dovecot2.service"
"postfix.service"
]; ];
} }
]; ];

View file

@ -5,41 +5,41 @@ let
in in
{ {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
# khscodes.services.vault-agent.templates = [ khscodes.services.vault-agent.templates = [
# { {
# contents = '' contents = ''
# {{- with secret "kanidm/data/apps/dovecot" -}} {{- with secret "kanidm/data/apps/dovecot" -}}
# scope = email openid profile scope = email openid profile
# username_attribute = username username_attribute = username
# debug = yes debug = yes
# introspection_url = https://dovecot:{{ .Data.data.basic_secret }}@login.kaareskovgaard.net/oauth2/token/introspect introspection_url = https://dovecot:{{ .Data.data.basic_secret }}@login.kaareskovgaard.net/oauth2/token/introspect
# introspection_mode = post introspection_mode = post
# {{- end -}} {{- end -}}
# ''; '';
# destination = oauthConfigFile; destination = oauthConfigFile;
# perms = "0600"; perms = "0600";
# owner = "root"; owner = "root";
# group = "root"; group = "root";
# restartUnits = [ "dovecot2.service" ]; restartUnits = [ "dovecot2.service" ];
# } }
# ]; ];
# services.dovecot2.extraConfig = '' services.dovecot2.extraConfig = ''
# auth_mechanisms = $auth_mechanisms oauthbearer xoauth2 auth_mechanisms = $auth_mechanisms oauthbearer xoauth2
# passdb { passdb {
# driver = oauth2 driver = oauth2
# mechanisms = xoauth2 oauthbearer mechanisms = xoauth2 oauthbearer
# args = ${oauthConfigFile} args = ${oauthConfigFile}
# } }
# ''; '';
# systemd.services.dovecot2 = { systemd.services.dovecot2 = {
# serviceConfig.ReadOnlyPaths = [ serviceConfig.ReadOnlyPaths = [
# oauthConfigFile oauthConfigFile
# ]; ];
# unitConfig.ConditionPathExists = [ unitConfig.ConditionPathExists = [
# oauthConfigFile oauthConfigFile
# ]; ];
# }; };
}; };
} }

View file

@ -1,23 +0,0 @@
# This file contains patches for Nixos 25.05 to be compatible with new stalwart mail
{
lib,
config,
pkgs,
...
}:
let
configFormat = pkgs.formats.toml { };
configFile = configFormat.generate "stalwart-mail.toml" config.services.stalwart-mail.settings;
in
{
systemd.services.stalwart-mail = lib.mkIf config.services.stalwart-mail.enable {
serviceConfig = {
User = "stalwart-mail";
Group = "stalwart-mail";
ExecStart = lib.mkForce [
""
"${lib.getExe config.services.stalwart-mail.package} --config=${configFile}"
];
};
};
}

View file

@ -1,194 +0,0 @@
{
lib,
rustPlatform,
fetchFromGitHub,
pkg-config,
protobuf,
bzip2,
openssl,
sqlite,
foundationdb,
zstd,
stdenv,
nix-update-script,
nixosTests,
rocksdb,
callPackage,
withFoundationdb ? false,
stalwartEnterprise ? false,
}:
rustPlatform.buildRustPackage (finalAttrs: {
pname = "stalwart-mail" + (lib.optionalString stalwartEnterprise "-enterprise");
version = "0.13.2";
src = fetchFromGitHub {
owner = "stalwartlabs";
repo = "stalwart";
rev = "51a0a1445d74a8cfb880e9d88f5be390fa0e9365";
hash = "sha256-VdeHb1HVGXA5RPenhhK4r/kkQiLG8/4qhdxoJ3xIqR4=";
};
cargoHash = "sha256-Wu6skjs3Stux5nCX++yoQPeA33Qln67GoKcob++Ldng=";
nativeBuildInputs = [
pkg-config
protobuf
rustPlatform.bindgenHook
];
buildInputs = [
bzip2
openssl
sqlite
zstd
]
++ lib.optionals (stdenv.hostPlatform.isLinux && withFoundationdb) [ foundationdb ];
# Issue: https://github.com/stalwartlabs/stalwart/issues/1104
buildNoDefaultFeatures = true;
buildFeatures = [
"postgres"
"rocks"
"elastic"
"redis"
]
++ lib.optionals withFoundationdb [ "foundationdb" ]
++ lib.optionals stalwartEnterprise [ "enterprise" ];
env = {
OPENSSL_NO_VENDOR = true;
ZSTD_SYS_USE_PKG_CONFIG = true;
ROCKSDB_INCLUDE_DIR = "${rocksdb}/include";
ROCKSDB_LIB_DIR = "${rocksdb}/lib";
};
postInstall = ''
mkdir -p $out/etc/stalwart
mkdir -p $out/lib/systemd/system
substitute resources/systemd/stalwart-mail.service $out/lib/systemd/system/stalwart-mail.service \
--replace "__PATH__" "$out"
'';
checkFlags = lib.forEach [
# Require running mysql, postgresql daemon
"directory::imap::imap_directory"
"directory::internal::internal_directory"
"directory::ldap::ldap_directory"
"directory::sql::sql_directory"
"directory::oidc::oidc_directory"
"store::blob::blob_tests"
"store::lookup::lookup_tests"
"smtp::lookup::sql::lookup_sql"
# thread 'directory::smtp::lmtp_directory' panicked at tests/src/store/mod.rs:122:44:
# called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }
"directory::smtp::lmtp_directory"
# thread 'imap::imap_tests' panicked at tests/src/imap/mod.rs:436:14:
# Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent
"imap::imap_tests"
# thread 'jmap::jmap_tests' panicked at tests/src/jmap/mod.rs:303:14:
# Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent
"jmap::jmap_tests"
# Failed to read system DNS config: io error: No such file or directory (os error 2)
"smtp::inbound::data::data"
# Expected "X-My-Header: true" but got Received: from foobar.net (unknown [10.0.0.123])
"smtp::inbound::scripts::sieve_scripts"
# thread 'smtp::outbound::lmtp::lmtp_delivery' panicked at tests/src/smtp/session.rs:313:13:
# Expected "<invalid@domain.org> (failed to lookup" but got From: "Mail Delivery Subsystem" <MAILER-DAEMON@localhost>
"smtp::outbound::lmtp::lmtp_delivery"
# thread 'smtp::outbound::extensions::extensions' panicked at tests/src/smtp/inbound/mod.rs:45:23:
# No queue event received.
"smtp::outbound::extensions::extensions"
# panicked at tests/src/smtp/outbound/smtp.rs:173:5:
"smtp::outbound::smtp::smtp_delivery"
# panicked at tests/src/smtp/outbound/lmtp.rs
"smtp::outbound::lmtp::lmtp_delivery"
# thread 'smtp::queue::retry::queue_retry' panicked at tests/src/smtp/queue/retry.rs:119:5:
# assertion `left == right` failed
# left: [1, 2, 2]
# right: [1, 2, 3]
"smtp::queue::retry::queue_retry"
# thread 'smtp::queue::virtualq::virtual_queue' panicked at /build/source/crates/store/src/dispatch/store.rs:548:14:
# called `Result::unwrap()` on an `Err` value: Error(Event { inner: Store(MysqlError), keys: [(Reason, String("Input/output error: Input/output error: Connection refused (os error 111)")), (CausedBy, String("crates/store/src/dispatch/store.rs:301"))] })
"smtp::queue::virtualq::virtual_queue"
# Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent
"store::store_tests"
# Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent
"cluster::cluster_tests"
# Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent
"webdav::webdav_tests"
# thread 'config::parser::tests::toml_parse' panicked at crates/utils/src/config/parser.rs:463:58:
# called `Result::unwrap()` on an `Err` value: "Expected ['\\n'] but found '!' in value at line 70."
"config::parser::tests::toml_parse"
# error[E0432]: unresolved import `r2d2_sqlite`
# use of undeclared crate or module `r2d2_sqlite`
"backend::sqlite::pool::SqliteConnectionManager::with_init"
# thread 'smtp::reporting::analyze::report_analyze' panicked at tests/src/smtp/reporting/analyze.rs:88:5:
# assertion `left == right` failed
# left: 0
# right: 12
"smtp::reporting::analyze::report_analyze"
# thread 'smtp::inbound::dmarc::dmarc' panicked at tests/src/smtp/inbound/mod.rs:59:26:
# Expected empty queue but got Reload
"smtp::inbound::dmarc::dmarc"
# thread 'smtp::queue::concurrent::concurrent_queue' panicked at tests/src/smtp/inbound/mod.rs:65:9:
# assertion `left == right` failed
"smtp::queue::concurrent::concurrent_queue"
# Failed to read system DNS config: io error: No such file or directory (os error 2)
"smtp::inbound::auth::auth"
# Failed to read system DNS config: io error: No such file or directory (os error 2)
"smtp::inbound::antispam::antispam"
# Failed to read system DNS config: io error: No such file or directory (os error 2)
"smtp::inbound::vrfy::vrfy_expn"
# thread 'smtp::management::queue::manage_queue' panicked at tests/src/smtp/inbound/mod.rs:45:23:
# No queue event received.
# NOTE: Test unreliable on high load systems
"smtp::management::queue::manage_queue"
# thread 'responses::tests::parse_responses' panicked at crates/dav-proto/src/responses/mod.rs:671:17:
# assertion `left == right` failed: failed for 008.xml
# left: ElementEnd
# right: Bytes([...])
"responses::tests::parse_responses"
] (test: "--skip=${test}");
doCheck = !(stdenv.hostPlatform.isLinux && stdenv.hostPlatform.isAarch64);
# Allow network access during tests on Darwin/macOS
__darwinAllowLocalNetworking = true;
passthru = {
inherit rocksdb; # make used rocksdb version available (e.g., for backup scripts)
webadmin = callPackage ./webadmin.nix { };
spam-filter = callPackage ./spam-filter.nix { };
updateScript = nix-update-script { };
tests.stalwart-mail = nixosTests.stalwart-mail;
};
meta = {
description = "Secure & Modern All-in-One Mail Server (IMAP, JMAP, SMTP)";
homepage = "https://github.com/stalwartlabs/mail-server";
changelog = "https://github.com/stalwartlabs/mail-server/blob/main/CHANGELOG.md";
license = [
lib.licenses.agpl3Only
]
++ lib.optionals stalwartEnterprise [
{
fullName = "Stalwart Enterprise License 1.0 (SELv1) Agreement";
url = "https://github.com/stalwartlabs/mail-server/blob/main/LICENSES/LicenseRef-SEL.txt";
free = false;
redistributable = false;
}
];
mainProgram = "stalwart";
maintainers = with lib.maintainers; [
happysalada
onny
oddlama
pandapip1
norpol
];
};
})

View file

@ -1,43 +0,0 @@
{
lib,
fetchFromGitHub,
stdenv,
stalwart-mail,
nix-update-script,
}:
stdenv.mkDerivation (finalAttrs: {
pname = "spam-filter";
version = "2.0.3";
src = fetchFromGitHub {
owner = "stalwartlabs";
repo = "spam-filter";
tag = "v${finalAttrs.version}";
hash = "sha256-NhD/qUiGhgESwR2IOzAHfDATRlgWMcCktlktvVfDONk=";
};
buildPhase = ''
bash ./build.sh
'';
installPhase = ''
mkdir -p $out
cp spam-filter.toml $out/
'';
passthru = {
updateScript = nix-update-script { };
};
meta = {
description = "Secure & modern all-in-one mail server Stalwart (spam-filter module)";
homepage = "https://github.com/stalwartlabs/spam-filter";
changelog = "https://github.com/stalwartlabs/spam-filter/blob/${finalAttrs.src.tag}/CHANGELOG.md";
license = with lib.licenses; [
mit
asl20
];
inherit (stalwart-mail.meta) maintainers;
};
})

View file

@ -1,77 +0,0 @@
{
lib,
rustPlatform,
stalwart-mail,
fetchFromGitHub,
trunk,
tailwindcss_3,
fetchNpmDeps,
nix-update-script,
nodejs,
npmHooks,
llvmPackages,
wasm-bindgen-cli_0_2_93,
binaryen,
zip,
}:
rustPlatform.buildRustPackage (finalAttrs: {
pname = "webadmin";
version = "0.1.31";
src = fetchFromGitHub {
owner = "stalwartlabs";
repo = "webadmin";
rev = "6f1368b8a1160341b385980accea489ee0e45440";
hash = "sha256-/EWn/wiY6zFNhObfo11OkoGufcUODMYs18P3vTBbB8s=";
};
npmDeps = fetchNpmDeps {
name = "${finalAttrs.pname}-npm-deps";
hash = "sha256-na1HEueX8w7kuDp8LEtJ0nD1Yv39cyk6sEMpS1zix2s=";
};
cargoHash = "sha256-Q05+wH9+NfkfmEDJFLuWVQ7wuDeEu9h1XmOMN6SYdyU=";
postPatch = ''
# Using local tailwindcss for compilation
substituteInPlace Trunk.toml --replace-fail "npx tailwindcss" "tailwindcss"
'';
nativeBuildInputs = [
binaryen
llvmPackages.bintools-unwrapped
nodejs
npmHooks.npmConfigHook
tailwindcss_3
trunk
# needs to match with wasm-bindgen version in upstreams Cargo.lock
wasm-bindgen-cli_0_2_93
zip
];
NODE_PATH = "$npmDeps";
buildPhase = ''
trunk build --offline --frozen --release
'';
installPhase = ''
cd dist
mkdir -p $out
zip -r $out/webadmin.zip *
'';
passthru = {
updateScript = nix-update-script { };
};
meta = {
description = "Secure & modern all-in-one mail server Stalwart (webadmin module)";
homepage = "https://github.com/stalwartlabs/webadmin";
changelog = "https://github.com/stalwartlabs/webadmin/blob/${finalAttrs.src.tag}/CHANGELOG.md";
license = lib.licenses.agpl3Only;
inherit (stalwart-mail.meta) maintainers;
};
})

View file

@ -1,16 +1,13 @@
{ config, lib, ... }: { config, lib, ... }:
let let
fqdn = config.khscodes.networking.fqdn;
cfg = config.khscodes.infrastructure.mailserver; cfg = config.khscodes.infrastructure.mailserver;
in in
{ {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
services.stalwart-mail.settings.metrics.prometheus = {
services.prometheus.exporters.postfix = {
enable = true; enable = true;
}; };
# Don't expose the endpoint khscodes.infrastructure.vault-prometheus-sender.exporters.enabled = [ "postfix" ];
khscodes.services.nginx.virtualHosts."${fqdn}".locations."=/metrics/prometheus" = {
return = 404;
};
}; };
} }