the_prg_server_configuratio.../nix-system-configs/modules/system/mail-server.nix

188 lines
6.8 KiB
Nix

{
config,
pkgs,
lib,
...
}: let
choose = paths: lib.findFirst builtins.pathExists null paths;
# Read secrets from sops-managed files and trim trailing newlines so the
# 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.
# 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 {
type = lib.types.str;
default = "nixos-default";
description = "System hostname";
};
username = lib.mkOption {
type = lib.types.str;
default = "user";
description = "Primary user username";
};
userDescription = lib.mkOption {
type = lib.types.str;
default = "NixOS User";
description = "Primary user description";
};
address = lib.mkOption {
type = lib.types.str;
default = "10.1.1.100";
description = "Static IP address";
};
};
imports = lib.filter (x: x != null) [
(choose [./modules/desktop-manager/sway_greetd_homemanager.nix ../desktop-manager/sway_greetd_homemanager.nix])
(choose [./modules/local/hostname_username.nix ../local/hostname_username.nix])
(choose [./modules/local/networking_local.nix ../local/networking_local.nix])
(choose [./modules/bootloader/seabios-assigned-proxmox-at-birth.nix ../bootloader/seabios-assigned-proxmox-at-birth.nix])
(choose [./modules/lix-default.nix ../lix-default.nix])
(choose [./modules/secrets-config/sops-the-blank-system.nix ../secrets-config/sops-the-blank-system.nix])
(choose [./modules/toolsets/grafana_metric.nix ../toolsets/grafana_metric.nix])
(choose [./modules/secrets-config/sops-mail.nix ../secrets-config/sops-mail.nix])
];
config = {
# Create /etc/stalwart secret files so the generated TOML can reference them
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)
"stalwart/acme-secret".mode = "0777";
"stalwart/cloudflare-username".text = cloudflareUsername; # contact email for ACME
"stalwart/cloudflare-username".mode = "0777";
};
systemd.tmpfiles.rules = [
# z = create/modify file or directory, set mode and owner
"z /etc/stalwart 0555 root root - -"
];
# Enable Tailscale for remote access to Traefik dashboard and configuration
services.tailscale.enable = true;
local.hostname = "nixos-mailserver";
local.username = "mailprg";
local.userDescription = "NixOS PRG Mailing Service";
local.address = "10.1.1.15";
system.stateVersion = "25.11";
services.stalwart-mail = {
enable = true;
openFirewall = true;
settings = {
server = {
hostname = "mail.prg-radio.org";
tls = {
enable = true;
implicit = true;
};
listener = {
smtp = {
protocol = "smtp";
# Use port 587 for SMTP submission with STARTTLS, as port 25 is often blocked by ISPs for outgoing mail to prevent spam.
# This way, you can still receive mail on port 25 but require authenticated submission on port 587.
bind = "[::]:25"; # I am so fucking fed up this shit, this fucking ISP is sending me to early grave.
};
submissions = {
bind = "[::]:465";
protocol = "smtp";
tls.implicit = true;
};
imaps = {
bind = "[::]:993";
protocol = "imap";
tls.implicit = true;
};
jmap = {
bind = "[::]:8080";
url = "https://mail.prg-radio.org";
protocol = "http";
};
management = {
bind = ["127.0.0.1:8080" "[::]:8081"];
protocol = "http";
};
};
};
lookup.default = {
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}%";
};
session.auth = {
mechanisms = ["plain"];
directory = "in-memory";
};
storage.directory = "in-memory";
session.rcpt.directory = "in-memory";
directory."imap".lookup.domains = ["prg-radio.org"];
directory."in-memory" = {
type = "memory";
principals = [
{
class = "individual";
name = "Polyteknisk Radiogruppe Board Member";
secret = "%{file:/etc/stalwart/mail-pw1}%";
email = ["board@prg-radio.org"];
}
{
class = "individual";
name = "postmaster";
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 = {
user = "admin";
secret = "%{file:/etc/stalwart/admin-pw}%";
};
};
};
networking.firewall.allowedTCPPorts = [
25
];
networking.firewall.allowedUDPPorts = [
25
];
};
}