feat: stalwart mail-server

This commit is contained in:
danny 2025-09-17 15:24:07 +08:00
parent 85feeb7b3f
commit a565033341
16 changed files with 1192 additions and 442 deletions

View file

@ -59,6 +59,33 @@ in
];
})
(import ../../modules/stalwart.nix {
enableNginx = true;
domain = "pre7780.dn";
adminPassFile = config.sops.secrets."stalwart/adminPassword".path;
dbPassFile = config.sops.secrets."stalwart/db".path;
acmeConf = {
directory = "https://ca.net.dn/acme/acme/directory";
ca_bundle = "${"" + ../../extra/ca.crt}";
challenge = "dns-01";
origin = "pre7780.dn";
contact = "admin@pre7780.dn";
domains = [
"pre7780.dn"
"mx1.pre7780.dn"
];
default = true;
provider = "rfc2136-tsig";
host = "10.0.0.1";
renew-before = "1d";
port = 5359;
cache = "${config.services.stalwart-mail.dataDir}/acme";
key = "stalwart";
tsig-algorithm = "hmac-sha512";
secret = "%{file:${config.sops.secrets."stalwart/tsig".path}}%";
};
})
../../modules/davinci-resolve.nix
../../modules/webcam.nix
../../modules/postgresql.nix

View file

