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:
parent
deca311c68
commit
7ccd7856ee
162 changed files with 4750 additions and 3718 deletions
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}" ];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 ];
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue