Add utility mail account and password; improve secret handling for dry-run in the most hecking scuffed arse secret management in the mail server configuration.

This commit is contained in:
Root User 2026-02-16 17:29:42 +01:00
parent 27610eca0f
commit 908c90ff49
Signed by: root
GPG key ID: 087F0A95E5766D72
4 changed files with 54 additions and 20 deletions

View file

@ -42,6 +42,13 @@
mode = "0400";
};
sops.secrets."utility-password" = {
format = "yaml";
sopsFile = ../../secrets/mail/secrets.yaml;
owner = "root";
mode = "0400";
};
sops.secrets."cloudflare-dns-token" = {
format = "yaml";
sopsFile = ../../secrets/mail/secrets.yaml;

View file

@ -9,10 +9,17 @@
# generated stalwart config contains the actual secret values (not the
# literal "$FOO" placeholders). Using builtins.readFile here ensures the
# values are placed into the TOML at build time.
adminPassword = builtins.replaceStrings ["\n"] [""] (builtins.readFile config.sops.secrets."admin-password".path);
boardPassword = builtins.replaceStrings ["\n"] [""] (builtins.readFile config.sops.secrets."board-member-password".path);
cloudflareUsername = builtins.replaceStrings ["\n"] [""] (builtins.readFile config.sops.secrets."cloudflare-username".path);
cloudflareToken = builtins.replaceStrings ["\n"] [""] (builtins.readFile config.sops.secrets."cloudflare-dns-token".path);
# Use a safe helper that handles missing secret attributes or missing files
# during a dry-run (returns empty string if absent).
getSecret = secretAttr:
if secretAttr != null && builtins.pathExists secretAttr.path
then builtins.replaceStrings ["\n"] [""] (builtins.readFile secretAttr.path)
else "";
adminPassword = getSecret config.sops.secrets."admin-password";
boardPassword = getSecret config.sops.secrets."board-member-password";
utilityPassword = getSecret config.sops.secrets."utility-password";
cloudflareUsername = getSecret config.sops.secrets."cloudflare-username";
cloudflareToken = getSecret config.sops.secrets."cloudflare-dns-token";
in {
options.local = {
hostname = lib.mkOption {
@ -53,6 +60,8 @@ in {
environment.etc = {
"stalwart/mail-pw1".text = boardPassword; # principal password (board)
"stalwart/mail-pw1".mode = "0777";
"stalwart/mail-pw2".text = utilityPassword; # principal password (utility)
"stalwart/mail-pw2".mode = "0777";
"stalwart/admin-pw".text = adminPassword; # admin fallback password
"stalwart/admin-pw".mode = "0777";
"stalwart/acme-secret".text = cloudflareToken; # API token for ACME (Cloudflare)
@ -117,14 +126,14 @@ in {
hostname = "mail.prg-radio.org";
domain = "prg-radio.org";
};
acme."letsencrypt" = {
directory = "https://acme-v02.api.letsencrypt.org/directory";
challenge = "dns-01";
# reference the contact and secret via files under /etc/stalwart
contact = "%{file:/etc/stalwart/cloudflare-username}%";
domains = ["prg-radio.org" "mailadmin.prg-radio.org" "mail.prg-radio.org"];
provider = "cloudflare";
secret = "%{file:/etc/stalwart/acme-secret}%";
acme."letsencrypt" = {
directory = "https://acme-v02.api.letsencrypt.org/directory";
challenge = "dns-01";
# reference the contact and secret via files under /etc/stalwart
contact = "%{file:/etc/stalwart/cloudflare-username}%";
domains = ["prg-radio.org" "mailadmin.prg-radio.org" "mail.prg-radio.org"];
provider = "cloudflare";
secret = "%{file:/etc/stalwart/acme-secret}%";
};
session.auth = {
mechanisms = ["plain"];
@ -148,6 +157,18 @@ in {
secret = "%{file:/etc/stalwart/mail-pw1}%";
email = ["postmaster@prg-radio.org"];
}
{
class = "individual";
name = "no-reply";
secret = "%{file:/etc/stalwart/mail-pw2}%";
email = ["no-reply@prg-radio.org"];
}
{
class = "individual";
name = "service";
secret = "%{file:/etc/stalwart/mail-pw2}%";
email = ["service@prg-radio.org"];
}
];
};
authentication.fallback-admin = {
@ -157,11 +178,11 @@ in {
};
};
networking.firewall.allowedTCPPorts = [
25
];
networking.firewall.allowedUDPPorts = [
25
];
networking.firewall.allowedTCPPorts = [
25
];
networking.firewall.allowedUDPPorts = [
25
];
};
}

View file

@ -0,0 +1,5 @@
## Mail Server
Todo: Resolve the issue with the Three / 3 about the IP PTR Record mismatch where the mail.prg-radio.org off the IP
record is XXX.XXX.XXX.XXX.mobile.3.dk and not XXX.XXX.XXX.XXX, which hecks up the secure reverse DNS lookup.
Current solution uses SMPT2Go, which has limited outbound mail limits.

View file

@ -2,6 +2,7 @@ admin-password: ENC[AES256_GCM,data:036xX0rMzmQn0iFBYEU2u6Xa,iv:fsWHfakdNdNf8r0P
board-member-password: ENC[AES256_GCM,data:e8jdc6FVBwB2Ieqty9s4d/aASdY=,iv:E09RhJOB84ad+L7zRXROLGD1RgDUYe/DOJCbz/zFN08=,tag:fboTZGEnrBz5pMGqwIXSTA==,type:str]
cloudflare-dns-token: ENC[AES256_GCM,data:IpgU7An3IW/LFL+5OJ3oYH4c3eZjZFP+qK8/oFsNCorYKVaWPVOIVA==,iv:49UwRT8DfbC9ZIXgx7nCSxjHeIIAkiHD70ti3rWUexA=,tag:Vn9wVUhTgRlGG9E+i9NzGw==,type:str]
cloudflare-username: ENC[AES256_GCM,data:AMgWBFP90f1ML/I6es0HIUoW,iv:64GGNMSdTrmurEsgdI82iTqD9FUW8leKu/JvT7Ls6Og=,tag:3rTWNF3NfTExCnV+RRwyVA==,type:str]
utility-password: ENC[AES256_GCM,data:aabQwXskJZvIZcI+XcxbbMyVLulpsOdm4nVUn971Tkw6sggMF+VJ91gvlfgkWo2Ttlk=,iv:AkUnFoZqZB1EZgFDBy5Gtg3ryX+VjA/8Ku9rpo6309M=,tag:3Rgsd3AQg+4+uXH/1qE5pQ==,type:str]
sops:
age:
- recipient: age1746rvsvsc3snxfl7cndm222wd5kck4aqj3x7nednlegq0gdjhfcqx0qv7m
@ -22,7 +23,7 @@ sops:
OFE3aWxZNThlWUUrUWlwZmtGYjJGT2sKFkoNZt6ThwzwQ2MMFjncrVrLKEhJ1hxh
uJuOfYFlQI80k3etChD64mTRMSK7Cr/BIc2625+jGJK4kOc+JpFDEQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-15T17:53:57Z"
mac: ENC[AES256_GCM,data:ZdDyFxbaUq/FPSe6z3+FhRJ6GeG4YjGc0fIVof/Sf/gsfKwZAStZs3CKxZtJXxUGBtkw93aguYjnNfxjUH+YxTemZPhWfJZo0pcR9YwWzXvB9fRLM95v/TzWY9qzqqbqVUSz+2SSfUJJG2+dKuzEzA3c1lE1pwOFXdfWOm0+uCE=,iv:mQp+yIVMDBY3tAPlDShk3IlIJ3cb4zrTn18x4HY+YsI=,tag:QLq34VwtxN5t66aE31LcYA==,type:str]
lastmodified: "2026-02-16T15:58:08Z"
mac: ENC[AES256_GCM,data:surdjriMXx9yA1wdNANPdl4p/1M3vli/COQx7D1CDrO6T3egHGQ16z1AO4OsS7rTxE2+jiGrwH0mWtW+pAHNfLKZmSJZ22NNwYFtFsh+UeCGCau/CVXJG6LBpI3lOBfq6PXKiv/H7haqcPjtLDSgthu6Vnyr+NnJ6V6rYYqK6B0=,iv:AHv6OqluBVPyNk+dB5ZcDB4Mb67klKCTB6z0fCMC6Go=,tag:QQSAQKm9Cm059zdfAgJ6Zg==,type:str]
unencrypted_suffix: _unencrypted
version: 3.11.0