@ -1,13 +1,20 @@
{ ... }:
{ config, ... }:
{
networking.firewall.allowedTCPPorts = [
443
80
];
security.acme = {
acceptTerms = true;
defaults = {
validMinDays = 2;
server = "https://10.0.0.1:${toString 8443}/acme/acme/directory";
server = "https://ca.net.dn/acme/acme/directory";
renewInterval = "daily";
email = "danny@net.dn";
webroot = "/var/lib/acme/acme-challenge";
dnsProvider = "pdns";
dnsPropagationCheck = false;
environmentFile = config.sops.secrets."acme/pdns".path;
};
};

View file

@ -8,6 +8,12 @@ openldap:
adminPassword: ENC[AES256_GCM,data:jEGuzgs5QTWfdyJenC3t3g==,iv:StfFOcvbDapnma6eAlpaGiBWnqiD3I/wfQsMBzufol0=,tag:892q7N4KrsSQoZYGy6CQrA==,type:str]
lam:
env: ENC[AES256_GCM,data:f1LlC/VvilH8o2Ra7MrSHsMEGlGw3LOV2O9JJf9f,iv:u7cXM8n3jJeLBfxXtA0QMyijBqTcC+yJeW/OO9JuZMI=,tag:QL5FkcCPI5Gxudi0NmCZWg==,type:str]
stalwart:
adminPassword: ENC[AES256_GCM,data:6tUL7b2s3gLtF4Ors9CgYQ==,iv:9UQowgXKr9HR/poELP6SZijp3c2HVTHzEfwf1tZI/3w=,tag:KIOiYEwLsZLH31E2Xb478A==,type:str]
tsig: ENC[AES256_GCM,data:wxsM/dbkW2fNf86b6TsLRNAce19h7mBEuSzFT84aIlaVZA/S29g1U4/CAwD4b+h/XfBgpZQCJf/9yT3yo6dbGAIAk5UgjV2cNY9pO1/uF1T6xoKDgfRZxA==,iv:9BvP8vQkTTEaNgYUPfQcfEMcWqDyD045EPBr7NyHmO4=,tag:coBBAe62kpe/L0S6V8NhXg==,type:str]
db: ENC[AES256_GCM,data:ZRZ2ZzUotYMe2GfkMS7o7dz0aGg=,iv:ys6ogueueESp0y6A+hUG9zTnqmCVobuIzyqA4WVtewo=,tag:p74G+8XhMcpgDnIfh1aXTg==,type:str]
acme:
pdns: ENC[AES256_GCM,data:+InGSnaGIFVtDRlVltzWbZfquzodHUQrPeMRBnVNB9mrajlKr5dFK6DD8dXAvN7UjZFBfrgZefOPkmLR2ncLXGOV2Kl7jorVw50Y0f0iKl7mqwHaZKaQdk7cpGDkCrt/LvfbP9x7gVrs6pQpsU+c/P5rbBLRyejchh/WtiyzgowYIJxYohggeG09+l7YI3FR6U5wiymIRISpNBGEhwG0q17qdAhdtc49qP/K,iv:JcSlxAwHwU528S7iSpAnSbUZw7iO+LMjR3qGwRHp+Zk=,tag:twf2WOQb/yZ3GtN/hlikMA==,type:str]
sops:
age:
- recipient: age1uvsvf5ljaezh5wze32p685kfentyle0l2mvysc67yvgct2h4850qqph9lv
@ -19,7 +25,7 @@ sops:
MEdmWkFwNXZoR1ZVRnQ0aWlkYzZwSmsK0EFecUIdqlDKX08oRCoDQQ3QCX1wzb8w
lghDJhWlfuKr+X24GoE4UK04aJVLqVMRRI4BJW+LQXeHS+dWKu3mQA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-06-15T08:18:50Z"
mac: ENC[AES256_GCM,data:sq+/fpOeNO5wn9S1kFqzRy6xCOVkSBcAkral7MTn4UxRebBDa78KF76Nsba0+o5bzwCchoGl/TC6vySIzGq8FUYwd1tQ9nH5DlqYBVVRgRlKLRyhxXf14BTyYgzHzFuRWdFyY8i4j0flZtlDHk4dVQrE4OhHvhLQ2Zvet5HQ20I=,iv:qoPZ+8tAHJxcR53M2PNwukYgdguSRrAVB+FtKYbf+aM=,tag:FYaPzh6o0ZI27Ul5jEhgVg==,type:str]
lastmodified: "2025-09-16T04:39:12Z"
mac: ENC[AES256_GCM,data:yRVAJz73AqlBm6fxeTehfSqlTLyRYIsPjC/5igpnGC8URUiK66SUtHJSE3196AaPV+CWJrxrXfNWoCmZsP85Rr5V9nw31ZF1boaAc0YzRQBxVmBBlAK7+9Z5KADShAetYNwk9qtCrXd6S8mCwmZjNJaN/Rthy3hchxzAB0/79R4=,iv:QeNUZfmnCx4QF/0wjU/JJRu6umNFC/weW2BJx+7OaPo=,tag:KsityLnPYhugFL4c6wrs6Q==,type:str]
unencrypted_suffix: _unencrypted
version: 3.10.2

View file

@ -15,6 +15,36 @@
group = config.services.dovecot2.group;
mode = "0660";
};
"stalwart/adminPassword" =
let
inherit (config.users.users.stalwart-mail) name group;
in
lib.mkIf config.services.stalwart-mail.enable {
inherit group;
owner = name;
};
"stalwart/tsig" =
let
inherit (config.users.users.stalwart-mail) name group;
in
lib.mkIf config.services.stalwart-mail.enable {
inherit group;
owner = name;
};
"stalwart/db" =
let
inherit (config.users.users.stalwart-mail) name group;
in
lib.mkIf config.services.stalwart-mail.enable {
inherit group;
owner = name;
};
"acme/pdns" = {
mode = "0660";
owner = "acme";
group = "acme";
};
};
};
}

View file

@ -64,6 +64,14 @@
locations."/".proxyPass = "http://10.0.0.130:8001/phone.html";
};
"ca.net.dn" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "https://10.0.0.1:8443/";
};
};
};
};
}

View file

@ -33,11 +33,20 @@ sops:
- recipient: age1z6f643a6vqm7cqh6fna5dhmxfkgwxgqy8kg9s0vf9uxhaswtngtspmqsjw
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuKzJObXlPVUJzUkEyZXlV
Q0tEbzBPTy9kUXIwVmJkckUyWklUMzhCcTE0Ckh3bXIwRkpESTJYeTBPMGhQYk9y
L2NQTWFuMWVqYzJHZGhTaHpDRE5CRGMKLS0tIEsybHdPMk9JeEM2cXFwdlpOeXRj
Qm0wbmNGZDZwZlNTOVl0WVh5RXNxK2cK1Fwbgl5kKAFyrIIhBP+X4ZKFS4Xl39QY
11qkglNgro/JBFJ/W7Hj5wtEd8QToiJM1RW0lQaI25sneQ2v6L5pDA==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuYWpiZ0h3VURrcW0rV0Vj
SFJwMlRUMHUyS1FGTEo3cHZJc1Z6a3FWbmtRCkdoZXhwOGJQNlV2dU8wRFRMUHVv
QzhxU3RiVHl5UVpUNk10S2VRVy95OHMKLS0tIE9zbUNUU3ZINU1JNGtmd2trS2tI
d3YxREtHcTBJYU1sNU9vMGZTUGh6NXMKtGKMnnamCAeftkQ0+Ygb/yg1NdyKDz1W
UjYvW2PYKzkx8IWmIgzdAI3fWDOiE7tmBTMlX9C3/2PKR6dCc/a+SQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1uvsvf5ljaezh5wze32p685kfentyle0l2mvysc67yvgct2h4850qqph9lv
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxc3pna3R3aG85bmt2WERa
aG9TaDBKTlNMTUVwaFlIdkV0UmFJQStYSHdvCmNuYWJpN2M3QjRkV2s0MHJ4TzZP
ZkhKc0xPUFBrblVFR1U4SUdjYzQ2cm8KLS0tIDVuNW9tRGoxanVKOUJYa2QwNFNz
OTRiU0cxeXp5K1FjaWRGTnBHcnpUYmcKVVlueEj/DELe9Xi9iaBddpPPRmoUmD48
wyjtlvKzS20zishE/D7GkHZ2ZdNsLD3AOnYZ6r6ATAndssC2YT/SXA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-08-29T07:40:00Z"
mac: ENC[AES256_GCM,data:QeQ5NOrcq3uNmt+MiVF+Jr3JWWBNGPw5A8pSdd1WR426WWqHTRP7NHAaVbS3st9VSmoYY5NI6JKeizuAq/NCvzOZL3Idy9mP+3HD9VZwn1GNSEGfhn+KZT02AY0JHq29KxcZlAYiWZOL4p+blG2aWfGm9+zy1GHoEXoo3OVhaEg=,iv:8uIoOE0ZJZYGZoQaskCXQKr7vl6wjsmJ4iudhvtgqtY=,tag:fRLGalP92dDyF8q+zT97BQ==,type:str]

View file

@ -4,7 +4,6 @@
username,
...
}:
let
inherit username;
@ -13,10 +12,10 @@ let
sshPortsString = builtins.concatStringsSep ", " (builtins.map (p: builtins.toString p) sshPorts);
personal = {
inherit (config.networking) domain;
ip = "10.0.0.1/24";
interface = "wg0";
port = 51820;
domain = config.networking.domain;
range = "10.0.0.0/24";
full = "10.0.0.1/25";
restrict = "10.0.0.128/25";
@ -160,11 +159,13 @@ in
kube.port
25565
kube.masterAPIServerPort
5359
];
allowedTCPPorts = sshPorts ++ [
53
25565
kube.masterAPIServerPort
5359
];
};
@ -237,8 +238,7 @@ in
listenPort = personal.port;
privateKeyFile = config.sops.secrets."wireguard/privateKey".path;
peers = builtins.map (r: {
publicKey = r.publicKey;
allowedIPs = r.allowedIPs;
inherit (r) publicKey allowedIPs;
}) (fullRoute ++ meshRoute);
};
@ -254,31 +254,31 @@ in
extraHosts = "${kube.masterIP} ${kube.masterHostname}";
};
services.postgresql = {
enable = lib.mkDefault true;
authentication = ''
host powerdnsadmin powerdnsadmin 127.0.0.1/32 trust
'';
ensureUsers = [
{
name = "powerdnsadmin";
ensureDBOwnership = true;
}
{
name = "pdns";
ensureDBOwnership = true;
}
];
ensureDatabases = [
"powerdnsadmin"
"pdns"
];
};
services = {
dbus.enable = true;
blueman.enable = true;
postgresql = {
enable = lib.mkDefault true;
authentication = ''
host powerdnsadmin powerdnsadmin 127.0.0.1/32 trust
'';
ensureUsers = [
{
name = "powerdnsadmin";
ensureDBOwnership = true;
}
{
name = "pdns";
ensureDBOwnership = true;
}
];
ensureDatabases = [
"powerdnsadmin"
"pdns"
];
};
openssh = {
enable = true;
ports = sshPorts;
@ -293,6 +293,7 @@ in
enable = true;
extraConfig = ''
launch=gpgsql
loglevel=6
webserver-password=$WEB_PASSWORD
api=yes
api-key=$WEB_PASSWORD
@ -302,6 +303,8 @@ in
webserver=yes
webserver-port=8081
local-port=5359
dnsupdate=yes
allow-dnsupdate-from=10.0.0.0/24
'';
secretFile = config.sops.secrets.powerdns.path;
};
@ -310,6 +313,7 @@ in
enable = true;
forwardZones = {
"${config.networking.domain}." = "127.0.0.1:5359";
"pre7780.dn." = "127.0.0.1:5359";
};
forwardZonesRecurse = {
"." = "8.8.8.8";
@ -380,11 +384,16 @@ in
};
};
systemd.services.raspamd-trainer = {
after = [ "pdns-recursor.service" ];
};
services.nginx.virtualHosts = {
"powerdns.${config.networking.domain}" = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://localhost:8000";
locations."/api".proxyPass = "http://127.0.0.1:8081";
locations."/".proxyPass = "http://127.0.0.1:8000";
};
"uptime.${config.networking.domain}" = {

View file

@ -41,7 +41,6 @@ in
postRun = ''
systemctl restart postfix.service
systemctl restart dovecot.service
systemctl restart rspamd-trainer.service
'';
};
"${cfg.domain}" = {

118
system/modules/stalwart.nix Normal file
View file

@ -0,0 +1,118 @@
{
adminPassFile,
dbPassFile,
domain ? null,
acmeConf ? null,
enableNginx ? true,
}:
{
config,
lib,
...
}:
let
inherit (lib) mkIf;
in
{
services.postgresql = {
enable = true;
ensureDatabases = [
"stalwart"
];
ensureUsers = [
{
name = "stalwart";
ensureDBOwnership = true;
}
];
};
services.stalwart-mail = {
enable = true;
openFirewall = true;
settings = {
server = {
hostname = if (domain != null) then "mx1.${domain}" else config.networking.fqdn;
auto-ban.scan.rate = "1000/1d";
tls = {
enable = true;
implicit = true;
};
listener = {
smtp = {
protocol = "smtp";
bind = "[::]:25";
};
submissions = {
protocol = "smtp";
bind = "[::]:465";
tls.implicit = true;
};
imaps = {
protocol = "imap";
bind = "[::]:993";
tls.implicit = true;
};
management = {
protocol = "http";
bind = [ "127.0.0.1:8080" ];
};
};
};
lookup.default = {
hostname = "mx1.${domain}";
domain = "${domain}";
};
acme."step-ca" = mkIf (acmeConf != null) acmeConf;
session.auth = {
mechanisms = "[plain]";
directory = "'in-memory'";
require = true;
allow-plain-text = true;
};
storage.data = "db";
store."db" = {
type = "postgresql";
host = "localhost";
port = 5432;
database = "stalwart";
user = "stalwart";
password = "%{file:${dbPassFile}}%";
};
directory = {
"imap".lookup.domains = [ domain ];
"in-memory" = {
type = "memory";
principals = [
{
name = "admin";
class = "admin";
secret = "%{file:${adminPassFile}}%";
email = [ "admin@${domain}" ];
}
];
};
};
authentication.fallback-admin = {
user = "admin";
secret = "%{file:${adminPassFile}}%";
};
tracer."stdout" = {
enable = true;
type = "console";
level = "debug";
};
};
};
services.nginx = mkIf enableNginx {
enable = true;
virtualHosts = {
"mail.${domain}" = {
locations."/".proxyPass = "http://127.0.0.1:8080";
enableACME = true;
forceSSL = true;
};
};
};
}