Skip to content

Commit 7775cfc

Browse files
committed
Add simple-nixos-mailserver to umbriel
1 parent 3440e39 commit 7775cfc

File tree

10 files changed

+325
-2
lines changed

10 files changed

+325
-2
lines changed

flake.lock

Lines changed: 85 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
url = "github:numtide/srvos";
3232
inputs.nixpkgs.follows = "nixpkgs";
3333
};
34+
simple-nixos-mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver";
3435
sops-nix = {
3536
url = "github:Mic92/sops-nix";
3637
inputs = {

non-critical-infra/flake-module.nix

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
value
2727
inputs.disko.nixosModules.disko
2828
inputs.first-time-contribution-tagger.nixosModule
29+
inputs.simple-nixos-mailserver.nixosModule
2930
inputs.sops-nix.nixosModules.sops
3031
];
3132
extraModules = [ inputs.colmena.nixosModules.deploymentOptions ];
@@ -49,8 +50,14 @@
4950
};
5051

5152
perSystem =
52-
{ pkgs, inputs', ... }:
53+
{ inputs', ... }:
54+
# Use the latest packages from `nixpkgs-unstable` for dev tools.
55+
let
56+
pkgs = inputs'.nixpkgs-unstable.legacyPackages;
57+
in
5358
{
59+
packages.encrypt-email-address = pkgs.callPackage ./packages/encrypt-email-address { };
60+
5461
devShells.non-critical-infra = pkgs.mkShellNoCC {
5562
packages = [
5663
inputs'.colmena.packages.colmena

non-critical-infra/hosts/umbriel.nixos.org/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
../../modules/common.nix
99
../../modules/mjolnir.nix
1010
../../modules/prometheus/node-exporter.nix
11+
./mailserver
1112
];
1213

1314
# Bootloader.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# NixOS mailserver
2+
3+
This module will [eventually][issue 485] provide mail services for `nixos.org`.
4+
5+
## Mailing lists
6+
7+
To create a new mailing list, or change membership of a mailing list, see the
8+
instructions at the top of [`mailing-lists.nix`](./mailing-lists.nix).
9+
10+
## Sending mail
11+
12+
This module does not yet provide SMTP login.
13+
14+
[issue 485]: https://github.com/NixOS/infra/issues/485
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{ config, ... }:
2+
3+
{
4+
imports = [ ./mailing-lists.nix ];
5+
6+
mailserver = {
7+
enable = true;
8+
certificateScheme = "acme-nginx";
9+
10+
# Until we have login accounts, there's no reason to run either of these.
11+
enablePop3 = false;
12+
enableImap = false;
13+
14+
fqdn = config.networking.fqdn;
15+
16+
# TODO: create an `MX` record for `playground.nixos.org` (value `umbriel.nixos.org`): <https://nixos-mailserver.readthedocs.io/en/latest/setup-guide.html#set-a-mx-record>.
17+
# TODO: create an `SPF` record for `playground.nixos.org`: <https://nixos-mailserver.readthedocs.io/en/latest/setup-guide.html#set-a-mx-record>.
18+
# TODO: create `DKIM` TXT record: <https://nixos-mailserver.readthedocs.io/en/latest/setup-guide.html#set-dkim-signature>.
19+
# (can't do this until after SNM is deployed)
20+
# TODO: set a `DMARC` record: <https://nixos-mailserver.readthedocs.io/en/latest/setup-guide.html#set-a-dmarc-record>.
21+
# (and then make it strict (`v=DMARC1; p=reject; adkim=s; aspf=s;`) once things are working)
22+
# TODO: change to `nixos.org` when ready
23+
domains = [ "playground.nixos.org" ];
24+
};
25+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# This module provides the mailing list definitions for `@nixos.org`.
2+
#
3+
# Simply change the `lists` attribute set below to create new mailing lists or
4+
# edit membership of existing lists.
5+
#
6+
# If you wish to hide your email address, you can encrypt it with SOPS. Just
7+
# run `nix run .#encrypt-email-address` in the `non-critical-infra/` folder and
8+
# follow the instructions.
9+
10+
{ config, lib, ... }:
11+
12+
let
13+
# Mailing lists go here.
14+
# TODO: replace with the real `nixos.org` mailing lists.
15+
lists = {
16+
17+
18+
config.sops.placeholder.jfly-email
19+
20+
];
21+
};
22+
in
23+
24+
{
25+
# Encrypted email addresses go here.
26+
sops.secrets.jfly-email = {
27+
format = "binary";
28+
sopsFile = ../../../secrets/jfly-email.umbriel;
29+
};
30+
31+
sops.templates."postfix-virtual-mailing-lists".content = lib.concatStringsSep "\n" (
32+
lib.mapAttrsToList (name: members: "${name} ${lib.concatStringsSep ", " members}") lists
33+
);
34+
35+
services.postfix.mapFiles.virtual-mailing-lists =
36+
config.sops.templates."postfix-virtual-mailing-lists".path;
37+
38+
services.postfix.config.virtual_alias_maps = [ "hash:/etc/postfix/virtual-mailing-lists" ];
39+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
lib,
3+
python3,
4+
ruff,
5+
sops,
6+
}:
7+
8+
python3.pkgs.buildPythonApplication {
9+
name = "encrypt-email-address";
10+
src = ./.;
11+
12+
format = "other";
13+
14+
nativeBuildInputs = [ ruff ];
15+
16+
propagatedBuildInputs = [ python3.pkgs.click ];
17+
18+
installPhase = ''
19+
ruff check ./encrypt-email-address.py
20+
mkdir -p $out/bin
21+
mv ./encrypt-email-address.py $out/bin/encrypt-email-address
22+
wrapProgram $out/bin/encrypt-email-address --prefix PATH : ${lib.makeBinPath [ sops ]}
23+
'';
24+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env python
2+
3+
import re
4+
import click
5+
import subprocess
6+
from pathlib import Path
7+
from textwrap import dedent
8+
from textwrap import indent
9+
10+
11+
@click.command(
12+
help=dedent("""\
13+
Encrypt an email address (or email addresses) for inclusion in a mailing list.
14+
15+
See `non-critical-infra/hosts/umbriel.nixos.org/mailserver/mailing-lists.nix` for
16+
documentation about how to use this.
17+
""")
18+
)
19+
@click.argument("id")
20+
@click.argument("email")
21+
@click.option("--force/--no-force", "-f/ ", default=False)
22+
def main(id: str, email: str, force: bool):
23+
# Feel free to make the regex less restrictive if you need to.
24+
id_re = re.compile("[A-Za-z0-9-]+")
25+
if not id_re.fullmatch(id):
26+
raise click.ClickException(
27+
f"Given ID: {id!r} is invalid. Must match regex: {id_re.pattern}"
28+
)
29+
30+
# Make sure we aren't being given a text file that happens to have a newline at the end.
31+
if "\n" in email:
32+
raise click.ClickException(
33+
"Email address must not not contain a newline character"
34+
)
35+
36+
if Path.cwd().name != "non-critical-infra":
37+
raise click.ClickException(
38+
"You must run this command inside the `non-critical-infra/` folder."
39+
)
40+
41+
secret_path = Path(f"secrets/{id}-email.umbriel")
42+
43+
if secret_path.exists():
44+
if not force:
45+
raise click.ClickException(
46+
f"Refusing to clobber existing {secret_path}. Use `--force` to override."
47+
)
48+
else:
49+
click.secho(f"Clobbering existing {secret_path}", fg="yellow")
50+
51+
cp = subprocess.run(
52+
["sops", "--encrypt", "--filename-override", secret_path, "/dev/stdin"],
53+
text=True,
54+
check=True,
55+
stdout=subprocess.PIPE,
56+
input=email,
57+
)
58+
59+
secret_path.write_text(cp.stdout)
60+
61+
click.secho(f"Successfully generated {secret_path}", fg="green")
62+
63+
mailing_list_nix = Path("hosts/umbriel.nixos.org/mailserver/mailing-lists.nix")
64+
assert mailing_list_nix.exists()
65+
66+
secret_id = f"{id}-email"
67+
68+
click.secho()
69+
click.secho("Now add yourself to ", nl=False)
70+
click.secho(mailing_list_nix, fg="blue", nl=False)
71+
click.secho(". ")
72+
73+
click.secho()
74+
click.secho("Search for '", nl=False)
75+
click.secho("# Encrypted email addresses go here.", fg="blue", nl=False)
76+
click.secho("' and add this:")
77+
click.secho()
78+
click.secho(
79+
indent(
80+
dedent(f"""\
81+
sops.secrets.{secret_id} = {{
82+
format = "binary";
83+
sopsFile = {secret_path.relative_to(mailing_list_nix.parent, walk_up=True)};
84+
}};
85+
"""),
86+
prefix=" ",
87+
),
88+
fg="blue",
89+
)
90+
91+
click.secho()
92+
click.secho("Lastly, add `", nl=False)
93+
click.secho(f"config.sops.placeholder.{secret_id}", fg="blue", nl=False)
94+
click.secho("` to the relevant mailing list under '", nl=False)
95+
click.secho("# Mailing lists go here.", fg="blue", nl=False)
96+
click.secho("'.")
97+
98+
99+
if __name__ == "__main__":
100+
main()

0 commit comments

Comments
 (0)