nixos/public-inbox: init
This commit is contained in:
parent
9b6223d1fa
commit
8514800c42
3 changed files with 572 additions and 0 deletions
|
@ -490,6 +490,7 @@
|
|||
./services/mail/postfixadmin.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
|
||||
|
|
560
nixos/modules/services/mail/public-inbox.nix
Normal file
560
nixos/modules/services/mail/public-inbox.nix
Normal file
|
@ -0,0 +1,560 @@
|
|||
{ lib, pkgs, config, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.public-inbox;
|
||||
stateDir = "/var/lib/public-inbox";
|
||||
|
||||
manref = name: vol: "<citerefentry><refentrytitle>${name}</refentrytitle><manvolnum>${toString vol}</manvolnum></citerefentry>";
|
||||
|
||||
gitIni = pkgs.formats.gitIni { listsAsDuplicateKeys = true; };
|
||||
iniAtom = elemAt gitIni.type/*attrsOf*/.functor.wrapped/*attrsOf*/.functor.wrapped/*either*/.functor.wrapped 0;
|
||||
|
||||
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 ${manref "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 <code>systemd.sockets.public-inbox-${proto}d.listenStreams</code>
|
||||
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 ${manref "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 ${manref "public-inbox-${proto}d" 1}.";
|
||||
};
|
||||
};
|
||||
|
||||
serviceConfig = srv:
|
||||
let proto = removeSuffix "d" srv;
|
||||
needNetwork = builtins.hasAttr proto cfg && cfg.${proto}.port == null;
|
||||
in {
|
||||
# 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"
|
||||
# Create RootDirectory= in the host's mount namespace.
|
||||
"public-inbox-${srv}/root"
|
||||
];
|
||||
RuntimeDirectoryMode = "700";
|
||||
# Avoid mounting RootDirectory= in the own RootDirectory= of ExecStart='s mount namespace.
|
||||
InaccessiblePaths = ["-+/run/public-inbox-${srv}/root"];
|
||||
# This is for BindPaths= and BindReadOnlyPaths=
|
||||
# to allow traversal of directories they create in RootDirectory=.
|
||||
UMask = "0066";
|
||||
RootDirectory = "/run/public-inbox-${srv}/root";
|
||||
RootDirectoryStartOnly = true;
|
||||
WorkingDirectory = stateDir;
|
||||
MountAPIVFS = true;
|
||||
BindReadOnlyPaths = [
|
||||
builtins.storeDir
|
||||
"/etc"
|
||||
"/run"
|
||||
# For Inline::C
|
||||
"/bin/sh"
|
||||
];
|
||||
BindPaths = [
|
||||
stateDir
|
||||
];
|
||||
# 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;
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateNetwork = mkDefault (!needNetwork);
|
||||
PrivateTmp = true;
|
||||
PrivateUsers = true;
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = mkDefault true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = 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";
|
||||
};
|
||||
in
|
||||
|
||||
{
|
||||
options.services.public-inbox = {
|
||||
enable = mkEnableOption "the public-inbox mail archiver";
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.public-inbox;
|
||||
defaultText = literalExpression "pkgs.public-inbox";
|
||||
description = "public-inbox package to use.";
|
||||
};
|
||||
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 = with types; nullOr str;
|
||||
default = null;
|
||||
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.";
|
||||
};
|
||||
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 ${manref "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, ${manref "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:
|
||||
<code>locations."/inbox".proxyPass = "http://unix:''${config.services.public-inbox.http.port}:/inbox";</code>
|
||||
Set to null and use <code>systemd.sockets.public-inbox-httpd.listenStreams</code>
|
||||
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 ${manref "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 <link xlink:href="https://public-inbox.org/public-inbox-config.html">public-inbox config file</link>.
|
||||
'';
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
freeformType = gitIni.type;
|
||||
options.publicinbox = mkOption {
|
||||
default = {};
|
||||
description = "public inboxes";
|
||||
type = types.submodule {
|
||||
freeformType = with types; /*inbox name*/attrsOf (/*inbox option name*/attrsOf /*inbox option value*/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.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.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, ${manref "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, ${manref "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 = {
|
||||
after = [ "public-inbox-init.service" "public-inbox-watch.service" ];
|
||||
requires = [ "public-inbox-init.service" ];
|
||||
serviceConfig = mkMerge [(serviceConfig "imapd") {
|
||||
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 = {
|
||||
after = [ "public-inbox-init.service" "public-inbox-watch.service" ];
|
||||
requires = [ "public-inbox-init.service" ];
|
||||
serviceConfig = mkMerge [(serviceConfig "httpd") {
|
||||
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 = {
|
||||
after = [ "public-inbox-init.service" "public-inbox-watch.service" ];
|
||||
requires = [ "public-inbox-init.service" ];
|
||||
serviceConfig = mkMerge [(serviceConfig "nntpd") {
|
||||
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 = {
|
||||
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 = mkMerge [(serviceConfig "watch") {
|
||||
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 {
|
||||
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 ${pkgs.writeText "description" 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
|
||||
) + ''
|
||||
shopt -s nullglob
|
||||
for inbox in ${stateDir}/inboxes/*/; do
|
||||
# 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.
|
||||
ls -1 "$inbox" | grep -q '^xap' ||
|
||||
${cfg.package}/bin/public-inbox-index "$inbox"
|
||||
done
|
||||
'';
|
||||
serviceConfig = mkMerge [(serviceConfig "init") {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
StateDirectory = [
|
||||
"public-inbox"
|
||||
"public-inbox/.public-inbox"
|
||||
"public-inbox/.public-inbox/emergency"
|
||||
"public-inbox/inboxes"
|
||||
];
|
||||
StateDirectoryMode = "0750";
|
||||
}];
|
||||
};
|
||||
})
|
||||
];
|
||||
environment.systemPackages = with pkgs; [ cfg.package ];
|
||||
};
|
||||
meta.maintainers = with lib.maintainers; [ julm qyliss ];
|
||||
}
|
|
@ -123,6 +123,17 @@ rec {
|
|||
|
||||
};
|
||||
|
||||
gitIni = { listsAsDuplicateKeys ? false, ... }@args: {
|
||||
|
||||
type = with lib.types; let
|
||||
|
||||
iniAtom = (ini args).type/*attrsOf*/.functor.wrapped/*attrsOf*/.functor.wrapped;
|
||||
|
||||
in attrsOf (attrsOf (either iniAtom (attrsOf iniAtom)));
|
||||
|
||||
generate = name: value: pkgs.writeText name (lib.generators.toGitINI value);
|
||||
};
|
||||
|
||||
toml = {}: json {} // {
|
||||
type = with lib.types; let
|
||||
valueType = oneOf [
|
||||
|
|
Loading…
Reference in a new issue