initial commit
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
{
|
||||
name,
|
||||
pkgs,
|
||||
lib,
|
||||
hostInfo,
|
||||
dsl,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (dsl) mkContainer mkService;
|
||||
inherit (hostInfo.paths) data secrets;
|
||||
|
||||
domain = "mail.floweytf.com";
|
||||
accountName = "flowey";
|
||||
stateDir = "/var/lib/stalwart";
|
||||
acmeDir = "/etc/mail-acme";
|
||||
|
||||
taggingScript = ''
|
||||
require ["envelope", "variables", "imap4flags", "fileinto"];
|
||||
|
||||
if envelope :localpart :matches "to" "*" {
|
||||
addflag "''${1}";
|
||||
}
|
||||
|
||||
fileinto "INBOX";
|
||||
'';
|
||||
|
||||
cli = lib.getExe' pkgs.stalwart-cli "stalwart-cli";
|
||||
jq = lib.getExe pkgs.jq;
|
||||
curl = lib.getExe pkgs.curl;
|
||||
grep = lib.getExe' pkgs.gnugrep "grep";
|
||||
paste = lib.getExe' pkgs.coreutils "paste";
|
||||
|
||||
# use sqlite b/c rocksdb segfaults
|
||||
configJsonTemplate = pkgs.writeText "stalwart-config.json" (
|
||||
builtins.toJSON {
|
||||
"@type" = "Sqlite";
|
||||
path = "${stateDir}/data.sqlite3";
|
||||
}
|
||||
);
|
||||
|
||||
staticPlan = pkgs.writeText "mail-static-plan.json" (
|
||||
builtins.concatStringsSep "\n" (
|
||||
map builtins.toJSON [
|
||||
{
|
||||
"@type" = "upsert";
|
||||
object = "Domain";
|
||||
matchOn = [ "name" ];
|
||||
value."domain-mail" = {
|
||||
name = domain;
|
||||
catchAllAddress = "${accountName}@${domain}";
|
||||
isEnabled = true;
|
||||
};
|
||||
}
|
||||
{
|
||||
"@type" = "upsert";
|
||||
object = "SieveSystemScript";
|
||||
matchOn = [ "name" ];
|
||||
value."sieve-tag" = {
|
||||
name = "tag-by-recipient";
|
||||
isActive = true;
|
||||
contents = taggingScript;
|
||||
};
|
||||
}
|
||||
]
|
||||
)
|
||||
+ "\n"
|
||||
);
|
||||
|
||||
stalwartAuthEnv = ''
|
||||
export STALWART_URL="http://127.0.0.1:8080"
|
||||
export STALWART_USER="admin"
|
||||
export STALWART_PASSWORD="$(cat "$CREDENTIALS_DIRECTORY/admin_password")"
|
||||
'';
|
||||
|
||||
readyGate = pkgs.writeShellScript "mail-ready-gate" ''
|
||||
set -uo pipefail
|
||||
until ${curl} -sS -o /dev/null "http://127.0.0.1:8080/api/account"; do
|
||||
if ! systemctl -q is-active stalwart.service; then
|
||||
echo "mail: stalwart is not running" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
'';
|
||||
|
||||
bootstrapScript = pkgs.writeShellScript "mail-bootstrap" ''
|
||||
set -uo pipefail
|
||||
${stalwartAuthEnv}
|
||||
|
||||
out="$(${cli} update Bootstrap singleton \
|
||||
--field defaultDomain=${domain} \
|
||||
--field serverHostname=${domain} \
|
||||
--field generateDkimKeys=true \
|
||||
--field requestTlsCertificate=false \
|
||||
--field 'directory={"@type":"Internal"}' 2>&1)"
|
||||
status=$?
|
||||
|
||||
if [ "$status" -eq 0 ]; then
|
||||
echo "mail-bootstrap: first-time setup complete, restarting stalwart into normal mode" >&2
|
||||
systemctl restart stalwart.service
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ${grep} -qi 'bootstrap mode' <<<"$out"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "mail-bootstrap: $out" >&2
|
||||
exit 1
|
||||
'';
|
||||
|
||||
certCredCert = "/run/credentials/stalwart.service/mail_tls_cert";
|
||||
certCredKey = "/run/credentials/stalwart.service/mail_tls_key";
|
||||
|
||||
retryOnFailure = {
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
};
|
||||
|
||||
retryLimit = {
|
||||
StartLimitBurst = 5;
|
||||
StartLimitIntervalSec = 120;
|
||||
};
|
||||
|
||||
provisionScript = pkgs.writeShellScript "mail-provision" ''
|
||||
set -euo pipefail
|
||||
${stalwartAuthEnv}
|
||||
export account_pass="$(cat "$CREDENTIALS_DIRECTORY/account_password")"
|
||||
|
||||
account_entry="$(${jq} -nc --arg name "${accountName}" \
|
||||
'{"@type":"upsert","object":"Account","matchOn":["name"],"value":{("account-"+$name):{"@type":"User","name":$name,"domainId":"#domain-mail","credentials":{"0":{"@type":"Password","secret":env.account_pass}}}}}')"
|
||||
|
||||
{ cat ${staticPlan}; echo "$account_entry"; } | ${cli} apply --stdin
|
||||
|
||||
existing_cert_ids="$(${cli} query Certificate --json --fields id | ${jq} -r .id | ${paste} -sd, -)"
|
||||
if [ -n "$existing_cert_ids" ]; then
|
||||
${cli} delete Certificate --ids "$existing_cert_ids"
|
||||
fi
|
||||
${cli} create Certificate \
|
||||
--field 'certificate={"@type":"File","filePath":"${certCredCert}"}' \
|
||||
--field 'privateKey={"@type":"File","filePath":"${certCredKey}"}'
|
||||
|
||||
systemctl restart stalwart.service
|
||||
'';
|
||||
in
|
||||
mkContainer {
|
||||
inherit name;
|
||||
net.access = [ { to = "@internet"; } ];
|
||||
svc.autoStart = true;
|
||||
net.forward.tcp = [
|
||||
25
|
||||
993
|
||||
];
|
||||
resources.fs = {
|
||||
"${stateDir}" = {
|
||||
hostPath = "${data}/core/mail";
|
||||
isReadOnly = false;
|
||||
};
|
||||
"${acmeDir}" = {
|
||||
hostPath = "${data}/core/nginx/acme/${domain}";
|
||||
isReadOnly = true;
|
||||
};
|
||||
"/run/secrets/mail-admin-password" = {
|
||||
hostPath = "${secrets}/mail-admin-password";
|
||||
isReadOnly = true;
|
||||
};
|
||||
"/run/secrets/mail-account-password" = {
|
||||
hostPath = "${secrets}/mail-account-password";
|
||||
isReadOnly = true;
|
||||
};
|
||||
};
|
||||
users = [
|
||||
{
|
||||
name = "stalwart";
|
||||
uid = 1000;
|
||||
home = stateDir;
|
||||
}
|
||||
];
|
||||
containerConfig = _: {
|
||||
systemd.tmpfiles.rules = [
|
||||
"Z ${stateDir} 0750 stalwart stalwart -"
|
||||
"C ${stateDir}/config.json 0640 stalwart stalwart - ${configJsonTemplate}"
|
||||
];
|
||||
|
||||
systemd.services.stalwart = mkService {
|
||||
description = "Stalwart Mail Server";
|
||||
execStart = pkgs.writeShellScript "stalwart-run" ''
|
||||
set -euo pipefail
|
||||
export STALWART_RECOVERY_ADMIN="admin:$(cat "$CREDENTIALS_DIRECTORY/admin_password")"
|
||||
exec ${lib.getExe' pkgs.stalwart_0_16 "stalwart"} -c ${stateDir}/config.json
|
||||
'';
|
||||
serviceConfig = {
|
||||
User = "stalwart";
|
||||
Group = "stalwart";
|
||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
ReadWritePaths = [ stateDir ];
|
||||
LoadCredential = [
|
||||
"admin_password:/run/secrets/mail-admin-password"
|
||||
"mail_tls_cert:${acmeDir}/fullchain.pem"
|
||||
"mail_tls_key:${acmeDir}/key.pem"
|
||||
];
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.paths.mail-cert-renew = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
pathConfig = {
|
||||
PathChanged = "${acmeDir}/key.pem";
|
||||
Unit = "mail-provision.service";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.mail-ready = mkService {
|
||||
description = "Wait for the Stalwart management API";
|
||||
execStart = readyGate;
|
||||
after = [ "stalwart.service" ];
|
||||
wants = [ "stalwart.service" ];
|
||||
wantedBy = [ ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
TimeoutStartSec = 60;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.mail-bootstrap = mkService {
|
||||
description = "One-time Stalwart bootstrap (server identity + admin account)";
|
||||
execStart = bootstrapScript;
|
||||
after = [ "mail-ready.service" ];
|
||||
wants = [ "mail-ready.service" ];
|
||||
unitConfig = retryLimit;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
LoadCredential = [ "admin_password:/run/secrets/mail-admin-password" ];
|
||||
}
|
||||
// retryOnFailure;
|
||||
};
|
||||
|
||||
systemd.services.mail-provision = mkService {
|
||||
description = "Provision mail.floweytf.com domain + catch-all account in Stalwart";
|
||||
execStart = provisionScript;
|
||||
after = [
|
||||
"mail-ready.service"
|
||||
"mail-bootstrap.service"
|
||||
];
|
||||
wants = [
|
||||
"mail-ready.service"
|
||||
"mail-bootstrap.service"
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
LoadCredential = [
|
||||
"admin_password:/run/secrets/mail-admin-password"
|
||||
"account_password:/run/secrets/mail-account-password"
|
||||
];
|
||||
}
|
||||
// retryOnFailure;
|
||||
unitConfig = retryLimit;
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user