diff --git a/.gitignore b/.gitignore index 29ee88c..ec86951 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ config.ini *.pfx *.crt *.pem +*.pw !tests/privkey.pem !tests/signer.pem diff --git a/config.example b/config.example index b39ac16..3663644 100644 --- a/config.example +++ b/config.example @@ -14,7 +14,6 @@ sender = foo@example.com # start_tls = false # smime_cert = /path/to/cert # smime_cert_private = /path/to/cert.key -# smime_to_cert = /path/to/recipient_cert [emails] # If you want to encrypt outgoing email, add the recipient certificate to the email diff --git a/default.nix b/default.nix index 7b4999f..59ce8f2 100644 --- a/default.nix +++ b/default.nix @@ -22,13 +22,6 @@ pkgs.python3Packages.buildPythonPackage rec { nativeCheckInputs = [ pkgs.python3Packages.pytestCheckHook ]; pythonImportsCheck = [ "smtprd_ng" ]; - postInstall = '' - install -D -m 644 systemd/smtprd-ng.service \ - $out/lib/systemd/system/smtprd-ng.service - substituteInPlace $out/lib/systemd/system/smtprd-ng.service \ - --replace-fail @smtprd@ "$out/bin/smtprd-ng" - ''; - meta = { description = "SMTP forwarding relay daemon with signing and encryption"; homepage = "https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z3gWc1qgaeZaoGwL4WTstLNoqjayM"; diff --git a/flake.nix b/flake.nix index 113008d..68e91cd 100644 --- a/flake.nix +++ b/flake.nix @@ -10,6 +10,7 @@ outputs = { self, nixpkgs, flake-utils, pre-commit-hooks, }: let supportedSystems = [ "x86_64-linux" "aarch64-darwin" ]; in { + nixosModules.smtprd-ng = import ./module.nix; overlays.default = import ./overlay.nix { inherit self; }; } // flake-utils.lib.eachSystem supportedSystems (system: let pkgs = import nixpkgs { inherit system; }; diff --git a/module.nix b/module.nix new file mode 100644 index 0000000..30c34db --- /dev/null +++ b/module.nix @@ -0,0 +1,171 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.smtprd-ng; + smtprd-ng = pkgs.callPackage ./. { }; + + cfgText = generators.toINI { } { + server = { + hostname = cfg.server.hostname; + port = cfg.server.port; + }; + client = { + hostname = cfg.client.hostname; + port = cfg.client.port; + username = cfg.client.username; + password_file = cfg.client.password_file; + sender = cfg.client.sender; + use_tls = cfg.client.use_tls; + start_tls = cfg.client.start_tls; + smime_cert = cfg.client.smime_cert; + smime_cert_private = cfg.client.smime_cert_private; + }; + emails = cfg.emails; + }; + confFile = pkgs.writeText "config.ini" cfgText; +in { + options.services.smtprd-ng = { + enable = mkEnableOption "smtprd-ng"; + + package = mkOption { + type = types.package; + default = smtprd-ng; + description = mdDoc '' + The smtprd-ng package to use. + ''; + }; + + server = { + hostname = mkOption { + type = types.str; + default = "localhost"; + description = mdDoc '' + The hostname the local SMTP server should listen on. + ''; + }; + port = mkOption { + type = types.port; + default = 8025; + description = mdDoc '' + The port the local SMTP server should listen on. + ''; + }; + }; + client = { + hostname = mkOption { + type = types.str; + default = ""; + description = mdDoc '' + The hostname of the remote SMTP server. + ''; + }; + port = mkOption { + type = types.port; + default = 587; + description = mdDoc '' + The port of the remote SMTP server. + ''; + }; + username = mkOption { + type = types.nullOr types.str; + default = null; + description = mdDoc '' + The username to login to the remote SMTP server. + ''; + }; + password_file = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + The path to the file containing the password to login to the remote SMTP server. + Should not be in /nix/store! + ''; + }; + sender = mkOption { + type = types.nullOr types.str; + default = null; + description = mdDoc '' + The email of the sender. Some SMTP servers require this to be the real FROM email address of the user. + ''; + }; + use_tls = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Use TLS to connect. Mutually exclusive with `start_tls` + ''; + }; + start_tls = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Use STARTLS to connect. Mutually exclusive with `use_tls` + ''; + }; + smime_cert = mkOption { + type = types.nullOr types.path; + default = null; + example = "''\${./mycert.pem}"; + description = mdDoc '' + The path to the S/MIME certificate used to sign messages. + If empty, will neither encrypt, nor sign relayed messages. + ''; + }; + smime_cert_private = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + The path to the S/MIME private key for the certificate used to sign messages. + Should not be in /nix/store! + ''; + }; + }; + emails = mkOption { + type = types.nullOr (types.attrsOf (types.nullOr types.str)); + default = null; + description = '' + A required set of recipients. A certificate is optional, but required if + messages should be signed and encrypted. + ''; + example = { + "monitoring.foo@example.com" = "`/path/to/certificate`"; + "unencrypted@example.com" = "`null`"; + }; + }; + }; + + config = mkIf cfg.enable { + + assertions = [ + { + assertion = !cfg.client.use_tls || !cfg.client.start_tls; + message = "Use either TLS or STARTTLS, not both."; + } + { + assertion = cfg.client.smime_cert == null || (cfg.client.smime_cert + != null && cfg.client.smime_cert_private != null); + message = + "If a S/MIME certificate should be used to sign messages, the private key to this certificate must be supplied."; + } + { + assertion = cfg.emails != null; + message = "At least one recipient must be configured."; + } + ]; + + systemd.services = { + smtprd-ng = { + description = "Run local SMTP relay"; + wantedBy = [ "multi-user.target" ]; + requires = [ "network.target" ]; + serviceConfig = { + DynamicUser = true; + User = "smtprd-ng"; + Group = "smtprd-ng"; + Restart = "on-failure"; + ExecStart = "${cfg.package}/bin/smtprd-ng --config ${confFile}"; + }; + }; + }; + }; +}