Compare commits
4
Commits
3d85ca85da
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96f8e9162b | ||
|
|
ce646a9598 | ||
|
|
a86df3a7ab
|
||
|
|
11a3e35ec6 |
No files matched your search
Generated
+29
-29
@@ -9,11 +9,11 @@
|
||||
"rust-overlay": "rust-overlay"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1791702392,
|
||||
"narHash": "sha256-8uWZP3UiJBtYzBzazI18WvlshKYHxPb+8aqm02OxL5M=",
|
||||
"lastModified": 1792911802,
|
||||
"narHash": "sha256-GdMiAfj8hRDCqrE2AvQJ3atf0y2XVGFx9/Ptr6qiju0=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "ee2a27ad749d6d774e26c2be5de5f3a5ecd4a93f",
|
||||
"revCount": 28,
|
||||
"rev": "a355f99a17b9dc38fc626b80340646a94ae7f1c9",
|
||||
"revCount": 30,
|
||||
"type": "git",
|
||||
"url": "https://scm.wyattjmiller.com/wymiller/caitsith.git"
|
||||
},
|
||||
@@ -29,11 +29,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1786845137,
|
||||
"narHash": "sha256-oQFip+v0luP8NIxJzmiW4Wu8bILsbFWom5l0zonl8hQ=",
|
||||
"lastModified": 1792737899,
|
||||
"narHash": "sha256-P0/GhewcEQmAbzq3QunNWY2jLJaYH9F6c/5dEM/jQ4M=",
|
||||
"owner": "LnL7",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "4cff07de74b50e64bdd68cd4e722ab5b6b35ee48",
|
||||
"rev": "bb0b90dbd54e207100de72b5dcbe3188c98a4b69",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -135,11 +135,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1792166485,
|
||||
"narHash": "sha256-TL2RJ56FbBQNi6vsCXAn7QhZECh5DPbvzfiX6v+4r1A=",
|
||||
"lastModified": 1792644631,
|
||||
"narHash": "sha256-3aILZu5bQ+6khPvGCdIbC7wBFZOgPbpP0TBSL9tf89E=",
|
||||
"owner": "purpleclay",
|
||||
"repo": "go-overlay",
|
||||
"rev": "5971ffb6c5de960d37f3b85c53cb5a30387cd05a",
|
||||
"rev": "ff2b494b477eb4349dd40f0564cedab552f9804d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -157,11 +157,11 @@
|
||||
"rust-overlay": "rust-overlay_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1791702441,
|
||||
"narHash": "sha256-qHf8tVIYu6OE1PKcthmpBgblg27u3bU7v4TVqy0B3jg=",
|
||||
"lastModified": 1792911744,
|
||||
"narHash": "sha256-iXH6FL8HYaZg0+p9lUv5bEc4k1YpvwKFYtMYbY1phTA=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "2c2e99f6347e9be744a72f7df6043015989d6cd7",
|
||||
"revCount": 17,
|
||||
"rev": "01ff244467f011033d98286e8f4171a890e9060a",
|
||||
"revCount": 19,
|
||||
"type": "git",
|
||||
"url": "https://scm.wyattjmiller.com/wymiller/hildibrand.git"
|
||||
},
|
||||
@@ -177,11 +177,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1792098797,
|
||||
"narHash": "sha256-u4DIIZgznEGzByxXrxWCXK19VcMtTBmi/M9gZ7Jk9bM=",
|
||||
"lastModified": 1792896866,
|
||||
"narHash": "sha256-/0tELNnhdSnP+bHSdWiEe6KpfSwi6WrTnT9cm/jNkjA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "640725978b99076e18e069f6e7d2959124584f8f",
|
||||
"rev": "c216bf5a1f173b0481bd5c8c489ebe5595ecc68c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -192,11 +192,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1792030654,
|
||||
"narHash": "sha256-OaQwAtPMTjQiY2sT/2/62UbSPhzP/0aDnWzMm/NCiUw=",
|
||||
"lastModified": 1792615934,
|
||||
"narHash": "sha256-MlvRfa1O5Cqc2/a+gG+l9TSuowEoCKCmrJ1d9oAVm0I=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "26f4107e47e3ad0ffd0db220469ba83b6f6d96e8",
|
||||
"rev": "da6dc0e7b26060f2f71ce3ef7088fb56ee44fa36",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -225,11 +225,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1791620365,
|
||||
"narHash": "sha256-CVZXIDNw7HEwyD8X6q9Ln370Sc0ZqRlTzDRwOZ1hJMY=",
|
||||
"lastModified": 1792825530,
|
||||
"narHash": "sha256-aphXA2IFNd3q48++TKmfuuvDDjkrgGdJDnDIhij9684=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "e90fafa71b440fabcd52dd7fb017ab2da81c781a",
|
||||
"rev": "5b9f1198b59bcb31c5c3250b332e7d77590e6306",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -246,11 +246,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1791620365,
|
||||
"narHash": "sha256-CVZXIDNw7HEwyD8X6q9Ln370Sc0ZqRlTzDRwOZ1hJMY=",
|
||||
"lastModified": 1792825530,
|
||||
"narHash": "sha256-aphXA2IFNd3q48++TKmfuuvDDjkrgGdJDnDIhij9684=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "e90fafa71b440fabcd52dd7fb017ab2da81c781a",
|
||||
"rev": "5b9f1198b59bcb31c5c3250b332e7d77590e6306",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -266,11 +266,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1792219666,
|
||||
"narHash": "sha256-RMO08tKD+hFIL5ryWi85VY4aHE5oIpdV1h4kVFctOkw=",
|
||||
"lastModified": 1792825530,
|
||||
"narHash": "sha256-aphXA2IFNd3q48++TKmfuuvDDjkrgGdJDnDIhij9684=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "a110337465cfb76e3806b81b9db3094c3e6e770a",
|
||||
"rev": "5b9f1198b59bcb31c5c3250b332e7d77590e6306",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
self: {
|
||||
stalwart = import ./stalwart.nix self;
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
self: {
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.stalwart;
|
||||
configFormat = pkgs.formats.toml { };
|
||||
configFile = configFormat.generate "stalwart.toml" cfg.settings;
|
||||
useLegacyStorage = lib.versionOlder cfg.stateVersion "24.11";
|
||||
pre2605 = lib.versionOlder cfg.stateVersion "26.05";
|
||||
stalwartIdentifier = if pre2605 then "stalwart-mail" else "stalwart";
|
||||
stalwartIdentifierText = ''if lib.versionOlder config.services.stalwart.stateVersion "26.05" then "stalwart-mail" else "stalwart"'';
|
||||
|
||||
parsePorts =
|
||||
listeners:
|
||||
let
|
||||
parseAddresses = listeners: lib.flatten (lib.mapAttrsToList (name: value: value.bind) listeners);
|
||||
splitAddress = addr: lib.splitString ":" addr;
|
||||
extractPort = addr: lib.toInt (builtins.foldl' (a: b: b) "" (splitAddress addr));
|
||||
in
|
||||
map (address: extractPort address) (parseAddresses listeners);
|
||||
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
# since 0.12.0 (2025-05-26) release, upstream re-branded project to 'stalwart' due to inclusion of collaboration features (CalDAV, CardDAV, and WebDAV)
|
||||
# https://github.com/stalwartlabs/stalwart/releases/tag/v0.12.0
|
||||
(lib.mkRenamedOptionModule [ "services" "stalwart-mail" ] [ "services" "stalwart" ])
|
||||
];
|
||||
options.services.stalwart = {
|
||||
enable = lib.mkEnableOption "the all-in-one collaboration and mail server, Stalwart";
|
||||
|
||||
stateVersion = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
The version of this module (=version of NixOS) when this module was first enabled on this particular machine, used to maintain compatibility with application data created on older versions of this module.
|
||||
|
||||
See {option}`system.stateVersion` for details on the NixOS-global equivalent to this option.
|
||||
'';
|
||||
};
|
||||
|
||||
package = lib.mkPackageOption pkgs "stalwart" { };
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to open TCP firewall ports, which are specified in
|
||||
{option}`services.stalwart.settings.server.listener` on all interfaces.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
inherit (configFormat) type;
|
||||
default = { };
|
||||
description = ''
|
||||
Configuration options for the Stalwart server.
|
||||
See <https://stalw.art/docs/configuration> for available options.
|
||||
|
||||
By default, the module is configured to store everything locally.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/var/lib/${stalwartIdentifier}";
|
||||
defaultText = lib.literalExpression "/var/lib/\${${stalwartIdentifierText}}";
|
||||
description = ''
|
||||
Data directory for stalwart
|
||||
'';
|
||||
};
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = stalwartIdentifier;
|
||||
defaultText = lib.literalExpression stalwartIdentifierText;
|
||||
description = ''
|
||||
User ownership of service
|
||||
'';
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = stalwartIdentifier;
|
||||
defaultText = lib.literalExpression stalwartIdentifierText;
|
||||
description = ''
|
||||
Group ownership of service
|
||||
'';
|
||||
};
|
||||
|
||||
credentials = lib.mkOption {
|
||||
description = ''
|
||||
Credentials envs used to configure Stalwart secrets.
|
||||
These secrets can be accessed in configuration values with
|
||||
the macros such as
|
||||
`%{file:/run/credentials/stalwart.service/VAR_NAME}%`.
|
||||
'';
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
example = {
|
||||
user_admin_password = "/run/keys/stalwart_admin_password";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
!(
|
||||
(lib.hasAttrByPath [ "settings" "queue" ] cfg)
|
||||
&& (builtins.any (lib.hasAttrByPath [
|
||||
"value"
|
||||
"next-hop"
|
||||
]) (lib.attrsToList cfg.settings.queue))
|
||||
);
|
||||
message = ''
|
||||
Stalwart deprecated `next-hop` in favor of "virtual queues" `queue.strategy.route` \
|
||||
with v0.13.0 see [Outbound Strategy](https://stalw.art/docs/mta/outbound/strategy/#configuration) \
|
||||
and [release announcement](https://github.com/stalwartlabs/stalwart/blob/main/UPGRADING.md#upgrading-from-v012x-and-v011x-to-v013x).
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
# Default config: all local
|
||||
services.stalwart.settings = {
|
||||
tracer =
|
||||
if pre2605 then
|
||||
{
|
||||
stdout = {
|
||||
type = lib.mkDefault "stdout";
|
||||
level = lib.mkDefault "info";
|
||||
ansi = lib.mkDefault false; # no colour markers to journald
|
||||
enable = lib.mkDefault true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
journal = {
|
||||
type = lib.mkDefault "journal";
|
||||
level = lib.mkDefault "info";
|
||||
enable = lib.mkDefault true;
|
||||
};
|
||||
};
|
||||
store =
|
||||
if useLegacyStorage then
|
||||
{
|
||||
# structured data in SQLite, blobs on filesystem
|
||||
db.type = lib.mkDefault "sqlite";
|
||||
db.path = lib.mkDefault "${cfg.dataDir}/data/index.sqlite3";
|
||||
fs.type = lib.mkDefault "fs";
|
||||
fs.path = lib.mkDefault "${cfg.dataDir}/data/blobs";
|
||||
}
|
||||
else
|
||||
{
|
||||
# everything in RocksDB
|
||||
db.type = lib.mkDefault "rocksdb";
|
||||
db.path = lib.mkDefault "${cfg.dataDir}/db";
|
||||
db.compression = lib.mkDefault "lz4";
|
||||
};
|
||||
storage.data = lib.mkDefault "db";
|
||||
storage.fts = lib.mkDefault "db";
|
||||
storage.lookup = lib.mkDefault "db";
|
||||
storage.blob = lib.mkDefault (if useLegacyStorage then "fs" else "db");
|
||||
directory.internal.type = lib.mkDefault "internal";
|
||||
directory.internal.store = lib.mkDefault "db";
|
||||
storage.directory = lib.mkDefault "internal";
|
||||
resolver.type = lib.mkDefault "system";
|
||||
resolver.public-suffix = lib.mkDefault [
|
||||
"file://${pkgs.publicsuffix-list}/share/publicsuffix/public_suffix_list.dat"
|
||||
];
|
||||
spam-filter.resource = lib.mkDefault "file://${cfg.package.spam-filter}/spam-filter.toml";
|
||||
webadmin =
|
||||
let
|
||||
hasHttpListener = builtins.any (listener: listener.protocol == "http") (
|
||||
lib.attrValues (cfg.settings.server.listener or { })
|
||||
);
|
||||
in
|
||||
{
|
||||
path = "/var/cache/${stalwartIdentifier}";
|
||||
resource = lib.mkIf hasHttpListener (lib.mkDefault "file://${cfg.package.webadmin}/webadmin.zip");
|
||||
};
|
||||
};
|
||||
|
||||
# This service stores a potentially large amount of data.
|
||||
# Running it as a dynamic user would force chown to be run everytime the
|
||||
# service is restarted on a potentially large number of files.
|
||||
# That would cause unnecessary and unwanted delays.
|
||||
users = {
|
||||
groups = lib.mkIf (cfg.group == stalwartIdentifier) {
|
||||
${cfg.group} = { };
|
||||
};
|
||||
users = lib.mkIf (cfg.user == stalwartIdentifier) {
|
||||
${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
inherit (cfg) group;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' - '${cfg.user}' '${cfg.group}' - -"
|
||||
];
|
||||
|
||||
systemd = {
|
||||
services.stalwart = {
|
||||
description = "Stalwart Server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [
|
||||
"local-fs.target"
|
||||
"network.target"
|
||||
];
|
||||
|
||||
serviceConfig = {
|
||||
# Upstream service config
|
||||
Type = "simple";
|
||||
LimitNOFILE = 65536;
|
||||
KillMode = "process";
|
||||
KillSignal = "SIGINT";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
SyslogIdentifier = stalwartIdentifier;
|
||||
|
||||
ExecStartPre =
|
||||
if useLegacyStorage then
|
||||
''
|
||||
${lib.getExe' pkgs.coreutils "mkdir"} -p ${cfg.dataDir}/data/blobs
|
||||
''
|
||||
else
|
||||
''
|
||||
${lib.getExe' pkgs.coreutils "mkdir"} -p ${cfg.dataDir}/db
|
||||
'';
|
||||
ExecStart = [
|
||||
""
|
||||
"${lib.getExe cfg.package} --config=${configFile}"
|
||||
];
|
||||
LoadCredential = lib.mapAttrsToList (key: value: "${key}:${value}") cfg.credentials;
|
||||
|
||||
ReadWritePaths = [
|
||||
cfg.dataDir
|
||||
];
|
||||
CacheDirectory = stalwartIdentifier;
|
||||
StateDirectory = stalwartIdentifier;
|
||||
|
||||
# Upstream uses "stalwart" as the username since 0.12.0
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
|
||||
# Bind standard privileged ports
|
||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
|
||||
|
||||
# Hardening
|
||||
DeviceAllow = [ "" ];
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = false; # incompatible with CAP_NET_BIND_SERVICE
|
||||
ProcSubset = "pid";
|
||||
PrivateTmp = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProtectSystem = "strict";
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_UNIX"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@privileged"
|
||||
];
|
||||
UMask = "0077";
|
||||
};
|
||||
unitConfig.ConditionPathExists = [
|
||||
"${configFile}"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# Make admin commands available in the shell
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
networking.firewall =
|
||||
lib.mkIf (cfg.openFirewall && (builtins.hasAttr "listener" cfg.settings.server))
|
||||
{
|
||||
allowedTCPPorts = parsePorts cfg.settings.server.listener;
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [
|
||||
happysalada
|
||||
onny
|
||||
norpol
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
lib,
|
||||
fetchFromGitHub,
|
||||
stdenvNoCC,
|
||||
stalwart_0_16,
|
||||
nix-update-script,
|
||||
python3,
|
||||
}:
|
||||
|
||||
stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
pname = "spam-filter";
|
||||
version = "3.0.2";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "stalwartlabs";
|
||||
repo = "spam-filter";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-dMHfVzSTP/J+ohBIOIXZ3eKPVK+gnLZ0ifq5/u2EBFE=";
|
||||
};
|
||||
|
||||
__structuredAttrs = true;
|
||||
|
||||
nativeBuildInputs = [ python3 ];
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
python3 generate_rules_json.py
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out
|
||||
cp spam-filter-rules.json.gz $out
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
updateScript = nix-update-script { };
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Spam filter module for the Stalwart server";
|
||||
homepage = "https://github.com/stalwartlabs/spam-filter";
|
||||
changelog = "https://github.com/stalwartlabs/spam-filter/blob/${finalAttrs.src.tag}/CHANGELOG.md";
|
||||
license =
|
||||
with lib.licenses;
|
||||
OR [
|
||||
mit
|
||||
asl20
|
||||
];
|
||||
inherit (stalwart_0_16.meta) maintainers;
|
||||
};
|
||||
})
|
||||
@@ -0,0 +1,315 @@
|
||||
{
|
||||
lib,
|
||||
rustPlatform,
|
||||
fetchFromGitHub,
|
||||
pkg-config,
|
||||
protobuf,
|
||||
bzip2,
|
||||
openssl,
|
||||
sqlite,
|
||||
foundationdb,
|
||||
zstd,
|
||||
stdenv,
|
||||
_experimental-update-script-combinators,
|
||||
nix-update-script,
|
||||
rocksdb,
|
||||
callPackage,
|
||||
python3Packages,
|
||||
cacert,
|
||||
libredirect,
|
||||
writeTextFile,
|
||||
withFoundationdb ? false,
|
||||
stalwartEnterprise ? false,
|
||||
}:
|
||||
let
|
||||
migrate_v016 =
|
||||
{
|
||||
src,
|
||||
version,
|
||||
}:
|
||||
python3Packages.buildPythonApplication {
|
||||
pname = "migrate_v016";
|
||||
inherit src version;
|
||||
__structuredAttrs = true;
|
||||
format = "other";
|
||||
dontBuild = true;
|
||||
dependencies = [
|
||||
python3Packages.urllib3
|
||||
python3Packages.requests
|
||||
];
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out/bin
|
||||
cp $src/resources/scripts/migrate_v016.py $out/bin/migrate_v016
|
||||
chmod +x $out/bin/migrate_v016
|
||||
runHook postInstall
|
||||
'';
|
||||
postFixUp = ''
|
||||
wrapPythonPrograms
|
||||
'';
|
||||
};
|
||||
in
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "stalwart" + (lib.optionalString stalwartEnterprise "-enterprise");
|
||||
version = "0.16.26";
|
||||
|
||||
__structuredAttrs = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "stalwartlabs";
|
||||
repo = "stalwart";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-CJ8XOWojIOD0/xBMDnZq0oS68vd77AEXkbT1nZx7bzY=";
|
||||
};
|
||||
|
||||
cargoHash = "sha256-fE4+Dq6SGDA6FVDPO1Zf/Ow2VUALRQlPxGk+iETxqts=";
|
||||
|
||||
env = {
|
||||
# https://docs.rs/openssl/latest/openssl/#manual
|
||||
OPENSSL_NO_VENDOR = true;
|
||||
OPENSSL_DIR = lib.getDev openssl;
|
||||
OPENSSL_LIB_DIR = "${lib.getLib openssl}/lib";
|
||||
ZSTD_SYS_USE_PKG_CONFIG = true;
|
||||
ROCKSDB_INCLUDE_DIR = "${rocksdb}/include";
|
||||
ROCKSDB_LIB_DIR = "${rocksdb}/lib";
|
||||
}
|
||||
//
|
||||
lib.optionalAttrs
|
||||
(stdenv.hostPlatform.isLinux && (stdenv.hostPlatform.isAarch64 || stdenv.hostPlatform.isArmv7))
|
||||
{
|
||||
# Required to fix Stalwart on ARM systems with 16 KB page size.
|
||||
# Using rust-jemalloc-sys causes segmentation faults on x86_64.
|
||||
JEMALLOC_SYS_WITH_LG_PAGE = 16;
|
||||
};
|
||||
|
||||
depsBuildBuild = [
|
||||
pkg-config
|
||||
zstd
|
||||
];
|
||||
|
||||
nativeBuildInputs = [
|
||||
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 = [
|
||||
"sqlite"
|
||||
"postgres"
|
||||
"mysql"
|
||||
"rocks"
|
||||
"s3"
|
||||
"redis"
|
||||
"azure"
|
||||
"nats"
|
||||
]
|
||||
++ lib.optionals withFoundationdb [ "foundationdb" ]
|
||||
++ lib.optionals stalwartEnterprise [ "enterprise" ];
|
||||
|
||||
cargoBuildFlags = [
|
||||
"-p"
|
||||
"stalwart"
|
||||
];
|
||||
cargoTestFlags = finalAttrs.cargoBuildFlags;
|
||||
|
||||
doCheck = true;
|
||||
nativeCheckInputs = [
|
||||
openssl
|
||||
# ... panicked at crates/utils/src/lib.rs:94:17: Failed to build TLS connectors: Failed to build platform verifier: unexpected error: No CA certificates were loaded from the system
|
||||
cacert
|
||||
];
|
||||
|
||||
# Allow network access during tests on Darwin/macOS
|
||||
__darwinAllowLocalNetworking = true;
|
||||
# Allow access to DNS settings exposed via config daemon
|
||||
# thread ... panicked at tests/src/lib.rs:49:13: Errors: [ Build { ... , message: "Failed to read system DNS config: failed to access System Configuration dynamic store", }, ]
|
||||
# -> https://github.com/hickory-dns/hickory-dns/blob/f09321075b1f97902b7bc4ca4ffda7816fcf2971/crates/resolver/src/system_conf/apple.rs#L21
|
||||
# -> https://crates.io/crates/system-configuration
|
||||
# -> https://developer.apple.com/documentation/systemconfiguration/scdynamicstore?language=objc
|
||||
# -> "The handle to an open dynamic store session with the system configuration daemon." (configd)
|
||||
sandboxProfile = lib.optionalString stdenv.hostPlatform.isDarwin ''
|
||||
(allow mach-lookup (global-name "com.apple.SystemConfiguration.configd"))
|
||||
'';
|
||||
|
||||
preCheck =
|
||||
let
|
||||
nsswitch = writeTextFile {
|
||||
name = "nsswitch.conf";
|
||||
text = ''
|
||||
hosts: files dns
|
||||
'';
|
||||
};
|
||||
hosts = writeTextFile {
|
||||
name = "hosts";
|
||||
text = ''
|
||||
127.0.0.1 localhost
|
||||
'';
|
||||
};
|
||||
# ... panicked at tests/src/lib.rs:49:13: Errors: [ Build { ... , message: "Failed to read system DNS config: io error: No such file or directory (os error 2)" }, ... ]
|
||||
# -> https://github.com/hickory-dns/hickory-dns/blob/v0.26.1/crates/resolver/src/system_conf/unix.rs#L25
|
||||
# -> known issue: https://github.com/hickory-dns/hickory-dns/issues/2959
|
||||
resolvConf = writeTextFile {
|
||||
name = "resolv.conf";
|
||||
text = ''
|
||||
nameserver 127.0.0.1
|
||||
'';
|
||||
};
|
||||
in
|
||||
(lib.optionalString stdenv.hostPlatform.isLinux ''
|
||||
export NIX_REDIRECTS="/etc/nsswitch.conf=${nsswitch}:/etc/hosts=${hosts}:/etc/resolv.conf=${resolvConf}"
|
||||
export LD_PRELOAD="${libredirect}/lib/libredirect.so"
|
||||
'')
|
||||
+ ''
|
||||
export STORE=RocksDb
|
||||
'';
|
||||
|
||||
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"
|
||||
# Require running ldap directory
|
||||
"directory::directory_tests"
|
||||
# Require running nats or redis instance
|
||||
"cluster::broadcast::cluster_tests"
|
||||
# flaky tests: ... panicked at tests/src/utils/jmap.rs:434:32: Missing created item 0: { "methodResponses": [ [ "x:Domain/set", { "accountId": "d333333", "notCreated": { "i0": { "type": "primaryKeyViolation", "properties": [ "name" ], "objectId": { "object": "Domain", "id": "c" } } } }, "0" ] ], "sessionState": "a22d1fca" }
|
||||
"cluster::stress::stress_tests"
|
||||
"imap::imap_tests"
|
||||
"system::system_tests"
|
||||
"telemetry::telemetry_tests"
|
||||
# flaky test: ... panicked at tests/src/utils/jmap.rs:434:32: Missing created item 0: { "methodResponses": [ [ "error", { "type": "serverUnavailable", description": "This server is temporarily unavailable. Attempting this same operation later may succeed." }, "0" ] ], "sessionState": "a22d1fca" }
|
||||
"jmap::jmap_tests"
|
||||
"automation::automation_tests"
|
||||
# thread 'webdav::webdav_tests' (4676902) panicked at tests/src/utils/webdav.rs:591:13: Expected 201 Created but got 409 Conflict
|
||||
"webdav::webdav_tests"
|
||||
# panicked at tests/src/smtp/inbound/mod.rs:270:18: Unexpected event: WorkerDone { queue_id: 312710620013133824, queue_name: QueueName([100, 101, 102, 97, 117, 108, 116, 0]), status: Completed }
|
||||
"smtp::outbound::smtp::smtp_delivery"
|
||||
# flaky test: ... panicked at tests/src/smtp/outbound/lmtp.rs:245:5: assertion `left == right` failed
|
||||
# left: 3
|
||||
# right: 4
|
||||
"smtp::outbound::lmtp::lmtp_delivery"
|
||||
# flaky test: ... panicked at tests/src/smtp/reporting/analyze.rs:110:5: assertion `left == right` failed
|
||||
# left: 0
|
||||
# right: 5
|
||||
"smtp::reporting::analyze::report_analyze"
|
||||
# flaky test: ... panicked at tests/src/smtp/queue/manager.rs:200:32: Expected rcpt b not found in [Recipient { address: "c", retry: Schedule { due: 1781399468, inner: 0 }, notify: Schedule { due: 1781399473, inner: 0 }, expires: Ttl(9), queue: QueueName([100, 101, 102, 97, 117, 108, 116, 0]), status: Scheduled, flags: 0, orcpt: None }]
|
||||
"smtp::queue::manager::queue_due"
|
||||
# flaky test: ... panicked at tests/src/smtp/queue/retry.rs:237:5: assertion `left == right` failed
|
||||
# left: [1, 2, 2]
|
||||
# right: [1, 2, 3]
|
||||
"smtp::queue::retry::queue_retry"
|
||||
# error[E0432]: unresolved import `r2d2_sqlite`
|
||||
"backend::sqlite::pool::SqliteConnectionManager::with_init"
|
||||
# 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"
|
||||
# NOTE: long running test(s), needs live dependencies, likely to fail on systems under high load
|
||||
"smtp::inbound::antispam::antispam"
|
||||
# flaky test (darwin): ... panicked at tests/src/smtp/inbound/mod.rs:287:18:
|
||||
# Unexpected event: Refresh
|
||||
# (linux): Invalid address: failed to lookup address information: Temporary failure in name resolution
|
||||
"smtp::outbound::dane::dane_verify"
|
||||
]
|
||||
# ... panicked at tests/src/lib.rs:49:13: Errors: [ Build { ... , message: "Invalid address: failed to lookup address information: Temporary failure in name resolution", }, ]
|
||||
++ lib.optionals stdenv.hostPlatform.isLinux [
|
||||
"smtp::inbound::auth::auth"
|
||||
"smtp::inbound::basic::basic_commands"
|
||||
"smtp::inbound::data::data"
|
||||
"smtp::inbound::dmarc::dmarc"
|
||||
"smtp::inbound::ehlo::ehlo"
|
||||
"smtp::inbound::limits::limits"
|
||||
"smtp::inbound::mail::mail"
|
||||
"smtp::inbound::milter::milter_session"
|
||||
"smtp::inbound::milter::mta_hook_session"
|
||||
"smtp::inbound::rcpt::rcpt"
|
||||
"smtp::inbound::rewrite::address_rewrite"
|
||||
"smtp::inbound::scripts::sieve_scripts"
|
||||
"smtp::inbound::sign::sign_and_seal"
|
||||
"smtp::inbound::throttle::throttle_inbound"
|
||||
"smtp::inbound::vrfy::vrfy_expn"
|
||||
"smtp::lookup::expressions::expressions"
|
||||
"smtp::lookup::utils::strategies"
|
||||
"smtp::management::report::manage_reports"
|
||||
"smtp::outbound::dane::dane_test"
|
||||
"smtp::outbound::extensions::extensions"
|
||||
"smtp::outbound::fallback_relay::fallback_relay"
|
||||
"smtp::outbound::ip_lookup::ip_lookup_strategy"
|
||||
"smtp::outbound::mta_sts::mta_sts_verify"
|
||||
"smtp::outbound::throttle::throttle_outbound"
|
||||
"smtp::outbound::tls::starttls_optional"
|
||||
"smtp::queue::concurrent::concurrent_queue"
|
||||
"smtp::queue::dsn::generate_dsn"
|
||||
"smtp::queue::virtualq::virtual_queue"
|
||||
"smtp::reporting::dmarc::report_dmarc"
|
||||
"smtp::reporting::scheduler::report_scheduler"
|
||||
"smtp::reporting::tls::report_tls"
|
||||
"store::search_tests"
|
||||
"store::store_tests"
|
||||
# since 0.16.10
|
||||
"smtp::outbound::dane::dane_downgrade_on_tlsa_servfail"
|
||||
]
|
||||
) (test: "--skip=${test}");
|
||||
|
||||
postInstall = ''
|
||||
mkdir -p $out/etc/stalwart
|
||||
|
||||
mkdir -p $out/lib/systemd/system
|
||||
|
||||
substitute resources/systemd/stalwart-mail.service $out/lib/systemd/system/stalwart.service \
|
||||
--replace-fail "__PATH__" "$out"
|
||||
|
||||
ln -s ${migrate_v016 { inherit (finalAttrs) src version; }}/bin/migrate_v016 $out/bin/migrate_v016
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
inherit rocksdb; # make used rocksdb version available (e.g., for backup scripts)
|
||||
webui = callPackage ./webui.nix { };
|
||||
spam-filter = callPackage ./spam-filter.nix { };
|
||||
# subpackages have distinct version numbers, so we can't use nix-update's `--subpackage`
|
||||
updateScript = _experimental-update-script-combinators.sequence [
|
||||
(nix-update-script { })
|
||||
(nix-update-script { attrPath = "stalwart_0_16.webui"; })
|
||||
(nix-update-script { attrPath = "stalwart_0_16.spam-filter"; })
|
||||
];
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Secure, modern, all-in-one mail and collaboration server";
|
||||
longDescription = ''
|
||||
Secure, scalable and fluent in every protocol (IMAP, JMAP, SMTP, CalDAV, CardDAV, WebDAV).
|
||||
'';
|
||||
homepage = "https://github.com/stalwartlabs/stalwart";
|
||||
changelog = "https://github.com/stalwartlabs/stalwart/blob/${finalAttrs.src.tag}/CHANGELOG.md";
|
||||
license = [
|
||||
lib.licenses.agpl3Only
|
||||
]
|
||||
++ lib.optionals stalwartEnterprise [
|
||||
lib.licenses.stalwart
|
||||
];
|
||||
maxSilent = 14400; # 4 hours
|
||||
mainProgram = "stalwart";
|
||||
maintainers = with lib.maintainers; [
|
||||
happysalada
|
||||
onny
|
||||
oddlama
|
||||
pandapip1
|
||||
norpol
|
||||
debtquity
|
||||
];
|
||||
};
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
lib,
|
||||
buildNpmPackage,
|
||||
zip,
|
||||
stalwart_0_16,
|
||||
fetchFromGitHub,
|
||||
nix-update-script,
|
||||
}:
|
||||
buildNpmPackage (finalAttrs: {
|
||||
pname = "webui";
|
||||
version = "1.0.11";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "stalwartlabs";
|
||||
repo = "webui";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-luaNHpPg7XHnPfQqhLEq+wW1/crXLogzyyltFr4mdBk=";
|
||||
};
|
||||
|
||||
npmDepsHash = "sha256-qe9cSrvs6kWwgbOO0xL7MBaJvICOvyuLFVi9R0dgnXQ=";
|
||||
__structuredAttrs = true;
|
||||
|
||||
env = {
|
||||
# https://github.com/stalwartlabs/webui/tree/main#environment-variables
|
||||
# https://github.com/stalwartlabs/webui/blob/main/.env.development
|
||||
VITE_API_BASE_URL = "";
|
||||
VITE_OAUTH_CLIENT_ID = "stalwart-webui";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ zip ];
|
||||
preBuild = ''
|
||||
rm .env.development
|
||||
'';
|
||||
|
||||
doCheck = true;
|
||||
checkPhase = ''
|
||||
runHook preCheck
|
||||
npm run test
|
||||
runHook postCheck
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out
|
||||
cd dist
|
||||
zip -r $out/webui.zip *
|
||||
cd ..
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
updateScript = nix-update-script { };
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Stalwart WebUI";
|
||||
longDescription = ''
|
||||
Stalwart WebUI is schema-driven single-page application for administering Stalwart.
|
||||
|
||||
After authentication the panel fetches a JSON schema from the server and dynamically
|
||||
generates all forms, lists, navigation, and layouts from that schema. Nothing is hardcoded.
|
||||
'';
|
||||
homepage = "https://github.com/stalwartlabs/webui";
|
||||
changelog = "https://github.com/stalwartlabs/webui/blob/${finalAttrs.src.tag}/CHANGELOG.md";
|
||||
license = lib.licenses.OR [
|
||||
lib.licenses.agpl3Only
|
||||
{
|
||||
fullName = "Stalwart Enterprise License 2.0 (SELv2) Agreement";
|
||||
url = "https://github.com/stalwartlabs/webui/blob/main/LICENSES/LicenseRef-SEL.txt";
|
||||
free = false;
|
||||
redistributable = false;
|
||||
}
|
||||
];
|
||||
inherit (stalwart_0_16.meta) maintainers;
|
||||
};
|
||||
})
|
||||
@@ -26,4 +26,5 @@
|
||||
yubilock = pkgs.callPackage ./linux/yubilock/default.nix { };
|
||||
ani-cli-rs = pkgs.callPackage ./linux/ani-cli-rs.nix { };
|
||||
opengoal = pkgs.callPackage ./linux/opengoal.nix { };
|
||||
stalwart = pkgs.callPackage ./common/stalwart/stalwart.nix { };
|
||||
}
|
||||
Reference in new issue
Block a user