Compare commits

..

No commits in common. "cab31335ff47090ad0703a6b2a671c05917b9e9d" and "fe289e0600699af0547016334cf251cc0ab9828c" have entirely different histories.

6 changed files with 48 additions and 130 deletions

View file

@ -5,7 +5,6 @@ keys:
- &server_database age1k9ddvzypz986a7dt403ja6evql2agz0gehll79mx64zceteya38smxph8m - &server_database age1k9ddvzypz986a7dt403ja6evql2agz0gehll79mx64zceteya38smxph8m
- &server_build_machine age1xtsm7kmql8794756ls53eu8pusyadk4lauadxx867tgsjhd38y5sj7elca - &server_build_machine age1xtsm7kmql8794756ls53eu8pusyadk4lauadxx867tgsjhd38y5sj7elca
- &server_christine age1kzsrlz86y5nqe4vaufv8chupq2hrf3avlmxsp3d5p9t5xj593deszslkmp - &server_christine age1kzsrlz86y5nqe4vaufv8chupq2hrf3avlmxsp3d5p9t5xj593deszslkmp
- &server_mail age1pkn38zzyckxqgeud7s7y3uuhd7pau3csh56rn3ue4altzkressdsaxs4et
creation_rules: creation_rules:
- path_regex: nix-system-configs/secrets/songsheet/[^/]+\.(yaml|json|env|ini)$ - path_regex: nix-system-configs/secrets/songsheet/[^/]+\.(yaml|json|env|ini)$
key_groups: key_groups:
@ -40,4 +39,3 @@ creation_rules:
key_groups: key_groups:
- age: - age:
- *admin_christine - *admin_christine
- *server_mail

View file

@ -1,11 +1,3 @@
## Todo, make a proper script for this ## Todo, make a proper script for this
EDITOR=micro nix-shell -p sops --run "SOPS_AGE_KEY_FILE=$HOME/.config/sops/age/keys.txt sops nix-system-configs/secrets/songsheet/secrets.yaml" EDITOR=micro nix-shell -p sops --run "SOPS_AGE_KEY_FILE=$HOME/.config/sops/age/keys.txt sops nix-system-configs/secrets/songsheet/secrets.yaml"
compose2nix --inputs nix-system-configs/modules/songsheet/wavelog/docker-compose.yml --sops_file nix-system-configs/secrets/songsheet/secrets.yaml --project=wavelog --output nix-system-configs/modules/songsheet/wavelog/docker-compose.nix --root_path nix-system-configs/modules/songsheet/wavelog compose2nix --inputs nix-system-configs/modules/songsheet/wavelog/docker-compose.yml --sops_file nix-system-configs/secrets/songsheet/secrets.yaml --project=wavelog --output nix-system-configs/modules/songsheet/wavelog/docker-compose.nix --root_path nix-system-configs/modules/songsheet/wavelog
# Todo make a proper script for this:
NIXOS_CONFIG="/home/nixosbm/the_prg_server_configuration/nix-system-configs/modules/system/mail-server.nix" nixos-rebuild build-image --image-variant proxmox
#Mind the ```
# - In `/home/nixosbm/the_prg_server_configuration/nix-system-configs/modules/bootloader/seabios-assigned-proxmox-at-birth.nix': "/dev/vda1"
# - In `/nix/var/nix/profiles/per-user/root/channels/nixos/nixos/modules/virtualisation/proxmox-image.nix': "/dev/disk/by-label/nixos"
#```

View file

@ -130,22 +130,6 @@ in {
isDefault = false; isDefault = false;
editable = true; editable = true;
} }
{
name = "Prometheus - Mail Server";
type = "prometheus";
url = "http://10.1.1.15:9001";
isDefault = false;
editable = true;
}
{
name = "Loki - Mail Server";
type = "loki";
url = "http://10.1.1.15:3100";
isDefault = false;
editable = true;
}
]; ];
}; };
}; };

View file

