diff options
author | Alyssa Ross <hi@alyssa.is> | 2020-01-10 15:24:25 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2020-01-11 16:17:03 +0000 |
commit | d83f59918fadf871c6ea16643b7208a8e38f7016 (patch) | |
tree | eba008d9a8eef9bd9626623377515a922f4d3154 /nixpkgs | |
parent | 91a60bc48c7a3edf372a43611dde9d49692f26f2 (diff) | |
download | nixlib-d83f59918fadf871c6ea16643b7208a8e38f7016.tar nixlib-d83f59918fadf871c6ea16643b7208a8e38f7016.tar.gz nixlib-d83f59918fadf871c6ea16643b7208a8e38f7016.tar.bz2 nixlib-d83f59918fadf871c6ea16643b7208a8e38f7016.tar.lz nixlib-d83f59918fadf871c6ea16643b7208a8e38f7016.tar.xz nixlib-d83f59918fadf871c6ea16643b7208a8e38f7016.tar.zst nixlib-d83f59918fadf871c6ea16643b7208a8e38f7016.zip |
nixos/public-inbox: init
This module encapsulates pretty much all of public-inbox's functionality. While there are a lot of options, they're only exposed for things that either I think have a high chance of being something a large proportion of users need to set, or if the module needs to do some special setup to accomodate them. All other public-inbox configuration can be set through the `config' options.
Diffstat (limited to 'nixpkgs')
-rw-r--r-- | nixpkgs/nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixpkgs/nixos/modules/services/mail/public-inbox.nix | 431 | ||||
-rw-r--r-- | nixpkgs/nixos/modules/services/mail/public-inbox.psgi | 16 |
3 files changed, 448 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/module-list.nix b/nixpkgs/nixos/modules/module-list.nix index 6321f6fc3ac3..46a8232dc8e0 100644 --- a/nixpkgs/nixos/modules/module-list.nix +++ b/nixpkgs/nixos/modules/module-list.nix @@ -391,6 +391,7 @@ ./services/mail/postfix.nix ./services/mail/postsrsd.nix ./services/mail/postgrey.nix + ./services/mail/public-inbox.nix ./services/mail/spamassassin.nix ./services/mail/rspamd.nix ./services/mail/rss2email.nix diff --git a/nixpkgs/nixos/modules/services/mail/public-inbox.nix b/nixpkgs/nixos/modules/services/mail/public-inbox.nix new file mode 100644 index 000000000000..9bc5cf17a7f5 --- /dev/null +++ b/nixpkgs/nixos/modules/services/mail/public-inbox.nix @@ -0,0 +1,431 @@ +{ lib, pkgs, config, ... }: + +with lib; + +let + cfg = config.services.public-inbox; + + inboxesDir = "/var/lib/public-inbox/inboxes"; + inboxPath = name: "${inboxesDir}/${name}"; + gitPath = name: "${inboxPath name}/all.git"; + + inboxes = mapAttrs (name: inbox: + (recursiveUpdate { + inherit (inbox) address url newsgroup watch; + mainrepo = inboxPath name; + watchheader = inbox.watchHeader; + } inbox.config)) + cfg.inboxes; + + concat = concatMap id; + + configToList = attrs: + concat (mapAttrsToList (name': value': + if isAttrs value' then + map ({ name, value }: nameValuePair "${name'}.${name}" value) + (configToList value') + else if isList value' then map (nameValuePair name') value' + else if value' == null then [] + else [ (nameValuePair name' value') ]) attrs); + + configFull = recursiveUpdate { + publicinbox = inboxes // { + nntpserver = cfg.nntpServer; + wwwlisting = cfg.wwwListing; + }; + publicinboxmda.spamcheck = cfg.mda.spamCheck; + publicinboxwatch.spamcheck = cfg.watch.spamCheck; + publicinboxwatch.watchspam = cfg.watch.watchSpam; + } cfg.config; + + configList = configToList configFull; + + gitConfig = key: val: '' + ${pkgs.git}/bin/git config --add --file $out ${escapeShellArgs [ key val ]} + ''; + + configFile = pkgs.runCommand "public-inbox-config" {} + (concatStrings (map ({ name, value }: gitConfig name value) configList)); + + environment = { + PI_EMERGENCY = "/var/lib/public-inbox/emergency"; + PI_CONFIG = configFile; + }; + + envList = mapAttrsToList (n: v: "${n}=${v}") environment; + + # Can't use pkgs.linkFarm, + # because Postfix rejects .forward if it's a symlink. + home = pkgs.runCommand "public-inbox-home" { + forward = '' + |"env ${concatStringsSep " " envList} PATH=\"${makeBinPath cfg.path}:$PATH\" ${cfg.package}/bin/public-inbox-mda + ''; + passAsFile = [ "forward" ]; + } '' + mkdir $out + ln -s /var/lib/public-inbox/spamassassin $out/.spamassassin + cp $forwardPath $out/.forward + ''; + + psgi = pkgs.substituteAll { + src = ./public-inbox.psgi; + perl = "${cfg.package.fullperl}/bin/perl"; + path = cfg.http.mount; + }; + + descriptionFile = { description, ... }: + pkgs.writeText "description" description; + + enableWatch = (any (i: i.watch != []) (attrValues cfg.inboxes)) + || (cfg.watch.watchSpam != null); + + useSpamAssassin = cfg.mda.spamCheck == "spamc" || + cfg.watch.spamCheck == "spamc"; + +in + +{ + options = { + services.public-inbox = { + enable = mkEnableOption "the public-inbox mail archiver"; + + package = mkOption { + type = types.package; + default = pkgs.public-inbox; + description = '' + public-inbox package to use with the public-inbox module + ''; + }; + + path = mkOption { + type = with types; listOf package; + default = []; + example = literalExample "with pkgs; [ spamassassin ]"; + description = '' + Additional packages to place in the path of public-inbox-mda, + public-inbox-watch, etc. + ''; + }; + + inboxes = mkOption { + description = '' + Inboxes to configure, where attribute names are inbox names + ''; + type = with types; loaOf (submodule { + options = { + address = mkOption { + type = listOf str; + example = "example-discuss@example.org"; + }; + + url = mkOption { + type = nullOr str; + default = null; + example = "https://example.org/lists/example-discuss"; + description = '' + URL where this inbox can be accessed over HTTP + ''; + }; + + description = mkOption { + type = str; + example = "user/dev discussion of public-inbox itself"; + description = '' + User-visible description for the repository + ''; + }; + + config = mkOption { + type = attrs; + default = {}; + description = '' + Additional structured config for the inbox + ''; + }; + + newsgroup = mkOption { + type = nullOr str; + default = null; + description = '' + NNTP group name for the inbox + ''; + }; + + watch = mkOption { + type = listOf str; + default = []; + description = '' + Paths for public-inbox-watch(1) to monitor for new mail + ''; + example = [ "maildir:/path/to/test.example.com.git" ]; + }; + + watchHeader = mkOption { + type = nullOr str; + default = null; + example = "List-Id:<test@example.com>"; + description = '' + If specified, public-inbox-watch(1) will only process + mail containing a matching header. + ''; + }; + }; + }); + }; + + mda = { + spamCheck = mkOption { + type = with types; nullOr (enum [ "spamc" ]); + default = "spamc"; + description = '' + If set to spamc, public-inbox-mda(1) will filter spam + using SpamAssassin + ''; + }; + }; + + watch = { + spamCheck = mkOption { + type = with types; nullOr (enum [ "spamc" ]); + default = "spamc"; + description = '' + If set to spamc, public-inbox-watch(1) will filter spam + using SpamAssassin + ''; + }; + + watchSpam = mkOption { + type = with types; nullOr str; + default = null; + example = "maildir:/path/to/spam"; + description = '' + If set, mail in this maildir will be trained as spam and + deleted from all watched inboxes + ''; + }; + }; + + http = { + mount = mkOption { + type = types.str; + default = "/"; + example = "/lists/archives"; + description = '' + Path components to prepend to public-inbox HTTP endpoints + ''; + }; + + listenStreams = mkOption { + type = with types; listOf str; + default = [ "/run/public-inbox-httpd.sock" ]; + description = '' + systemd.socket(5) ListenStream values for the + public-inbox-httpd service to listen on + ''; + }; + }; + + nntp = { + listenStreams = mkOption { + type = with types; listOf str; + default = [ "0.0.0.0:119" "0.0.0.0:563" ]; + description = '' + systemd.socket(5) ListenStream values for the + public-inbox-nntpd service to listen on + ''; + }; + + cert = mkOption { + type = with types; nullOr str; + default = null; + example = "/path/to/fullchain.pem"; + description = '' + Path to TLS certificate to use for public-inbox NNTP connections + ''; + }; + + key = mkOption { + type = with types; nullOr str; + default = null; + example = "/path/to/key.pem"; + description = '' + Path to TLS key to use for public-inbox NNTP connections + ''; + }; + + extraGroups = mkOption { + type = with types; listOf str; + default = []; + example = [ "tls" ]; + description = '' + Secondary groups to assign to the systemd DynamicUser + running public-inbox-nntpd, in addition to the + public-inbox group. This is useful for giving + public-inbox-nntpd access to a TLS certificate / key, for + example. + ''; + }; + }; + + nntpServer = mkOption { + type = with types; listOf str; + default = []; + example = [ "nntp://news.public-inbox.org" "nntps://news.public-inbox.org" ]; + description = '' + NNTP URLs to this public-inbox instance + ''; + }; + + wwwListing = mkOption { + type = with types; enum [ "all" "404" "match=domain" ]; + default = "404"; + description = '' + Controls which lists (if any) are listed for when the root + public-inbox URL is accessed over HTTP. + ''; + }; + + spamAssassinRules = mkOption { + type = with types; nullOr path; + default = "${cfg.package.sa_config}/user/.spamassassin/user_prefs"; + description = '' + SpamAssassin configuration specific to public-inbox + ''; + }; + + config = mkOption { + type = types.attrs; + default = {}; + description = '' + Additional structured config for the public-inbox config file + ''; + }; + }; + }; + + config = mkIf cfg.enable { + + assertions = [ + { assertion = config.services.spamassassin.enable || !useSpamAssassin; + message = '' + public-inbox is configured to use SpamAssassin, but + services.spamassassin.enable is false. If you don't need + spam checking, set services.public-inbox.mda.spamCheck and + services.public-inbox.watch.spamCheck to null. + ''; + } + { assertion = cfg.path != [] || !useSpamAssassin; + message = '' + public-inbox is configured to use SpamAssassin, but there is + no spamc executable in services.public-inbox.path. If you + don't need spam checking, set + services.public-inbox.mda.spamCheck and + services.public-inbox.watch.spamCheck to null. + ''; + } + ]; + + users.users.public-inbox = { + inherit home; + group = "public-inbox"; + isSystemUser = true; + }; + + users.groups.public-inbox = {}; + + systemd.sockets.public-inbox-httpd = { + inherit (cfg.http) listenStreams; + wantedBy = [ "sockets.target" ]; + }; + + systemd.sockets.public-inbox-nntpd = { + inherit (cfg.nntp) listenStreams; + wantedBy = [ "sockets.target" ]; + }; + + systemd.services.public-inbox-httpd = { + inherit environment; + serviceConfig.ExecStart = "${cfg.package}/bin/public-inbox-httpd ${psgi}"; + serviceConfig.NonBlocking = true; + serviceConfig.DynamicUser = true; + serviceConfig.SupplementaryGroups = [ "public-inbox" ]; + }; + + systemd.services.public-inbox-nntpd = { + inherit environment; + serviceConfig.ExecStart = escapeShellArgs ( + [ "${cfg.package}/bin/public-inbox-nntpd" ] ++ + (optionals (cfg.nntp.cert != null) [ "--cert" cfg.nntp.cert ]) ++ + (optionals (cfg.nntp.key != null) [ "--key" cfg.nntp.key ]) + ); + serviceConfig.NonBlocking = true; + serviceConfig.DynamicUser = true; + serviceConfig.SupplementaryGroups = [ "public-inbox" ] ++ cfg.nntp.extraGroups; + }; + + systemd.services.public-inbox-watch = { + inherit environment; + inherit (cfg) path; + after = optional (cfg.watch.spamCheck == "spamc") "spamassassin.service"; + wantedBy = optional enableWatch "multi-user.target"; + serviceConfig.ExecStart = "${cfg.package}/bin/public-inbox-watch"; + serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + serviceConfig.User = "public-inbox"; + }; + + system.activationScripts.public-inbox = stringAfter [ "users" ] '' + install -m 0755 -o public-inbox -g public-inbox -d /var/lib/public-inbox + install -m 0750 -o public-inbox -g public-inbox -d ${inboxesDir} + install -m 0700 -o public-inbox -g public-inbox -d /var/lib/public-inbox/emergency + + ${optionalString useSpamAssassin '' + install -m 0700 -o spamd -d /var/lib/public-inbox/spamassassin + ${optionalString (cfg.spamAssassinRules != null) '' + ln -sf ${cfg.spamAssassinRules} /var/lib/public-inbox/spamassassin/user_prefs + ''} + ''} + + ${concatStrings (mapAttrsToList (name: { address, url, ... } @ inbox: '' + if [ ! -e ${escapeShellArg (inboxPath name)} ]; then + # public-inbox-init creates an inbox and adds it to a config file. + # It tries to atomically write the config file by creating + # another file in the same directory, and renaming it. + # This has the sad consequence that we can't use + # /dev/null, or it would try to create a file in /dev. + conf_dir="$(${pkgs.sudo}/bin/sudo -u public-inbox mktemp -d)" + + ${pkgs.sudo}/bin/sudo -u public-inbox \ + env PI_CONFIG=$conf_dir/conf \ + ${cfg.package}/bin/public-inbox-init -V2 \ + ${escapeShellArgs ([ name (inboxPath name) url ] ++ address)} + + rm -rf $conf_dir + fi + + ln -sf ${descriptionFile inbox} ${inboxPath name}/description + + if [ -d ${escapeShellArg (gitPath name)} ]; then + # Config is inherited by each epoch repository, + # so just needs to be set for all.git. + ${pkgs.git}/bin/git --git-dir ${gitPath name} \ + config core.sharedRepository 0640 + fi + '') cfg.inboxes)} + + for inbox in /var/lib/public-inbox/inboxes/*/; do + ls -1 "$inbox" | grep -q '^xap' && continue + + # This should be idempotent, but only do it for new + # inboxes anyway because it's only needed once, and could + # be slow for large pre-existing inboxes. + ${pkgs.sudo}/bin/sudo -u public-inbox \ + env ${concatStringsSep " " envList} \ + ${cfg.package}/bin/public-inbox-index "$inbox" + done + + ''; + + environment.systemPackages = with pkgs; [ cfg.package ]; + + }; +} diff --git a/nixpkgs/nixos/modules/services/mail/public-inbox.psgi b/nixpkgs/nixos/modules/services/mail/public-inbox.psgi new file mode 100644 index 000000000000..0808abe1d177 --- /dev/null +++ b/nixpkgs/nixos/modules/services/mail/public-inbox.psgi @@ -0,0 +1,16 @@ +#!@perl@ -w +# Copyright (C) 2014-2019 all contributors <meta@public-inbox.org> +# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> +use strict; +use PublicInbox::WWW; +use Plack::Builder; + +my $www = PublicInbox::WWW->new; +$www->preload; + +builder { + enable 'Head'; + enable 'ReverseProxy'; + mount q(@path@) => sub { $www->call(@_) }; +} + |