mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-02-05 19:53:43 +00:00
4f0dadbf38
After final improvements to the official formatter implementation, this commit now performs the first treewide reformat of Nix files using it. This is part of the implementation of RFC 166. Only "inactive" files are reformatted, meaning only files that aren't being touched by any PR with activity in the past 2 months. This is to avoid conflicts for PRs that might soon be merged. Later we can do a full treewide reformat to get the rest, which should not cause as many conflicts. A CI check has already been running for some time to ensure that new and already-formatted files are formatted, so the files being reformatted here should also stay formatted. This commit was automatically created and can be verified using nix-builda08b3a4d19
.tar.gz \ --argstr baseRevb32a094368
result/bin/apply-formatting $NIXPKGS_PATH
706 lines
25 KiB
Nix
706 lines
25 KiB
Nix
{
|
|
lib,
|
|
pkgs,
|
|
config,
|
|
...
|
|
}:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.public-inbox;
|
|
stateDir = "/var/lib/public-inbox";
|
|
|
|
gitIni = pkgs.formats.gitIni { listsAsDuplicateKeys = true; };
|
|
iniAtom = gitIni.lib.types.atom;
|
|
|
|
useSpamAssassin =
|
|
cfg.settings.publicinboxmda.spamcheck == "spamc"
|
|
|| cfg.settings.publicinboxwatch.spamcheck == "spamc";
|
|
|
|
publicInboxDaemonOptions = proto: defaultPort: {
|
|
args = mkOption {
|
|
type = with types; listOf str;
|
|
default = [ ];
|
|
description = "Command-line arguments to pass to {manpage}`public-inbox-${proto}d(1)`.";
|
|
};
|
|
port = mkOption {
|
|
type = with types; nullOr (either str port);
|
|
default = defaultPort;
|
|
description = ''
|
|
Listening port.
|
|
Beware that public-inbox uses well-known ports number to decide whether to enable TLS or not.
|
|
Set to null and use `systemd.sockets.public-inbox-${proto}d.listenStreams`
|
|
if you need a more advanced listening.
|
|
'';
|
|
};
|
|
cert = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
example = "/path/to/fullchain.pem";
|
|
description = "Path to TLS certificate to use for connections to {manpage}`public-inbox-${proto}d(1)`.";
|
|
};
|
|
key = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
example = "/path/to/key.pem";
|
|
description = "Path to TLS key to use for connections to {manpage}`public-inbox-${proto}d(1)`.";
|
|
};
|
|
};
|
|
|
|
serviceConfig =
|
|
srv:
|
|
let
|
|
proto = removeSuffix "d" srv;
|
|
needNetwork = builtins.hasAttr proto cfg && cfg.${proto}.port == null;
|
|
in
|
|
{
|
|
serviceConfig = {
|
|
# Enable JIT-compiled C (via Inline::C)
|
|
Environment = [ "PERL_INLINE_DIRECTORY=/run/public-inbox-${srv}/perl-inline" ];
|
|
# NonBlocking is REQUIRED to avoid a race condition
|
|
# if running simultaneous services.
|
|
NonBlocking = true;
|
|
#LimitNOFILE = 30000;
|
|
User = config.users.users."public-inbox".name;
|
|
Group = config.users.groups."public-inbox".name;
|
|
RuntimeDirectory = [
|
|
"public-inbox-${srv}/perl-inline"
|
|
];
|
|
RuntimeDirectoryMode = "700";
|
|
# This is for BindPaths= and BindReadOnlyPaths=
|
|
# to allow traversal of directories they create inside RootDirectory=
|
|
UMask = "0066";
|
|
StateDirectory = [ "public-inbox" ];
|
|
StateDirectoryMode = "0750";
|
|
WorkingDirectory = stateDir;
|
|
BindReadOnlyPaths =
|
|
[
|
|
"/etc"
|
|
"/run/systemd"
|
|
"${config.i18n.glibcLocales}"
|
|
]
|
|
++ mapAttrsToList (name: inbox: inbox.description) cfg.inboxes
|
|
++
|
|
# Without confinement the whole Nix store
|
|
# is made available to the service
|
|
optionals (!config.systemd.services."public-inbox-${srv}".confinement.enable) [
|
|
"${pkgs.dash}/bin/dash:/bin/sh"
|
|
builtins.storeDir
|
|
];
|
|
# The following options are only for optimizing:
|
|
# systemd-analyze security public-inbox-'*'
|
|
AmbientCapabilities = "";
|
|
CapabilityBoundingSet = "";
|
|
# ProtectClock= adds DeviceAllow=char-rtc r
|
|
DeviceAllow = "";
|
|
LockPersonality = true;
|
|
MemoryDenyWriteExecute = true;
|
|
NoNewPrivileges = true;
|
|
PrivateNetwork = mkDefault (!needNetwork);
|
|
ProcSubset = "pid";
|
|
ProtectClock = true;
|
|
ProtectHome = "tmpfs";
|
|
ProtectHostname = true;
|
|
ProtectKernelLogs = true;
|
|
ProtectProc = "invisible";
|
|
#ProtectSystem = "strict";
|
|
RemoveIPC = true;
|
|
RestrictAddressFamilies =
|
|
[ "AF_UNIX" ]
|
|
++ optionals needNetwork [
|
|
"AF_INET"
|
|
"AF_INET6"
|
|
];
|
|
RestrictNamespaces = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
SystemCallFilter = [
|
|
"@system-service"
|
|
"~@aio"
|
|
"~@chown"
|
|
"~@keyring"
|
|
"~@memlock"
|
|
"~@resources"
|
|
# Not removing @setuid and @privileged because Inline::C needs them.
|
|
# Not removing @timer because git upload-pack needs it.
|
|
];
|
|
SystemCallArchitectures = "native";
|
|
|
|
# The following options are redundant when confinement is enabled
|
|
RootDirectory = "/var/empty";
|
|
TemporaryFileSystem = "/";
|
|
PrivateMounts = true;
|
|
MountAPIVFS = true;
|
|
PrivateDevices = true;
|
|
PrivateTmp = true;
|
|
PrivateUsers = true;
|
|
ProtectControlGroups = true;
|
|
ProtectKernelModules = true;
|
|
ProtectKernelTunables = true;
|
|
};
|
|
confinement = {
|
|
# Until we agree upon doing it directly here in NixOS
|
|
# https://github.com/NixOS/nixpkgs/pull/104457#issuecomment-1115768447
|
|
# let the user choose to enable the confinement with:
|
|
# systemd.services.public-inbox-httpd.confinement.enable = true;
|
|
# systemd.services.public-inbox-imapd.confinement.enable = true;
|
|
# systemd.services.public-inbox-init.confinement.enable = true;
|
|
# systemd.services.public-inbox-nntpd.confinement.enable = true;
|
|
#enable = true;
|
|
mode = "full-apivfs";
|
|
# Inline::C needs a /bin/sh, and dash is enough
|
|
binSh = "${pkgs.dash}/bin/dash";
|
|
packages = [
|
|
pkgs.iana-etc
|
|
(getLib pkgs.nss)
|
|
pkgs.tzdata
|
|
];
|
|
};
|
|
};
|
|
in
|
|
|
|
{
|
|
options.services.public-inbox = {
|
|
enable = mkEnableOption "the public-inbox mail archiver";
|
|
package = mkPackageOption pkgs "public-inbox" { };
|
|
path = mkOption {
|
|
type = with types; listOf package;
|
|
default = [ ];
|
|
example = literalExpression "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.
|
|
'';
|
|
default = { };
|
|
type = types.attrsOf (
|
|
types.submodule (
|
|
{ name, ... }:
|
|
{
|
|
freeformType = types.attrsOf iniAtom;
|
|
options.inboxdir = mkOption {
|
|
type = types.str;
|
|
default = "${stateDir}/inboxes/${name}";
|
|
description = "The absolute path to the directory which hosts the public-inbox.";
|
|
};
|
|
options.address = mkOption {
|
|
type = with types; listOf str;
|
|
example = "example-discuss@example.org";
|
|
description = "The email addresses of the public-inbox.";
|
|
};
|
|
options.url = mkOption {
|
|
type = types.nonEmptyStr;
|
|
example = "https://example.org/lists/example-discuss";
|
|
description = "URL where this inbox can be accessed over HTTP.";
|
|
};
|
|
options.description = mkOption {
|
|
type = types.str;
|
|
example = "user/dev discussion of public-inbox itself";
|
|
description = "User-visible description for the repository.";
|
|
apply = pkgs.writeText "public-inbox-description-${name}";
|
|
};
|
|
options.newsgroup = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
description = "NNTP group name for the inbox.";
|
|
};
|
|
options.watch = mkOption {
|
|
type = with types; listOf str;
|
|
default = [ ];
|
|
description = "Paths for {manpage}`public-inbox-watch(1)` to monitor for new mail.";
|
|
example = [ "maildir:/path/to/test.example.com.git" ];
|
|
};
|
|
options.watchheader = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
example = "List-Id:<test@example.com>";
|
|
description = ''
|
|
If specified, {manpage}`public-inbox-watch(1)` will only process
|
|
mail containing a matching header.
|
|
'';
|
|
};
|
|
options.coderepo = mkOption {
|
|
type = (types.listOf (types.enum (attrNames cfg.settings.coderepo))) // {
|
|
description = "list of coderepo names";
|
|
};
|
|
default = [ ];
|
|
description = "Nicknames of a 'coderepo' section associated with the inbox.";
|
|
};
|
|
}
|
|
)
|
|
);
|
|
};
|
|
imap = {
|
|
enable = mkEnableOption "the public-inbox IMAP server";
|
|
} // publicInboxDaemonOptions "imap" 993;
|
|
http = {
|
|
enable = mkEnableOption "the public-inbox HTTP server";
|
|
mounts = mkOption {
|
|
type = with types; listOf str;
|
|
default = [ "/" ];
|
|
example = [ "/lists/archives" ];
|
|
description = ''
|
|
Root paths or URLs that public-inbox will be served on.
|
|
If domain parts are present, only requests to those
|
|
domains will be accepted.
|
|
'';
|
|
};
|
|
args = (publicInboxDaemonOptions "http" 80).args;
|
|
port = mkOption {
|
|
type = with types; nullOr (either str port);
|
|
default = 80;
|
|
example = "/run/public-inbox-httpd.sock";
|
|
description = ''
|
|
Listening port or systemd's ListenStream= entry
|
|
to be used as a reverse proxy, eg. in nginx:
|
|
`locations."/inbox".proxyPass = "http://unix:''${config.services.public-inbox.http.port}:/inbox";`
|
|
Set to null and use `systemd.sockets.public-inbox-httpd.listenStreams`
|
|
if you need a more advanced listening.
|
|
'';
|
|
};
|
|
};
|
|
mda = {
|
|
enable = mkEnableOption "the public-inbox Mail Delivery Agent";
|
|
args = mkOption {
|
|
type = with types; listOf str;
|
|
default = [ ];
|
|
description = "Command-line arguments to pass to {manpage}`public-inbox-mda(1)`.";
|
|
};
|
|
};
|
|
postfix.enable = mkEnableOption "the integration into Postfix";
|
|
nntp = {
|
|
enable = mkEnableOption "the public-inbox NNTP server";
|
|
} // publicInboxDaemonOptions "nntp" 563;
|
|
spamAssassinRules = mkOption {
|
|
type = with types; nullOr path;
|
|
default = "${cfg.package.sa_config}/user/.spamassassin/user_prefs";
|
|
defaultText = literalExpression "\${cfg.package.sa_config}/user/.spamassassin/user_prefs";
|
|
description = "SpamAssassin configuration specific to public-inbox.";
|
|
};
|
|
settings = mkOption {
|
|
description = ''
|
|
Settings for the [public-inbox config file](https://public-inbox.org/public-inbox-config.html).
|
|
'';
|
|
default = { };
|
|
type = types.submodule {
|
|
freeformType = gitIni.type;
|
|
options.publicinbox = mkOption {
|
|
default = { };
|
|
description = "public inboxes";
|
|
type = types.submodule {
|
|
# Support both global options like `services.public-inbox.settings.publicinbox.imapserver`
|
|
# and inbox specific options like `services.public-inbox.settings.publicinbox.foo.address`.
|
|
freeformType =
|
|
with types;
|
|
attrsOf (oneOf [
|
|
iniAtom
|
|
(attrsOf iniAtom)
|
|
]);
|
|
|
|
options.css = mkOption {
|
|
type = with types; listOf str;
|
|
default = [ ];
|
|
description = "The local path name of a CSS file for the PSGI web interface.";
|
|
};
|
|
options.imapserver = mkOption {
|
|
type = with types; listOf str;
|
|
default = [ ];
|
|
example = [ "imap.public-inbox.org" ];
|
|
description = "IMAP URLs to this public-inbox instance";
|
|
};
|
|
options.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";
|
|
};
|
|
options.pop3server = mkOption {
|
|
type = with types; listOf str;
|
|
default = [ ];
|
|
example = [ "pop.public-inbox.org" ];
|
|
description = "POP3 URLs to this public-inbox instance";
|
|
};
|
|
options.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.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
options.publicinboxmda.spamcheck = mkOption {
|
|
type =
|
|
with types;
|
|
enum [
|
|
"spamc"
|
|
"none"
|
|
];
|
|
default = "none";
|
|
description = ''
|
|
If set to spamc, {manpage}`public-inbox-watch(1)` will filter spam
|
|
using SpamAssassin.
|
|
'';
|
|
};
|
|
options.publicinboxwatch.spamcheck = mkOption {
|
|
type =
|
|
with types;
|
|
enum [
|
|
"spamc"
|
|
"none"
|
|
];
|
|
default = "none";
|
|
description = ''
|
|
If set to spamc, {manpage}`public-inbox-watch(1)` will filter spam
|
|
using SpamAssassin.
|
|
'';
|
|
};
|
|
options.publicinboxwatch.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
|
|
'';
|
|
};
|
|
options.coderepo = mkOption {
|
|
default = { };
|
|
description = "code repositories";
|
|
type = types.attrsOf (
|
|
types.submodule {
|
|
freeformType = types.attrsOf iniAtom;
|
|
options.cgitUrl = mkOption {
|
|
type = types.str;
|
|
description = "URL of a cgit instance";
|
|
};
|
|
options.dir = mkOption {
|
|
type = types.str;
|
|
description = "Path to a git repository";
|
|
};
|
|
}
|
|
);
|
|
};
|
|
};
|
|
};
|
|
openFirewall = mkEnableOption "opening the firewall when using a port option";
|
|
};
|
|
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.settings.publicinboxmda.spamcheck' and
|
|
`services.public-inbox.settings.publicinboxwatch.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.settings.publicinboxmda.spamcheck' and
|
|
`services.public-inbox.settings.publicinboxwatch.spamcheck' to null.
|
|
'';
|
|
}
|
|
];
|
|
services.public-inbox.settings = filterAttrsRecursive (n: v: v != null) {
|
|
publicinbox = mapAttrs (n: filterAttrs (n: v: n != "description")) cfg.inboxes;
|
|
};
|
|
users = {
|
|
users.public-inbox = {
|
|
home = stateDir;
|
|
group = "public-inbox";
|
|
isSystemUser = true;
|
|
};
|
|
groups.public-inbox = { };
|
|
};
|
|
networking.firewall = mkIf cfg.openFirewall {
|
|
allowedTCPPorts = mkMerge (
|
|
map
|
|
(proto: (mkIf (cfg.${proto}.enable && types.port.check cfg.${proto}.port) [ cfg.${proto}.port ]))
|
|
[
|
|
"imap"
|
|
"http"
|
|
"nntp"
|
|
]
|
|
);
|
|
};
|
|
services.postfix = mkIf (cfg.postfix.enable && cfg.mda.enable) {
|
|
# Not sure limiting to 1 is necessary, but better safe than sorry.
|
|
config.public-inbox_destination_recipient_limit = "1";
|
|
|
|
# Register the addresses as existing
|
|
virtual = concatStringsSep "\n" (
|
|
mapAttrsToList (
|
|
_: inbox: concatMapStringsSep "\n" (address: "${address} ${address}") inbox.address
|
|
) cfg.inboxes
|
|
);
|
|
|
|
# Deliver the addresses with the public-inbox transport
|
|
transport = concatStringsSep "\n" (
|
|
mapAttrsToList (
|
|
_: inbox: concatMapStringsSep "\n" (address: "${address} public-inbox:${address}") inbox.address
|
|
) cfg.inboxes
|
|
);
|
|
|
|
# The public-inbox transport
|
|
masterConfig.public-inbox = {
|
|
type = "unix";
|
|
privileged = true; # Required for user=
|
|
command = "pipe";
|
|
args = [
|
|
"flags=X" # Report as a final delivery
|
|
"user=${with config.users; users."public-inbox".name + ":" + groups."public-inbox".name}"
|
|
# Specifying a nexthop when using the transport
|
|
# (eg. test public-inbox:test) allows to
|
|
# receive mails with an extension (eg. test+foo).
|
|
"argv=${pkgs.writeShellScript "public-inbox-transport" ''
|
|
export HOME="${stateDir}"
|
|
export ORIGINAL_RECIPIENT="''${2:-1}"
|
|
export PATH="${makeBinPath cfg.path}:$PATH"
|
|
exec ${cfg.package}/bin/public-inbox-mda ${escapeShellArgs cfg.mda.args}
|
|
''} \${original_recipient} \${nexthop}"
|
|
];
|
|
};
|
|
};
|
|
systemd.sockets = mkMerge (
|
|
map
|
|
(
|
|
proto:
|
|
mkIf (cfg.${proto}.enable && cfg.${proto}.port != null) {
|
|
"public-inbox-${proto}d" = {
|
|
listenStreams = [ (toString cfg.${proto}.port) ];
|
|
wantedBy = [ "sockets.target" ];
|
|
};
|
|
}
|
|
)
|
|
[
|
|
"imap"
|
|
"http"
|
|
"nntp"
|
|
]
|
|
);
|
|
systemd.services = mkMerge [
|
|
(mkIf cfg.imap.enable {
|
|
public-inbox-imapd = mkMerge [
|
|
(serviceConfig "imapd")
|
|
{
|
|
after = [
|
|
"public-inbox-init.service"
|
|
"public-inbox-watch.service"
|
|
];
|
|
requires = [ "public-inbox-init.service" ];
|
|
serviceConfig = {
|
|
ExecStart = escapeShellArgs (
|
|
[ "${cfg.package}/bin/public-inbox-imapd" ]
|
|
++ cfg.imap.args
|
|
++ optionals (cfg.imap.cert != null) [
|
|
"--cert"
|
|
cfg.imap.cert
|
|
]
|
|
++ optionals (cfg.imap.key != null) [
|
|
"--key"
|
|
cfg.imap.key
|
|
]
|
|
);
|
|
};
|
|
}
|
|
];
|
|
})
|
|
(mkIf cfg.http.enable {
|
|
public-inbox-httpd = mkMerge [
|
|
(serviceConfig "httpd")
|
|
{
|
|
after = [
|
|
"public-inbox-init.service"
|
|
"public-inbox-watch.service"
|
|
];
|
|
requires = [ "public-inbox-init.service" ];
|
|
serviceConfig = {
|
|
BindReadOnlyPaths = map (c: c.dir) (lib.attrValues cfg.settings.coderepo);
|
|
ExecStart = escapeShellArgs (
|
|
[ "${cfg.package}/bin/public-inbox-httpd" ]
|
|
++ cfg.http.args
|
|
++
|
|
# See https://public-inbox.org/public-inbox.git/tree/examples/public-inbox.psgi
|
|
# for upstream's example.
|
|
[
|
|
(pkgs.writeText "public-inbox.psgi" ''
|
|
#!${cfg.package.fullperl} -w
|
|
use strict;
|
|
use warnings;
|
|
use Plack::Builder;
|
|
use PublicInbox::WWW;
|
|
|
|
my $www = PublicInbox::WWW->new;
|
|
$www->preload;
|
|
|
|
builder {
|
|
# If reached through a reverse proxy,
|
|
# make it transparent by resetting some HTTP headers
|
|
# used by public-inbox to generate URIs.
|
|
enable 'ReverseProxy';
|
|
|
|
# No need to send a response body if it's an HTTP HEAD requests.
|
|
enable 'Head';
|
|
|
|
# Route according to configured domains and root paths.
|
|
${concatMapStrings (path: ''
|
|
mount q(${path}) => sub { $www->call(@_); };
|
|
'') cfg.http.mounts}
|
|
}
|
|
'')
|
|
]
|
|
);
|
|
};
|
|
}
|
|
];
|
|
})
|
|
(mkIf cfg.nntp.enable {
|
|
public-inbox-nntpd = mkMerge [
|
|
(serviceConfig "nntpd")
|
|
{
|
|
after = [
|
|
"public-inbox-init.service"
|
|
"public-inbox-watch.service"
|
|
];
|
|
requires = [ "public-inbox-init.service" ];
|
|
serviceConfig = {
|
|
ExecStart = escapeShellArgs (
|
|
[ "${cfg.package}/bin/public-inbox-nntpd" ]
|
|
++ cfg.nntp.args
|
|
++ optionals (cfg.nntp.cert != null) [
|
|
"--cert"
|
|
cfg.nntp.cert
|
|
]
|
|
++ optionals (cfg.nntp.key != null) [
|
|
"--key"
|
|
cfg.nntp.key
|
|
]
|
|
);
|
|
};
|
|
}
|
|
];
|
|
})
|
|
(mkIf
|
|
(
|
|
any (inbox: inbox.watch != [ ]) (attrValues cfg.inboxes)
|
|
|| cfg.settings.publicinboxwatch.watchspam != null
|
|
)
|
|
{
|
|
public-inbox-watch = mkMerge [
|
|
(serviceConfig "watch")
|
|
{
|
|
inherit (cfg) path;
|
|
wants = [ "public-inbox-init.service" ];
|
|
requires = [
|
|
"public-inbox-init.service"
|
|
] ++ optional (cfg.settings.publicinboxwatch.spamcheck == "spamc") "spamassassin.service";
|
|
wantedBy = [ "multi-user.target" ];
|
|
serviceConfig = {
|
|
ExecStart = "${cfg.package}/bin/public-inbox-watch";
|
|
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
|
};
|
|
}
|
|
];
|
|
}
|
|
)
|
|
({
|
|
public-inbox-init =
|
|
let
|
|
PI_CONFIG = gitIni.generate "public-inbox.ini" (
|
|
filterAttrsRecursive (n: v: v != null) cfg.settings
|
|
);
|
|
in
|
|
mkMerge [
|
|
(serviceConfig "init")
|
|
{
|
|
wantedBy = [ "multi-user.target" ];
|
|
restartIfChanged = true;
|
|
restartTriggers = [ PI_CONFIG ];
|
|
script =
|
|
''
|
|
set -ux
|
|
install -D -p ${PI_CONFIG} ${stateDir}/.public-inbox/config
|
|
''
|
|
+ optionalString useSpamAssassin ''
|
|
install -m 0700 -o spamd -d ${stateDir}/.spamassassin
|
|
${optionalString (cfg.spamAssassinRules != null) ''
|
|
ln -sf ${cfg.spamAssassinRules} ${stateDir}/.spamassassin/user_prefs
|
|
''}
|
|
''
|
|
+ concatStrings (
|
|
mapAttrsToList (name: inbox: ''
|
|
if [ ! -e ${stateDir}/inboxes/${escapeShellArg 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="$(mktemp -d)"
|
|
|
|
PI_CONFIG=$conf_dir/conf \
|
|
${cfg.package}/bin/public-inbox-init -V2 \
|
|
${escapeShellArgs (
|
|
[
|
|
name
|
|
"${stateDir}/inboxes/${name}"
|
|
inbox.url
|
|
]
|
|
++ inbox.address
|
|
)}
|
|
|
|
rm -rf $conf_dir
|
|
fi
|
|
|
|
ln -sf ${inbox.description} \
|
|
${stateDir}/inboxes/${escapeShellArg name}/description
|
|
|
|
export GIT_DIR=${stateDir}/inboxes/${escapeShellArg name}/all.git
|
|
if test -d "$GIT_DIR"; then
|
|
# Config is inherited by each epoch repository,
|
|
# so just needs to be set for all.git.
|
|
${pkgs.git}/bin/git config core.sharedRepository 0640
|
|
fi
|
|
'') cfg.inboxes
|
|
);
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
StateDirectory = [
|
|
"public-inbox/.public-inbox"
|
|
"public-inbox/.public-inbox/emergency"
|
|
"public-inbox/inboxes"
|
|
];
|
|
};
|
|
}
|
|
];
|
|
})
|
|
];
|
|
environment.systemPackages = with pkgs; [ cfg.package ];
|
|
};
|
|
meta.maintainers = with lib.maintainers; [
|
|
julm
|
|
qyliss
|
|
];
|
|
}
|