@ -5,14 +5,6 @@
... ...
}: let }: let
choose = paths: lib.findFirst builtins.pathExists null paths; 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.
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);
in { in {
options.local = { options.local = {
hostname = lib.mkOption { hostname = lib.mkOption {
@ -49,26 +41,6 @@ in {
]; ];
config = { 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/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.hostname = "nixos-mailserver";
local.username = "mailprg"; local.username = "mailprg";
local.userDescription = "NixOS PRG Mailing Service"; local.userDescription = "NixOS PRG Mailing Service";
@ -89,16 +61,22 @@ in {
smtp = { smtp = {
protocol = "smtp"; protocol = "smtp";
bind = "[::]:25"; bind = "[::]:25";
proxy.trusted-networks = [
"10.1.1.250/32"
];
}; };
submissions = { submissions = {
bind = "[::]:465"; bind = "[::]:465";
protocol = "smtp"; protocol = "smtp";
tls.implicit = true; tls.implicit = true;
# Also trust proxy for SMTPS
proxy.trusted-networks = ["10.1.1.250/32"];
}; };
imaps = { imaps = {
bind = "[::]:993"; bind = "[::]:993";
protocol = "imap"; protocol = "imap";
tls.implicit = true; tls.implicit = true;
proxy.trusted-networks = ["10.1.1.250/32"];
}; };
jmap = { jmap = {
bind = "[::]:8080"; bind = "[::]:8080";
@ -106,7 +84,7 @@ in {
protocol = "http"; protocol = "http";
}; };
management = { management = {
bind = ["127.0.0.1:8080" "[::]:8081"]; bind = ["127.0.0.1:8080"];
protocol = "http"; protocol = "http";
}; };
}; };
@ -118,18 +96,17 @@ in {
acme."letsencrypt" = { acme."letsencrypt" = {
directory = "https://acme-v02.api.letsencrypt.org/directory"; directory = "https://acme-v02.api.letsencrypt.org/directory";
challenge = "dns-01"; challenge = "dns-01";
# reference the contact and secret via files under /etc/stalwart contact = config.sops.secrets."cloudflare-username".path;
contact = "%{file:/etc/stalwart/cloudflare-username}%";
domains = ["prg-radio.org" "mail.prg-radio.org"]; domains = ["prg-radio.org" "mail.prg-radio.org"];
provider = "cloudflare"; provider = "cloudflare";
secret = "%{file:/etc/stalwart/acme-secret}%"; secret = config.sops.secrets."cloudflare-dns-token".path;
}; };
session.auth = { session.auth = {
mechanisms = ["plain"]; mechanisms = "[plain]";
directory = "in-memory"; directory = "'in-memory'";
}; };
storage.directory = "in-memory"; storage.directory = "in-memory";
session.rcpt.directory = "in-memory"; session.rcpt.directory = "'in-memory'";
directory."imap".lookup.domains = ["prg-radio.org"]; directory."imap".lookup.domains = ["prg-radio.org"];
directory."in-memory" = { directory."in-memory" = {
type = "memory"; type = "memory";
@ -137,20 +114,20 @@ in {
{ {
class = "individual"; class = "individual";
name = "Polyteknisk Radiogruppe Board Member"; name = "Polyteknisk Radiogruppe Board Member";
secret = "%{file:/etc/stalwart/mail-pw1}%"; secret = config.sops.secrets."board-member-password".path;
email = ["board@prg-radio.org"]; email = ["board@prg-radio.org"];
} }
{ {
class = "individual"; class = "individual";
name = "postmaster"; name = "postmaster";
secret = "%{file:/etc/stalwart/mail-pw1}%"; secret = config.sops.secrets."board-member-password".path;
email = ["postmaster@prg-radio.org"]; email = ["postmaster@prg-radio.org"];
} }
]; ];
}; };
authentication.fallback-admin = { authentication.fallback-admin = {
user = "admin"; user = "admin";
secret = "%{file:/etc/stalwart/admin-pw}%"; secret = config.sops.secrets."admin-password".path;
}; };
}; };
}; };

View file

@ -267,10 +267,10 @@ in {
}; };
# Mail entrypoints: plain SMTP, implicit SMTPS (465) and IMAPS (993) # Mail entrypoints: plain SMTP, implicit SMTPS (465) and IMAPS (993)
smtp = { #smtp = {
# plain SMTP (port 25) - TCP passthrough to backend # # plain SMTP (port 25) - TCP passthrough to backend
address = "[::]:25"; # address = "[::]:25";
}; # };
smtps = { smtps = {
# implicit TLS SMTP (port 465) - passthrough to backend # implicit TLS SMTP (port 465) - passthrough to backend
address = "[::]:465"; address = "[::]:465";
@ -309,10 +309,6 @@ in {
scheme = "https"; scheme = "https";
}; };
}; };
# Middleware to strip the /management prefix when proxying to the mail admin
mail-strip-management = {
stripPrefix = {prefixes = ["/management"];};
};
}; };
http.routers = { http.routers = {
@ -376,16 +372,6 @@ in {
entryPoints = ["websecure"]; entryPoints = ["websecure"];
tls = {certresolver = "acme";}; tls = {certresolver = "acme";};
}; };
# Mail web administration UI (Stalwart management) - exposed under /management
mail-webadmin = {
rule = "Host(`mail.prg-radio.org`) && PathPrefix(`/management`)";
service = "mail-webadmin";
entryPoints = ["websecure"];
tls = {certresolver = "acme";};
# strip the /management prefix before proxying to the backend
middlewares = ["mail-strip-management"];
};
}; };
http.services = { http.services = {
@ -428,13 +414,6 @@ in {
mail-jmap.loadBalancer = { mail-jmap.loadBalancer = {
servers = [{url = "http://10.1.1.15:8080";}]; servers = [{url = "http://10.1.1.15:8080";}];
}; };
# Mail webadmin backend (management). Traefik will strip the /management
# prefix and forward the remainder to the management listener on the mail
# server which is bound to port 8081.
mail-webadmin.loadBalancer = {
servers = [{url = "http://10.1.1.15:8081";}];
};
}; };
# TCP routing for TeamSpeak # TCP routing for TeamSpeak
@ -474,12 +453,12 @@ in {
}; };
# Mail TCP routers: SMTP (25), SMTPS (465 implicit TLS), IMAPS (993 implicit TLS) # Mail TCP routers: SMTP (25), SMTPS (465 implicit TLS), IMAPS (993 implicit TLS)
mail-smtp = { #mail-smtp = {
# catch-all TCP router for port 25 # # catch-all TCP router for port 25
rule = "HostSNI(`*`)"; # rule = "HostSNI(`*`)";
service = "mail-smtp"; # service = "mail-smtp";
entryPoints = ["smtp"]; # entryPoints = ["smtp"];
}; # };
mail-smtps = { mail-smtps = {
# SMTPS implicit TLS - passthrough to backend # SMTPS implicit TLS - passthrough to backend
@ -516,18 +495,15 @@ in {
}; };
# Mail TCP services # Mail TCP services
mail-smtp.loadBalancer = { # mail-smtp.loadBalancer = {
proxyProtocol = {version = 2;}; # Add this line # servers = [ { address = "10.1.1.15:25"; } ];
servers = [{address = "10.1.1.15:25";}]; # };
};
mail-smtps.loadBalancer = { mail-smtps.loadBalancer = {
proxyProtocol = {version = 2;}; # Add this line
servers = [{address = "10.1.1.15:465";}]; servers = [{address = "10.1.1.15:465";}];
}; };
mail-imaps.loadBalancer = { mail-imaps.loadBalancer = {
proxyProtocol = {version = 2;}; # Add this line
servers = [{address = "10.1.1.15:993";}]; servers = [{address = "10.1.1.15:993";}];
}; };
}; };
@ -570,8 +546,8 @@ in {
wants = ["acme-finished-prg-radio.org.target"]; wants = ["acme-finished-prg-radio.org.target"];
}; };
networking.firewall.allowedTCPPorts = [80 443 10022 30033 8448 9001 3001 993 465 25]; networking.firewall.allowedTCPPorts = [80 443 10022 30033 8448 9001 3001];
networking.firewall.allowedUDPPorts = [80 443 9987 9001 3001 993 465 25]; networking.firewall.allowedUDPPorts = [80 443 9987 9001 3001];
system.stateVersion = "25.11"; system.stateVersion = "25.11";
}; };

