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()