1
1
Fork 1
mirror of https://github.com/oddlama/nix-config.git synced 2025-10-10 23:00:39 +02:00

chore: format everything

This commit is contained in:
oddlama 2024-11-26 13:34:55 +01:00
parent deca311c68
commit 7ccd7856ee
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
162 changed files with 4750 additions and 3718 deletions

View file

@ -1,6 +1,8 @@
{config, ...}: let
{ config, ... }:
let
inherit (config.repo.secrets.local) acme;
in {
in
{
age.secrets.acme-cloudflare-dns-token = {
rekeyFile = ./secrets/acme-cloudflare-dns-token.age;
mode = "440";
@ -22,7 +24,10 @@ in {
};
dnsProvider = "cloudflare";
dnsPropagationCheck = true;
reloadServices = ["nginx" "stalwart-mail"];
reloadServices = [
"nginx"
"stalwart-mail"
];
};
inherit (acme) certs wildcardDomains;
};

View file

@ -2,7 +2,8 @@
globals,
nodes,
...
}: {
}:
{
imports = [
../../config
../../config/hardware/hetzner-cloud.nix
@ -18,7 +19,7 @@
nixpkgs.hostPlatform = "x86_64-linux";
boot.mode = "bios";
users.groups.acme.members = ["nginx"];
users.groups.acme.members = [ "nginx" ];
services.nginx.enable = true;
services.nginx.recommendedSetup = true;
@ -28,7 +29,9 @@
};
# Connect safely via wireguard to skip authentication
networking.hosts.${nodes.sentinel.config.wireguard.proxy-sentinel.ipv4} = [globals.services.influxdb.domain];
networking.hosts.${nodes.sentinel.config.wireguard.proxy-sentinel.ipv4} = [
globals.services.influxdb.domain
];
meta.telegraf = {
enable = true;
scrapeSensors = false;

View file

@ -2,9 +2,11 @@
config,
lib,
...
}: let
}:
let
inherit (config.repo.secrets.local) disks;
in {
in
{
disko.devices = {
disk = {
main = {
@ -21,9 +23,9 @@ in {
};
};
zpool = {
rpool = lib.disko.zfs.mkZpool {datasets = lib.disko.zfs.impermanenceZfsDatasets;};
rpool = lib.disko.zfs.mkZpool { datasets = lib.disko.zfs.impermanenceZfsDatasets; };
};
};
boot.loader.grub.devices = ["/dev/disk/by-id/${disks.main}"];
boot.loader.grub.devices = [ "/dev/disk/by-id/${disks.main}" ];
}

View file

@ -4,7 +4,8 @@
globals,
lib,
...
}: let
}:
let
primaryDomain = globals.mail.primary;
idmailDomain = "alias.${primaryDomain}";
@ -14,12 +15,13 @@
};
mkArgon2id = secret: {
generator.dependencies = [config.age.secrets.${secret}];
generator.dependencies = [ config.age.secrets.${secret} ];
generator.script = "argon2id";
mode = "440";
group = "stalwart-mail";
};
in {
in
{
environment.persistence."/persist".directories = [
{
directory = config.services.idmail.dataDir;
@ -56,17 +58,19 @@ in {
admin = true;
password_hash = "%{file:${config.age.secrets.idmail-user-hash_admin.path}}%";
};
domains = lib.flip lib.mapAttrs globals.mail.domains (_domain: domainCfg: {
owner = "admin";
catch_all = "catch-all@${primaryDomain}";
inherit (domainCfg) public;
});
domains = lib.flip lib.mapAttrs globals.mail.domains (
_domain: domainCfg: {
owner = "admin";
catch_all = "catch-all@${primaryDomain}";
inherit (domainCfg) public;
}
);
mailboxes = lib.flip lib.mapAttrs' globals.mail.domains (
_domain: _domainCfg:
lib.nameValuePair "catch-all@${primaryDomain}" {
password_hash = "%{file:${config.age.secrets.idmail-mailbox-hash_catch-all.path}}%";
owner = "admin";
}
lib.nameValuePair "catch-all@${primaryDomain}" {
password_hash = "%{file:${config.age.secrets.idmail-mailbox-hash_catch-all.path}}%";
owner = "admin";
}
);
# XXX: create mailboxes for git@ vaultwarden@ and simultaneously alias them to the catch all for a send only mail.
};
@ -75,7 +79,7 @@ in {
services.nginx = {
upstreams.idmail = {
servers."127.0.0.1:3000" = {};
servers."127.0.0.1:3000" = { };
extraConfig = ''
zone idmail 64k;
keepalive 2;

View file

@ -3,12 +3,14 @@
globals,
lib,
...
}: let
}:
let
icfg = config.repo.secrets.local.networking.interfaces.wan;
in {
in
{
networking.hostId = config.repo.secrets.local.networking.hostId;
networking.domain = globals.mail.primary;
networking.hosts."127.0.0.1" = ["mail.${globals.mail.primary}"];
networking.hosts."127.0.0.1" = [ "mail.${globals.mail.primary}" ];
globals.monitoring.ping.envoy = {
hostv4 = lib.net.cidr.ip icfg.hostCidrv4;
@ -18,7 +20,9 @@ in {
boot.initrd.systemd.network = {
enable = true;
networks = {inherit (config.systemd.network.networks) "10-wan";};
networks = {
inherit (config.systemd.network.networks) "10-wan";
};
};
systemd.network.networks = {
@ -27,9 +31,9 @@ in {
icfg.hostCidrv4
icfg.hostCidrv6
];
gateway = ["fe80::1"];
gateway = [ "fe80::1" ];
routes = [
{Destination = "172.31.1.1";}
{ Destination = "172.31.1.1"; }
{
Gateway = "172.31.1.1";
GatewayOnLink = true;
@ -41,7 +45,7 @@ in {
};
};
networking.nftables.firewall.zones.untrusted.interfaces = ["wan"];
networking.nftables.firewall.zones.untrusted.interfaces = [ "wan" ];
# Allow accessing influx
wireguard.proxy-sentinel.client.via = "sentinel";

View file

@ -4,12 +4,14 @@
lib,
pkgs,
...
}: let
}:
let
primaryDomain = globals.mail.primary;
stalwartDomain = "mail.${primaryDomain}";
dataDir = "/var/lib/stalwart-mail";
mailBackupDir = "/var/cache/mail-backup";
in {
in
{
environment.persistence."/persist".directories = [
{
directory = dataDir;
@ -25,13 +27,13 @@ in {
};
age.secrets.stalwart-admin-hash = {
generator.dependencies = [config.age.secrets.stalwart-admin-pw];
generator.dependencies = [ config.age.secrets.stalwart-admin-pw ];
generator.script = "argon2id";
mode = "440";
group = "stalwart-mail";
};
users.groups.acme.members = ["stalwart-mail"];
users.groups.acme.members = [ "stalwart-mail" ];
networking.firewall.allowedTCPPorts = [
25 # smtp
@ -51,23 +53,24 @@ in {
services.stalwart-mail = {
enable = true;
settings = let
case = field: check: value: data: {
"if" = field;
${check} = value;
"then" = data;
};
ifthen = field: data: {
"if" = field;
"then" = data;
};
otherwise = value: {"else" = value;};
is-smtp = case "listener" "eq" "smtp";
is-authenticated = data: {
"if" = "!is_empty(authenticated_as)";
"then" = data;
};
in
settings =
let
case = field: check: value: data: {
"if" = field;
${check} = value;
"then" = data;
};
ifthen = field: data: {
"if" = field;
"then" = data;
};
otherwise = value: { "else" = value; };
is-smtp = case "listener" "eq" "smtp";
is-authenticated = data: {
"if" = "!is_empty(authenticated_as)";
"then" = data;
};
in
lib.mkForce {
config.local-keys = [
"store.*"
@ -111,49 +114,190 @@ in {
store.idmail = {
type = "sqlite";
path = "${config.services.idmail.dataDir}/idmail.db";
query = let
# Remove comments from SQL and make it single-line
toSingleLineSql = sql:
lib.concatStringsSep " " (
lib.forEach (lib.flatten (lib.split "\n" sql)) (
line: lib.optionalString (builtins.match "^[[:space:]]*--.*" line == null) line
query =
let
# Remove comments from SQL and make it single-line
toSingleLineSql =
sql:
lib.concatStringsSep " " (
lib.forEach (lib.flatten (lib.split "\n" sql)) (
line: lib.optionalString (builtins.match "^[[:space:]]*--.*" line == null) line
)
);
in
{
# "SELECT name, type, secret, description, quota FROM accounts WHERE name = ?1 AND active = true";
name = toSingleLineSql ''
SELECT
m.address AS name,
'individual' AS type,
m.password_hash AS secret,
m.address AS description,
0 AS quota
FROM mailboxes AS m
JOIN domains AS d ON m.domain = d.domain
JOIN users AS u ON m.owner = u.username
WHERE m.address = ?1
AND m.active = true
AND d.active = true
AND u.active = true
'';
# "SELECT member_of FROM group_members WHERE name = ?1";
members = "";
# "SELECT name FROM emails WHERE address = ?1";
recipients = toSingleLineSql ''
-- It is important that we return only one value here, but in theory three UNIONed
-- queries are guaranteed to be distinct. This is because a mailbox address
-- and alias address can never be the same, their cross-table uniqueness is guaranteed on insert.
-- The catch-all union can also only return something if @domain.tld is given as a parameter,
-- which is invalid for aliases and mailboxes.
--
-- Nonetheless, it may be beneficial to allow an alias to override an existing mailbox,
-- so we can have send-only mailboxes which have their incoming mail redirected somewhere else.
-- Therefore, we make sure to order the query by (aliases -> mailboxes -> catch all) and only return the
-- highest priority one.
SELECT name FROM (
-- Select the target of a matching alias (if any)
-- but make sure that all related parts are active.
SELECT a.target AS name, 1 AS rowOrder
FROM aliases AS a
JOIN domains AS d ON a.domain = d.domain
JOIN (
-- To check whether the owner is active we need to make a subquery
-- because the owner could be a user or mailbox
SELECT username
FROM users
WHERE active = true
UNION
SELECT m.address AS username
FROM mailboxes AS m
JOIN users AS u ON m.owner = u.username
WHERE m.active = true
AND u.active = true
) AS u ON a.owner = u.username
WHERE a.address = ?1
AND a.active = true
AND d.active = true
-- Select the primary mailbox address if it matches and
-- all related parts are active.
UNION
SELECT m.address AS name, 2 AS rowOrder
FROM mailboxes AS m
JOIN domains AS d ON m.domain = d.domain
JOIN users AS u ON m.owner = u.username
WHERE m.address = ?1
AND m.active = true
AND d.active = true
AND u.active = true
-- Finally, select any catch_all address that would catch this.
-- Again make sure everything is active.
UNION
SELECT d.catch_all AS name, 3 AS rowOrder
FROM domains AS d
JOIN mailboxes AS m ON d.catch_all = m.address
JOIN users AS u ON m.owner = u.username
WHERE ?1 = ('@' || d.domain)
AND d.active = true
AND m.active = true
AND u.active = true
ORDER BY rowOrder, name ASC
LIMIT 1
)
);
in {
# "SELECT name, type, secret, description, quota FROM accounts WHERE name = ?1 AND active = true";
name = toSingleLineSql ''
SELECT
m.address AS name,
'individual' AS type,
m.password_hash AS secret,
m.address AS description,
0 AS quota
FROM mailboxes AS m
JOIN domains AS d ON m.domain = d.domain
JOIN users AS u ON m.owner = u.username
WHERE m.address = ?1
AND m.active = true
AND d.active = true
AND u.active = true
'';
# "SELECT member_of FROM group_members WHERE name = ?1";
members = "";
# "SELECT name FROM emails WHERE address = ?1";
recipients = toSingleLineSql ''
-- It is important that we return only one value here, but in theory three UNIONed
-- queries are guaranteed to be distinct. This is because a mailbox address
-- and alias address can never be the same, their cross-table uniqueness is guaranteed on insert.
-- The catch-all union can also only return something if @domain.tld is given as a parameter,
-- which is invalid for aliases and mailboxes.
--
-- Nonetheless, it may be beneficial to allow an alias to override an existing mailbox,
-- so we can have send-only mailboxes which have their incoming mail redirected somewhere else.
-- Therefore, we make sure to order the query by (aliases -> mailboxes -> catch all) and only return the
-- highest priority one.
SELECT name FROM (
-- Select the target of a matching alias (if any)
-- but make sure that all related parts are active.
SELECT a.target AS name, 1 AS rowOrder
'';
# "SELECT address FROM emails WHERE name = ?1 AND type != 'list' ORDER BY type DESC, address ASC";
emails = toSingleLineSql ''
-- Return first the primary address, then any aliases.
SELECT address FROM (
-- Select primary address, if active
SELECT m.address AS address, 1 AS rowOrder
FROM mailboxes AS m
JOIN domains AS d ON m.domain = d.domain
JOIN users AS u ON m.owner = u.username
WHERE m.address = ?1
AND m.active = true
AND d.active = true
AND u.active = true
-- Select any active aliases
UNION
SELECT a.address AS address, 2 AS rowOrder
FROM aliases AS a
JOIN domains AS d ON a.domain = d.domain
JOIN (
-- To check whether the owner is active we need to make a subquery
-- because the owner could be a user or mailbox
SELECT username
FROM users
WHERE active = true
UNION
SELECT m.address AS username
FROM mailboxes AS m
JOIN users AS u ON m.owner = u.username
WHERE m.active = true
AND u.active = true
) AS u ON a.owner = u.username
WHERE a.target = ?1
AND a.active = true
AND d.active = true
-- Select the catch-all marker, if we are the target.
UNION
-- Order 2 is correct, it counts as an alias
SELECT ('@' || d.domain) AS address, 2 AS rowOrder
FROM domains AS d
JOIN mailboxes AS m ON d.catch_all = m.address
JOIN users AS u ON m.owner = u.username
WHERE d.catch_all = ?1
AND d.active = true
AND m.active = true
AND u.active = true
ORDER BY rowOrder, address ASC
)
'';
# "SELECT address FROM emails WHERE address LIKE '%' || ?1 || '%' AND type = 'primary' ORDER BY address LIMIT 5";
verify = toSingleLineSql ''
SELECT m.address AS address
FROM mailboxes AS m
JOIN domains AS d ON m.domain = d.domain
JOIN users AS u ON m.owner = u.username
WHERE m.address LIKE '%' || ?1 || '%'
AND m.active = true
AND d.active = true
AND u.active = true
UNION
SELECT a.address AS address
FROM aliases AS a
JOIN domains AS d ON a.domain = d.domain
JOIN (
-- To check whether the owner is active we need to make a subquery
-- because the owner could be a user or mailbox
SELECT username
FROM users
WHERE active = true
UNION
SELECT m.address AS username
FROM mailboxes AS m
JOIN users AS u ON m.owner = u.username
WHERE m.active = true
AND u.active = true
) AS u ON a.owner = u.username
WHERE a.address LIKE '%' || ?1 || '%'
AND a.active = true
AND d.active = true
ORDER BY address
LIMIT 5
'';
# "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = ?1 AND l.type = 'list' ORDER BY p.address LIMIT 50";
# XXX: We don't actually expand, but return the same address if it exists since we don't support mailing lists
expand = toSingleLineSql ''
SELECT m.address AS address
FROM mailboxes AS m
JOIN domains AS d ON m.domain = d.domain
JOIN users AS u ON m.owner = u.username
WHERE m.address = ?1
AND m.active = true
AND d.active = true
AND u.active = true
UNION
SELECT a.address AS address
FROM aliases AS a
JOIN domains AS d ON a.domain = d.domain
JOIN (
@ -172,154 +316,16 @@ in {
WHERE a.address = ?1
AND a.active = true
AND d.active = true
-- Select the primary mailbox address if it matches and
-- all related parts are active.
UNION
SELECT m.address AS name, 2 AS rowOrder
FROM mailboxes AS m
JOIN domains AS d ON m.domain = d.domain
JOIN users AS u ON m.owner = u.username
WHERE m.address = ?1
AND m.active = true
AND d.active = true
AND u.active = true
-- Finally, select any catch_all address that would catch this.
-- Again make sure everything is active.
UNION
SELECT d.catch_all AS name, 3 AS rowOrder
FROM domains AS d
JOIN mailboxes AS m ON d.catch_all = m.address
JOIN users AS u ON m.owner = u.username
WHERE ?1 = ('@' || d.domain)
AND d.active = true
AND m.active = true
AND u.active = true
ORDER BY rowOrder, name ASC
LIMIT 1
)
'';
# "SELECT address FROM emails WHERE name = ?1 AND type != 'list' ORDER BY type DESC, address ASC";
emails = toSingleLineSql ''
-- Return first the primary address, then any aliases.
SELECT address FROM (
-- Select primary address, if active
SELECT m.address AS address, 1 AS rowOrder
FROM mailboxes AS m
JOIN domains AS d ON m.domain = d.domain
JOIN users AS u ON m.owner = u.username
WHERE m.address = ?1
AND m.active = true
AND d.active = true
AND u.active = true
-- Select any active aliases
UNION
SELECT a.address AS address, 2 AS rowOrder
FROM aliases AS a
JOIN domains AS d ON a.domain = d.domain
JOIN (
-- To check whether the owner is active we need to make a subquery
-- because the owner could be a user or mailbox
SELECT username
FROM users
WHERE active = true
UNION
SELECT m.address AS username
FROM mailboxes AS m
JOIN users AS u ON m.owner = u.username
WHERE m.active = true
AND u.active = true
) AS u ON a.owner = u.username
WHERE a.target = ?1
AND a.active = true
AND d.active = true
-- Select the catch-all marker, if we are the target.
UNION
-- Order 2 is correct, it counts as an alias
SELECT ('@' || d.domain) AS address, 2 AS rowOrder
FROM domains AS d
JOIN mailboxes AS m ON d.catch_all = m.address
JOIN users AS u ON m.owner = u.username
WHERE d.catch_all = ?1
AND d.active = true
AND m.active = true
AND u.active = true
ORDER BY rowOrder, address ASC
)
'';
# "SELECT address FROM emails WHERE address LIKE '%' || ?1 || '%' AND type = 'primary' ORDER BY address LIMIT 5";
verify = toSingleLineSql ''
SELECT m.address AS address
FROM mailboxes AS m
JOIN domains AS d ON m.domain = d.domain
JOIN users AS u ON m.owner = u.username
WHERE m.address LIKE '%' || ?1 || '%'
AND m.active = true
AND d.active = true
AND u.active = true
UNION
SELECT a.address AS address
FROM aliases AS a
JOIN domains AS d ON a.domain = d.domain
JOIN (
-- To check whether the owner is active we need to make a subquery
-- because the owner could be a user or mailbox
SELECT username
FROM users
WHERE active = true
UNION
SELECT m.address AS username
FROM mailboxes AS m
JOIN users AS u ON m.owner = u.username
WHERE m.active = true
AND u.active = true
) AS u ON a.owner = u.username
WHERE a.address LIKE '%' || ?1 || '%'
AND a.active = true
AND d.active = true
ORDER BY address
LIMIT 5
'';
# "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = ?1 AND l.type = 'list' ORDER BY p.address LIMIT 50";
# XXX: We don't actually expand, but return the same address if it exists since we don't support mailing lists
expand = toSingleLineSql ''
SELECT m.address AS address
FROM mailboxes AS m
JOIN domains AS d ON m.domain = d.domain
JOIN users AS u ON m.owner = u.username
WHERE m.address = ?1
AND m.active = true
AND d.active = true
AND u.active = true
UNION
SELECT a.address AS address
FROM aliases AS a
JOIN domains AS d ON a.domain = d.domain
JOIN (
-- To check whether the owner is active we need to make a subquery
-- because the owner could be a user or mailbox
SELECT username
FROM users
WHERE active = true
UNION
SELECT m.address AS username
FROM mailboxes AS m
JOIN users AS u ON m.owner = u.username
WHERE m.active = true
AND u.active = true
) AS u ON a.owner = u.username
WHERE a.address = ?1
AND a.active = true
AND d.active = true
ORDER BY address
LIMIT 50
'';
# "SELECT 1 FROM emails WHERE address LIKE '%@' || ?1 LIMIT 1";
domains = toSingleLineSql ''
SELECT domain
FROM domains
WHERE domain = ?1
'';
};
ORDER BY address
LIMIT 50
'';
# "SELECT 1 FROM emails WHERE address LIKE '%@' || ?1 LIMIT 1";
domains = toSingleLineSql ''
SELECT domain
FROM domains
WHERE domain = ?1
'';
};
};
storage = {
@ -428,7 +434,13 @@ in {
private-key = "%{file:/var/lib/stalwart-mail/dkim/ed25519-${domain}.key}%";
inherit domain;
selector = "ed_default";
headers = ["From" "To" "Date" "Subject" "Message-ID"];
headers = [
"From"
"To"
"Date"
"Subject"
"Message-ID"
];
algorithm = "ed25519-sha256";
canonicalization = "relaxed/relaxed";
set-body-length = false;
@ -438,7 +450,13 @@ in {
private-key = "%{file:/var/lib/stalwart-mail/dkim/rsa-${domain}.key}%";
inherit domain;
selector = "rsa_default";
headers = ["From" "To" "Date" "Subject" "Message-ID"];
headers = [
"From"
"To"
"Date"
"Subject"
"Message-ID"
];
algorithm = "rsa-sha256";
canonicalization = "relaxed/relaxed";
set-body-length = false;
@ -496,7 +514,7 @@ in {
services.nginx = {
upstreams.stalwart = {
servers."127.0.0.1:8080" = {};
servers."127.0.0.1:8080" = { };
extraConfig = ''
zone stalwart 64k;
keepalive 2;
@ -516,52 +534,60 @@ in {
};
};
}
// lib.genAttrs ["autoconfig.${primaryDomain}" "autodiscover.${primaryDomain}" "mta-sts.${primaryDomain}"] (_: {
forceSSL = true;
useACMEWildcardHost = true;
locations."/".proxyPass = "http://stalwart";
});
// lib.genAttrs
[
"autoconfig.${primaryDomain}"
"autodiscover.${primaryDomain}"
"mta-sts.${primaryDomain}"
]
(_: {
forceSSL = true;
useACMEWildcardHost = true;
locations."/".proxyPass = "http://stalwart";
});
};
systemd.services.stalwart-mail = let
cfg = config.services.stalwart-mail;
configFormat = pkgs.formats.toml {};
configFile = configFormat.generate "stalwart-mail.toml" cfg.settings;
in {
preStart = lib.mkAfter (
''
cat ${configFile} > /run/stalwart-mail/config.toml
cat ${config.age.secrets.stalwart-admin-hash.path} \
| tr -d '\n' > /run/stalwart-mail/admin-hash
systemd.services.stalwart-mail =
let
cfg = config.services.stalwart-mail;
configFormat = pkgs.formats.toml { };
configFile = configFormat.generate "stalwart-mail.toml" cfg.settings;
in
{
preStart = lib.mkAfter (
''
cat ${configFile} > /run/stalwart-mail/config.toml
cat ${config.age.secrets.stalwart-admin-hash.path} \
| tr -d '\n' > /run/stalwart-mail/admin-hash
mkdir -p /var/lib/stalwart-mail/dkim
''
# Generate DKIM keys if necessary
+ lib.concatLines (
lib.forEach (builtins.attrNames globals.mail.domains) (domain: ''
if [[ ! -e /var/lib/stalwart-mail/dkim/rsa-${domain}.key ]]; then
echo "Generating DKIM key for ${domain} (rsa)"
${lib.getExe pkgs.openssl} genrsa -traditional -out /var/lib/stalwart-mail/dkim/rsa-${domain}.key 2048
fi
if [[ ! -e /var/lib/stalwart-mail/dkim/ed25519-${domain}.key ]]; then
echo "Generating DKIM key for ${domain} (ed25519)"
${lib.getExe pkgs.openssl} genpkey -algorithm ed25519 -out /var/lib/stalwart-mail/dkim/ed25519-${domain}.key
fi
'')
)
);
mkdir -p /var/lib/stalwart-mail/dkim
''
# Generate DKIM keys if necessary
+ lib.concatLines (
lib.forEach (builtins.attrNames globals.mail.domains) (domain: ''
if [[ ! -e /var/lib/stalwart-mail/dkim/rsa-${domain}.key ]]; then
echo "Generating DKIM key for ${domain} (rsa)"
${lib.getExe pkgs.openssl} genrsa -traditional -out /var/lib/stalwart-mail/dkim/rsa-${domain}.key 2048
fi
if [[ ! -e /var/lib/stalwart-mail/dkim/ed25519-${domain}.key ]]; then
echo "Generating DKIM key for ${domain} (ed25519)"
${lib.getExe pkgs.openssl} genpkey -algorithm ed25519 -out /var/lib/stalwart-mail/dkim/ed25519-${domain}.key
fi
'')
)
);
serviceConfig = {
RuntimeDirectory = "stalwart-mail";
ReadWritePaths = [config.services.idmail.dataDir];
ExecStart = lib.mkForce [
""
"${cfg.package}/bin/stalwart-mail --config=/run/stalwart-mail/config.toml"
];
RestartSec = "60"; # Retry every minute
CacheDirectory = lib.trace "remove stalwart cache soon, it's upstream" "stalwart-mail";
serviceConfig = {
RuntimeDirectory = "stalwart-mail";
ReadWritePaths = [ config.services.idmail.dataDir ];
ExecStart = lib.mkForce [
""
"${cfg.package}/bin/stalwart-mail --config=/run/stalwart-mail/config.toml"
];
RestartSec = "60"; # Retry every minute
CacheDirectory = lib.trace "remove stalwart cache soon, it's upstream" "stalwart-mail";
};
};
};
systemd.services.backup-mail = {
description = "Mail backup";
@ -575,15 +601,17 @@ in {
Type = "oneshot";
User = "stalwart-mail";
Group = "stalwart-mail";
ExecStart = lib.getExe (pkgs.writeShellApplication {
name = "backup-mail";
runtimeInputs = [pkgs.sqlite];
text = ''
sqlite3 "$STALWART_DATA/database.sqlite3" ".backup '$BACKUP_DIR/database.sqlite3'"
sqlite3 "$IDMAIL_DATA/database.sqlite3" ".backup '$BACKUP_DIR/idmail.db'"
cp -r "$STALWART_DATA/dkim" "$BACKUP_DIR/"
'';
});
ExecStart = lib.getExe (
pkgs.writeShellApplication {
name = "backup-mail";
runtimeInputs = [ pkgs.sqlite ];
text = ''
sqlite3 "$STALWART_DATA/database.sqlite3" ".backup '$BACKUP_DIR/database.sqlite3'"
sqlite3 "$IDMAIL_DATA/database.sqlite3" ".backup '$BACKUP_DIR/idmail.db'"
cp -r "$STALWART_DATA/dkim" "$BACKUP_DIR/"
'';
}
);
ReadWritePaths = [
dataDir
config.services.idmail.dataDir
@ -591,8 +619,8 @@ in {
];
Restart = "no";
};
requiredBy = ["restic-backups-storage-box-dusk.service"];
before = ["restic-backups-storage-box-dusk.service"];
requiredBy = [ "restic-backups-storage-box-dusk.service" ];
before = [ "restic-backups-storage-box-dusk.service" ];
};
# Needed so we don't run out of tmpfs space for large backups.
@ -608,6 +636,6 @@ in {
backups.storageBoxes.dusk = {
subuser = "stalwart";
paths = [mailBackupDir];
paths = [ mailBackupDir ];
};
}