Skip to content

Commit

Permalink
Add simple-nixos-mailserver to umbriel
Browse files Browse the repository at this point in the history
  • Loading branch information
jfly committed Oct 31, 2024
1 parent fb5e9ae commit baf4add
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 3 deletions.
86 changes: 85 additions & 1 deletion flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
url = "github:numtide/srvos";
inputs.nixpkgs.follows = "nixpkgs";
};
simple-nixos-mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver";
sops-nix = {
url = "github:Mic92/sops-nix";
inputs = {
Expand Down
9 changes: 8 additions & 1 deletion non-critical-infra/flake-module.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
value
inputs.disko.nixosModules.disko
inputs.first-time-contribution-tagger.nixosModule
inputs.simple-nixos-mailserver.nixosModule
inputs.sops-nix.nixosModules.sops
];
extraModules = [ inputs.colmena.nixosModules.deploymentOptions ];
Expand All @@ -49,8 +50,14 @@
};

perSystem =
{ pkgs, inputs', ... }:
{ inputs', ... }:
# Use the latest packages from `nixpkgs-unstable` for dev tools.
let
pkgs = inputs'.nixpkgs-unstable.legacyPackages;
in
{
packages.encrypt-email-address = pkgs.callPackage ./packages/encrypt-email-address { };

devShells.non-critical-infra = pkgs.mkShellNoCC {
packages = [
inputs'.colmena.packages.colmena
Expand Down
1 change: 1 addition & 0 deletions non-critical-infra/hosts/umbriel.nixos.org/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
../../modules/common.nix
../../modules/mjolnir.nix
../../modules/prometheus/node-exporter.nix
../../modules/mailserver
];

# Bootloader.
Expand Down
14 changes: 14 additions & 0 deletions non-critical-infra/modules/mailserver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# NixOS mailserver

This module will [eventually][issue 485] provide mail services for `nixos.org`.

## Mailing lists

To create a new mailing list, or change membership of a mailing list, see the
instructions at the top of [`mailing-lists.nix`](./mailing-lists.nix).

## Sending mail

This module does not yet provide SMTP login.

[issue 485]: https://github.com/NixOS/infra/issues/485
19 changes: 19 additions & 0 deletions non-critical-infra/modules/mailserver/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{ config, ... }:

{
imports = [ ./mailing-lists.nix ];

mailserver = {
enable = true;
certificateScheme = "acme-nginx";

# Until we have login accounts, there's no reason to run either of these.
enablePop3 = false;
enableImap = false;

fqdn = config.networking.fqdn;

# TODO: change to `nixos.org` when ready
domains = [ "mail-test.nixos.org" ];
};
}
62 changes: 62 additions & 0 deletions non-critical-infra/modules/mailserver/mailing-lists.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# This module provides the mailing list definitions for `@nixos.org`.
#
# Simply change the `lists` attribute set below to create new mailing lists or
# edit membership of existing lists.
#
# If you wish to hide your email address, you can encrypt it with SOPS. Just
# run `nix run .#encrypt-email-address` in the `non-critical-infra/` folder and
# follow the instructions.

{ config, lib, ... }:

let
# Mailing lists go here.
# TODO: replace with the real `nixos.org` mailing lists.
listsWithSecretFiles = {
"[email protected]" = [
"[email protected]"
../../secrets/jfly-email.umbriel
"[email protected]"
];
};

fileToSecretId = file: builtins.baseNameOf file;

listsWithSecretPlaceholders = lib.mapAttrs' (name: members: {
name = name;
value = map (
member:
if builtins.isString member then member else config.sops.placeholder.${fileToSecretId member}
) members;
}) listsWithSecretFiles;

secretFiles = lib.pipe listsWithSecretFiles [
(lib.mapAttrsToList (_name: members: members))
lib.flatten
(builtins.filter (member: !builtins.isString member))
];
in

{
# Declare secrets for every secret email in the lists above.
sops.secrets = builtins.listToAttrs (
map (file: {
name = fileToSecretId file;
value = {
format = "binary";
sopsFile = file;
};
}) secretFiles
);

sops.templates."postfix-virtual-mailing-lists".content = lib.concatStringsSep "\n" (
lib.mapAttrsToList (
name: members: "${name} ${lib.concatStringsSep ", " members}"
) listsWithSecretPlaceholders
);

services.postfix.mapFiles.virtual-mailing-lists =
config.sops.templates."postfix-virtual-mailing-lists".path;

services.postfix.config.virtual_alias_maps = [ "hash:/etc/postfix/virtual-mailing-lists" ];
}
20 changes: 20 additions & 0 deletions non-critical-infra/packages/encrypt-email-address/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
lib,
python3,
sops,
}:

python3.pkgs.buildPythonApplication {
name = "encrypt-email-address";
src = ./.;

format = "other";

propagatedBuildInputs = [ python3.pkgs.click ];

installPhase = ''
mkdir -p $out/bin
mv ./encrypt-email-address.py $out/bin/encrypt-email-address
wrapProgram $out/bin/encrypt-email-address --prefix PATH : ${lib.makeBinPath [ sops ]}
'';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env python3

import re
import click
import subprocess
from pathlib import Path
from textwrap import dedent


@click.command(
help=dedent("""\
Encrypt an email address (or email addresses) for inclusion in a mailing list.
See `non-critical-infra/modules/mailserver/mailing-lists.nix` for
documentation about how to use this.
""")
)
@click.argument("id")
@click.argument("email")
@click.option("--force/--no-force", "-f/ ", default=False)
def main(id: str, email: str, force: bool) -> None:
# Feel free to make the regex less restrictive if you need to.
id_re = re.compile("[A-Za-z0-9-]+")
if not id_re.fullmatch(id):
raise click.ClickException(
f"Given ID: {id!r} is invalid. Must match regex: {id_re.pattern}"
)

# Make sure we aren't being given a text file that happens to have a newline at the end.
clean_email = email.strip()
if clean_email != email:
click.secho("Removed whitespace surrounding given email address", fg="yellow")
email = clean_email

if Path.cwd().name != "non-critical-infra":
raise click.ClickException(
"You must run this command inside the `non-critical-infra/` folder."
)

secret_path = Path(f"secrets/{id}-email.umbriel")

if secret_path.exists():
if not force:
raise click.ClickException(
f"Refusing to clobber existing {secret_path}. Use `--force` to override."
)
else:
click.secho(f"Clobbering existing {secret_path}", fg="yellow")

cp = subprocess.run(
["sops", "--encrypt", "--filename-override", secret_path, "/dev/stdin"],
text=True,
check=True,
stdout=subprocess.PIPE,
input=email,
)

secret_path.write_text(cp.stdout)
subprocess.run(
["git", "add", "--intent-to-add", "--force", "--", secret_path], check=True
)

click.secho(f"Successfully generated {secret_path}", fg="green")

mailing_list_nix = Path("modules/mailserver/mailing-lists.nix")
assert mailing_list_nix.exists()

click.secho()
click.secho("Now add yourself to ", nl=False)
click.secho(mailing_list_nix, fg="blue", nl=False)
click.secho(". ")

click.secho()
click.secho("Lastly, add `", nl=False)
click.secho(
secret_path.relative_to(mailing_list_nix.parent, walk_up=True),
fg="blue",
nl=False,
)
click.secho("` to the relevant mailing list under '", nl=False)
click.secho("# Mailing lists go here.", fg="blue", nl=False)
click.secho("'.")


if __name__ == "__main__":
main()
Loading

0 comments on commit baf4add

Please sign in to comment.