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.
This commit is contained in:
Kaare Hoff Skovgaard 2025-07-30 11:11:17 +02:00
parent ad84cfae7e
commit 9af8f29b48
Signed by: khs
GPG key ID: C7D890804F01E9F0
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;
};
}; };
} }