@cryptotaxi247 / infra / commits / 6c6dc313

chore: refactor mailing lists to a dedicated file

I started working on the ImprovMX import, and realized it's enough data that we'll want it in a standalone file.

Jeremy Fleischman committed Mar 22, 2025 at 10:30 UTC 6c6dc3139c3cc469af19d8f8f0eb4133390228e4
5 files changed +164 -161
non-critical-infra/modules/mailserver/README.md
+3 -2
@@ -2,11 +2,12 @@
2
3 This module will [eventually][issue 485] provide mail services for `nixos.org`.
4
5 +[issue 485]: https://github.com/NixOS/infra/issues/485
6 +
7 ## Mailing lists
8
9 To create a new mailing list, or change membership of a mailing list, see the
8 -instructions under `### Mailing lists go here ###` in
9 -[`default.nix`](./default.nix).
10 +instructions at the top of [`mailing-lists.nix`](./mailing-lists.nix).
11
12 Some mailing lists allow login and sending email via `SMTP`. Search for
13 `loginAccount` to find examples of this.
non-critical-infra/modules/mailserver/default.nix
+3 -23
@@ -1,7 +1,9 @@
1 { config, pkgs, ... }:
2
3 {
4 - imports = [ ./mailing-lists.nix ];
4 + imports = [
5 + ./mailing-lists.nix
6 + ];
7
8 mailserver = {
9 enable = true;
@@ -38,28 +40,6 @@
40 path = "${config.mailserver.dkimKeyDirectory}/nixos.org.mail.key";
41 };
42
41 - ### Mailing lists go here ###
42 - # If you wish to hide your email address, you can encrypt it with SOPS. Just
43 - # run `nix run .#encrypt-email address -- --help` and follow the instructions.
44 - #
45 - # If you wish to set up a login account for sending email, you must generate
46 - # an encrypted password. Run `nix run .#encrypt-email login -- --help` and
47 - # follow the instructions.
48 - mailing-lists = {
49 - # TODO: replace with the real `nixos.org` mailing lists.
50 - "test-list@nixos.org" = {
51 - forwardTo = [
52 - "jfly@playground.jflei.com"
53 - ../../secrets/jfly-email-address.umbriel
54 - "jeremyfleischman+subscriber@gmail.com"
55 - ];
56 - };
57 - "test-sender@nixos.org" = {
58 - forwardTo = [ "jeremy@playground.jflei.com" ];
59 - loginAccount.encryptedHashedPassword = ../../secrets/test-sender-email-login.umbriel;
60 - };
61 - };
62 -
43 services.postfix.config.bounce_template_file = "${pkgs.writeText "bounce-template.cf" ''
44 failure_template = <<EOF
45 Charset: us-ascii
non-critical-infra/modules/mailserver/mailing-lists-options.nix new
+134
@@ -0,0 +1,134 @@
1 +# This module makes it easy to define mailing lists in `simple-nixos-mailserver`
2 +# with a couple of features:
3 +#
4 +# 1. We can (optionally) encrypt the forward addresses for increased privacy.
5 +# 2. We can set up a login account for mailing addresses to allow sending
6 +# email via `SMTP` from those addresses.
7 +
8 +{ config, lib, ... }:
9 +
10 +let
11 + inherit (lib) types;
12 +
13 + fileToSecretId = file: builtins.baseNameOf file;
14 +
15 + listsWithSecretPlaceholders = lib.mapAttrs' (name: mailingList: {
16 + name = name;
17 + value = map (
18 + member:
19 + if builtins.isString member then member else config.sops.placeholder.${fileToSecretId member}
20 + ) mailingList.forwardTo;
21 + }) config.mailing-lists;
22 +
23 + secretAddressFiles = lib.pipe config.mailing-lists [
24 + (lib.mapAttrsToList (_name: mailingList: mailingList.forwardTo))
25 + lib.flatten
26 + (builtins.filter (member: !builtins.isString member))
27 + ];
28 +
29 + secretPasswordFiles = lib.pipe config.mailing-lists [
30 + (lib.filterAttrs (_name: mailingList: mailingList.loginAccount != null))
31 + (lib.mapAttrsToList (_name: mailingList: mailingList.loginAccount.encryptedHashedPassword))
32 + ];
33 +in
34 +
35 +{
36 + options = {
37 + mailing-lists = lib.mkOption {
38 + type = types.attrsOf (
39 + types.submodule {
40 + options = {
41 + forwardTo = lib.mkOption {
42 + type = types.listOf (types.either types.str types.path);
43 + description = ''
44 + Either a plaintext email address, or a path to an email address
45 + encrypted with `nix run .#encrypt-email address`
46 + '';
47 + };
48 + loginAccount = lib.mkOption {
49 + type = types.nullOr (
50 + types.submodule {
51 + options = {
52 + encryptedHashedPassword = lib.mkOption {
53 + type = types.path;
54 + description = ''
55 + If specified, this enables sending emails from this address via SMTP.
56 + Must be a path to encrypted file generated with `nix run .#encrypt-email login`
57 + '';
58 + };
59 + };
60 + }
61 + );
62 + default = null;
63 + };
64 + };
65 + }
66 + );
67 + description = ''
68 + Mailing lists. Supports both forward-only mailing lists, as well as mailing
69 + lists that allow sending via SMTP.
70 + '';
71 + };
72 + };
73 +
74 + config = {
75 + # Disable IMAP. We don't need it, as we don't store email on this server, we
76 + # only forward emails.
77 + mailserver.enableImap = false;
78 + mailserver.enableImapSsl = false;
79 + services.dovecot2.enableImap = false;
80 +
81 + mailserver.loginAccounts = lib.pipe config.mailing-lists [
82 + (lib.filterAttrs (_name: mailingList: mailingList.loginAccount != null))
83 + (lib.mapAttrs (
84 + _name: mailingList: {
85 + hashedPasswordFile =
86 + config.sops.secrets.${fileToSecretId mailingList.loginAccount.encryptedHashedPassword}.path;
87 + }
88 + ))
89 + ];
90 +
91 + # Declare secrets for every secret file.
92 + sops.secrets = builtins.listToAttrs (
93 + (map (file: {
94 + name = fileToSecretId file;
95 + value = {
96 + format = "binary";
97 + sopsFile = file;
98 + };
99 + }) secretAddressFiles)
100 + ++ (map (file: {
101 + name = fileToSecretId file;
102 + value = {
103 + format = "binary";
104 + sopsFile = file;
105 + # Need to restart `dovecot2.service` to trigger `genPasswdScript` in
106 + # `nixos-mailserver`:
107 + # https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/af7d3bf5daeba3fc28089b015c0dd43f06b176f2/mail-server/dovecot.nix#L369
108 + # This could go away if sops-nix gets support for "input addressed secret
109 + # paths": https://github.com/Mic92/sops-nix/issues/648
110 + restartUnits = [ "dovecot2.service" ];
111 + };
112 + }) secretPasswordFiles)
113 + );
114 +
115 + sops.templates."postfix-virtual-mailing-lists" = {
116 + content = lib.concatStringsSep "\n" (
117 + lib.mapAttrsToList (
118 + name: members: "${name} ${lib.concatStringsSep ", " members}"
119 + ) listsWithSecretPlaceholders
120 + );
121 +
122 + # Need to restart postfix-setup to rerun `postmap` and generate updated `.db`
123 + # files whenever mailing list membership changes.
124 + # This could go away if sops-nix gets support for "input addressed secret
125 + # paths": https://github.com/Mic92/sops-nix/issues/648
126 + restartUnits = [ "postfix-setup.service" ];
127 + };
128 +
129 + services.postfix.mapFiles.virtual-mailing-lists =
130 + config.sops.templates."postfix-virtual-mailing-lists".path;
131 +
132 + services.postfix.config.virtual_alias_maps = [ "hash:/etc/postfix/virtual-mailing-lists" ];
133 + };
134 +}
non-critical-infra/modules/mailserver/mailing-lists.nix
+19 -129
@@ -1,134 +1,24 @@
1 -# This module makes it easy to define mailing lists in `simple-nixos-mailserver`
2 -# with a couple of features:
3 -#
4 -# 1. We can (optionally) encrypt the forward addresses for increase privacy.
5 -# 2. We can set up a login account for mailing addresses to allow sending
6 -# email via `SMTP` from those addresses.
7 -
8 -{ config, lib, ... }:
9 -
10 -let
11 - inherit (lib) types;
12 -
13 - fileToSecretId = file: builtins.baseNameOf file;
14 -
15 - listsWithSecretPlaceholders = lib.mapAttrs' (name: mailingList: {
16 - name = name;
17 - value = map (
18 - member:
19 - if builtins.isString member then member else config.sops.placeholder.${fileToSecretId member}
20 - ) mailingList.forwardTo;
21 - }) config.mailing-lists;
22 -
23 - secretAddressFiles = lib.pipe config.mailing-lists [
24 - (lib.mapAttrsToList (_name: mailingList: mailingList.forwardTo))
25 - lib.flatten
26 - (builtins.filter (member: !builtins.isString member))
27 - ];
28 -
29 - secretPasswordFiles = lib.pipe config.mailing-lists [
30 - (lib.filterAttrs (_name: mailingList: mailingList.loginAccount != null))
31 - (lib.mapAttrsToList (_name: mailingList: mailingList.loginAccount.encryptedHashedPassword))
32 - ];
33 -in
34 -
1 {
36 - options = {
37 - mailing-lists = lib.mkOption {
38 - type = types.attrsOf (
39 - types.submodule {
40 - options = {
41 - forwardTo = lib.mkOption {
42 - type = types.listOf (types.either types.str types.path);
43 - description = ''
44 - Either a plaintext email address, or a path to an email address
45 - encrypted with `nix run .#encrypt-email address`
46 - '';
47 - };
48 - loginAccount = lib.mkOption {
49 - type = types.nullOr (
50 - types.submodule {
51 - options = {
52 - encryptedHashedPassword = lib.mkOption {
53 - type = types.path;
54 - description = ''
55 - If specified, this enables sending emails from this address via SMTP.
56 - Must be a path to encrypted file generated with `nix run .#encrypt-email login`
57 - '';
58 - };
59 - };
60 - }
61 - );
62 - default = null;
63 - };
64 - };
65 - }
66 - );
67 - description = ''
68 - Mailing lists. Supports both forward-only mailing lists, as well as mailing
69 - lists that allow sending via SMTP.
70 - '';
2 + imports = [ ./mailing-lists-options.nix ];
3 +
4 + # If you wish to hide your email address, you can encrypt it with SOPS. Just
5 + # run `nix run .#encrypt-email address -- --help` and follow the instructions.
6 + #
7 + # If you wish to set up a login account for sending email, you must generate
8 + # an encrypted password. Run `nix run .#encrypt-email login -- --help` and
9 + # follow the instructions.
10 + mailing-lists = {
11 + # TODO: replace with the real `nixos.org` mailing lists.
12 + "test-list@nixos.org" = {
13 + forwardTo = [
14 + "jfly@playground.jflei.com"
15 + ../../secrets/jfly-email-address.umbriel
16 + "jeremyfleischman+subscriber@gmail.com"
17 + ];
18 };
72 - };
73 -
74 - config = {
75 - # Disable IMAP. We don't need it, as we don't store email on this server, we
76 - # only forward emails.
77 - mailserver.enableImap = false;
78 - mailserver.enableImapSsl = false;
79 - services.dovecot2.enableImap = false;
80 -
81 - mailserver.loginAccounts = lib.pipe config.mailing-lists [
82 - (lib.filterAttrs (_name: mailingList: mailingList.loginAccount != null))
83 - (lib.mapAttrs (
84 - _name: mailingList: {
85 - hashedPasswordFile =
86 - config.sops.secrets.${fileToSecretId mailingList.loginAccount.encryptedHashedPassword}.path;
87 - }
88 - ))
89 - ];
90 -
91 - # Declare secrets for every secret file.
92 - sops.secrets = builtins.listToAttrs (
93 - (map (file: {
94 - name = fileToSecretId file;
95 - value = {
96 - format = "binary";
97 - sopsFile = file;
98 - };
99 - }) secretAddressFiles)
100 - ++ (map (file: {
101 - name = fileToSecretId file;
102 - value = {
103 - format = "binary";
104 - sopsFile = file;
105 - # Need to restart `dovecot2.service` to trigger `genPasswdScript` in
106 - # `nixos-mailserver`:
107 - # https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/af7d3bf5daeba3fc28089b015c0dd43f06b176f2/mail-server/dovecot.nix#L369
108 - # This could go away if sops-nix gets support for "input addressed secret
109 - # paths": https://github.com/Mic92/sops-nix/issues/648
110 - restartUnits = [ "dovecot2.service" ];
111 - };
112 - }) secretPasswordFiles)
113 - );
114 -
115 - sops.templates."postfix-virtual-mailing-lists" = {
116 - content = lib.concatStringsSep "\n" (
117 - lib.mapAttrsToList (
118 - name: members: "${name} ${lib.concatStringsSep ", " members}"
119 - ) listsWithSecretPlaceholders
120 - );
121 -
122 - # Need to restart postfix-setup to rerun `postmap` and generate updated `.db`
123 - # files whenever mailing list membership changes.
124 - # This could go away if sops-nix gets support for "input addressed secret
125 - # paths": https://github.com/Mic92/sops-nix/issues/648
126 - restartUnits = [ "postfix-setup.service" ];
19 + "test-sender@nixos.org" = {
20 + forwardTo = [ "jeremy@playground.jflei.com" ];
21 + loginAccount.encryptedHashedPassword = ../../secrets/test-sender-email-login.umbriel;
22 };
128 -
129 - services.postfix.mapFiles.virtual-mailing-lists =
130 - config.sops.templates."postfix-virtual-mailing-lists".path;
131 -
132 - services.postfix.config.virtual_alias_maps = [ "hash:/etc/postfix/virtual-mailing-lists" ];
23 };
24 }
non-critical-infra/packages/encrypt-email/encrypt-email.py
+5 -7
@@ -100,20 +100,18 @@ def address(address_id: str, email: str, force: bool) -> None:
100 secret_path = non_critical_infra_dir / f"secrets/{address_id}-email-address.umbriel"
101 encrypt_to_file(email, secret_path, force)
102
103 - default_nix = non_critical_infra_dir / "modules/mailserver/default.nix"
104 - assert default_nix.exists()
103 + mailing_lists_nix = non_critical_infra_dir / "modules/mailserver/mailing-lists.nix"
104 + assert mailing_lists_nix.exists()
105
106 click.secho()
107 click.secho("Now add `", nl=False)
108 click.secho(
109 - secret_path.relative_to(default_nix.parent, walk_up=True),
109 + secret_path.relative_to(mailing_lists_nix.parent, walk_up=True),
110 fg="blue",
111 nl=False,
112 )
113 - click.secho("` to the relevant mailing list under '", nl=False)
114 - click.secho("### Mailing lists go here ###", fg="blue", nl=False)
115 - click.secho("' in ", nl=False)
116 - click.secho(default_nix, fg="blue")
113 + click.secho("` to the relevant mailing list in '", nl=False)
114 + click.secho(mailing_lists_nix, fg="blue")
115
116
117 @main.command()