View file

@ -1,28 +1,19 @@
admin-password: ENC[AES256_GCM,data:036xX0rMzmQn0iFBYEU2u6Xa,iv:fsWHfakdNdNf8r0PrazuTbBeHmaeVqP+9l4Ip6Yx3nM=,tag:sbYB+wMOckVtBaac84zEow==,type:str] admin-password: ENC[AES256_GCM,data:o8nD9CDNzufxVjfH7TqUY4QO6Xkz1xbpFu74jMVRR57NDKpKsg3ijRmBvvq1/QuJ2TM=,iv:xVGjzrRFpCyjriW6yB/UjuGiNhK4oeBiDA+Pk/BTOWc=,tag:H/ZuqffTDGsGTWTTKplvvA==,type:str]
board-member-password: ENC[AES256_GCM,data:e8jdc6FVBwB2Ieqty9s4d/aASdY=,iv:E09RhJOB84ad+L7zRXROLGD1RgDUYe/DOJCbz/zFN08=,tag:fboTZGEnrBz5pMGqwIXSTA==,type:str] board-member-password: ENC[AES256_GCM,data:8KLyqyGFi/ehvbNU6HsWilMO42UnXYL6puc/OOPgMDiU2fuDM2o7TibUCn1BXi9UyiE=,iv:UyLOFSARK/5p/zin7vqQic9C5qlryd/O/mjcw3eZ3ao=,tag:N2RJC6nZC+V3XoAw/eWYfA==,type:str]
cloudflare-dns-token: ENC[AES256_GCM,data:IpgU7An3IW/LFL+5OJ3oYH4c3eZjZFP+qK8/oFsNCorYKVaWPVOIVA==,iv:49UwRT8DfbC9ZIXgx7nCSxjHeIIAkiHD70ti3rWUexA=,tag:Vn9wVUhTgRlGG9E+i9NzGw==,type:str] cloudflare-dns-token: ENC[AES256_GCM,data:W0S9WWmhQMwjKbKI60vBjYlSb0chZUYcVs3A4DsVRfJwErcIqDWoJg==,iv:nRH8hLvCavydVC16M93N+r/t9WwNY4339CE4zGSINsk=,tag:yIUKs/XB3arhApGeik6p1A==,type:str]
cloudflare-username: ENC[AES256_GCM,data:AMgWBFP90f1ML/I6es0HIUoW,iv:64GGNMSdTrmurEsgdI82iTqD9FUW8leKu/JvT7Ls6Og=,tag:3rTWNF3NfTExCnV+RRwyVA==,type:str] cloudflare-username: ENC[AES256_GCM,data:5QCWOZcv6RKlk1/5LWjjCW0m,iv:9VzhRDlLq36pMfv0tu7POxheLFKdXP8tLLiKlTOr/30=,tag:MiijON1+mKJj6VMdnaJTkA==,type:str]
sops: sops:
age: age:
- recipient: age1746rvsvsc3snxfl7cndm222wd5kck4aqj3x7nednlegq0gdjhfcqx0qv7m - recipient: age1746rvsvsc3snxfl7cndm222wd5kck4aqj3x7nednlegq0gdjhfcqx0qv7m
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYRFU5S1EvUmFTeFoxQ0dZ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBseTM3bTJtamFBVzRkOVpp
Ymx5Tm92U2NtS2JsaGlzS3Y2Z3dsRGpkOW5BCkxCRXJWMjBkTjdiejg0bGpRcXg0 bEZNQlJoNUVjTFd0NGl3VktmUUdHWi9wMTM0CnE3bTh5QmM1KzVqTnI3RHdmbXhN
UzBzSHUwajRSM2s4bjFWRGNCVDVXclEKLS0tICtxYnZJQm9KcCs1eURCYTUyRHYx a0NWRkthbG9OZ3pZMEZkU3hjWUNyd1UKLS0tIE5KdXgzQmRRWTVBbDFTSEkzQW9o
SFQxT0hyZ3A5d1BPbEhWUVJJNFVFM1EKI7Srn4G6r0mKIfPd+VsCCAQGlshirxLS ZWE0ci9wSmhpWmc1OHZwb09aTjk1TUUKitmILkhief6sapPh3gZAEDsaHqcv3se0
cvMHhG3WqMV/esuHfi/0xpQuKoCQpBcS2sW/vtJk5Au+uV3dVfv/jg== +6w+hs05ChkXHQ+JlXOTznd5ZNS4hwOAk5KOconNbauBaKWDplnHhA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1pkn38zzyckxqgeud7s7y3uuhd7pau3csh56rn3ue4altzkressdsaxs4et lastmodified: "2026-02-15T14:13:19Z"
enc: | mac: ENC[AES256_GCM,data:7hFjuvictSbcXLqXwG0VgWErKJpFsy1PfDyepQQXpszpMT4Z/BwvXlk4ppKo8C0PaCLv2qi86yBmFm/O6xUBhsMEFWYHQ+mJpYtqLX0GDvj1cn4LwDEnRa+2SiHkkZeHSwrtOHCBw8vE2R2sXBaNMkUoSXkcQ4lPS6YjulpO1vw=,iv:5aZxEnPymcvNpsUyGvvRI3o7hnfExSFWlBrzoIhQkFQ=,tag:6JJGvBL9XeiPw+TdN2qEgA==,type:str]
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvOFYyWGYrUWNDdzBkajlh
RVdENTloUDcvVnhPeHgvcDIzRnNIWUNnS21BCmFhMlVSSk1kNmpUek0yZDh3cGcx
WHltUWVqREhZdlJ2bWJtczJjTXZ3dHcKLS0tIDRweTY1RVdVZVQ1ZlNsc3FwdVdL
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]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.11.0 version: 3.11.0