From 908c90ff49b9c744553aefc266793a68456aeee6 Mon Sep 17 00:00:00 2001 From: Christine Elisabeth Koppel Date: Mon, 16 Feb 2026 17:29:42 +0100 Subject: [PATCH] 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. --- .../modules/secrets-config/sops-mail.nix | 7 +++ .../modules/system/mail-server.nix | 57 +++++++++++++------ .../modules/system/system_list.md | 5 ++ nix-system-configs/secrets/mail/secrets.yaml | 5 +- 4 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 nix-system-configs/modules/system/system_list.md diff --git a/nix-system-configs/modules/secrets-config/sops-mail.nix b/nix-system-configs/modules/secrets-config/sops-mail.nix index 499d620..d8f0c18 100644 --- a/nix-system-configs/modules/secrets-config/sops-mail.nix +++ b/nix-system-configs/modules/secrets-config/sops-mail.nix @@ -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; diff --git a/nix-system-configs/modules/system/mail-server.nix b/nix-system-configs/modules/system/mail-server.nix index 4ab5c72..6eee68d 100644 --- a/nix-system-configs/modules/system/mail-server.nix +++ b/nix-system-configs/modules/system/mail-server.nix @@ -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 + ]; }; } diff --git a/nix-system-configs/modules/system/system_list.md b/nix-system-configs/modules/system/system_list.md new file mode 100644 index 0000000..afd3538 --- /dev/null +++ b/nix-system-configs/modules/system/system_list.md @@ -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. \ No newline at end of file diff --git a/nix-system-configs/secrets/mail/secrets.yaml b/nix-system-configs/secrets/mail/secrets.yaml index fc3b5c2..697be96 100644 --- a/nix-system-configs/secrets/mail/secrets.yaml +++ b/nix-system-configs/secrets/mail/secrets.yaml @@ -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