Merge pull request #104457 from ju1m/public-inbox

Update public-inbox to 1.8.0 and add systemd services

authored by Silvan Mosberger and committed by GitHub fd508269 914c76f1

+934 -200
+1
nixos/modules/module-list.nix
··· 505 505 ./services/mail/postfixadmin.nix 506 506 ./services/mail/postsrsd.nix 507 507 ./services/mail/postgrey.nix 508 + ./services/mail/public-inbox.nix 508 509 ./services/mail/spamassassin.nix 509 510 ./services/mail/rspamd.nix 510 511 ./services/mail/rss2email.nix
+579
nixos/modules/services/mail/public-inbox.nix
··· 1 + { lib, pkgs, config, ... }: 2 + 3 + with lib; 4 + 5 + let 6 + cfg = config.services.public-inbox; 7 + stateDir = "/var/lib/public-inbox"; 8 + 9 + manref = name: vol: "<citerefentry><refentrytitle>${name}</refentrytitle><manvolnum>${toString vol}</manvolnum></citerefentry>"; 10 + 11 + gitIni = pkgs.formats.gitIni { listsAsDuplicateKeys = true; }; 12 + iniAtom = elemAt gitIni.type/*attrsOf*/.functor.wrapped/*attrsOf*/.functor.wrapped/*either*/.functor.wrapped 0; 13 + 14 + useSpamAssassin = cfg.settings.publicinboxmda.spamcheck == "spamc" || 15 + cfg.settings.publicinboxwatch.spamcheck == "spamc"; 16 + 17 + publicInboxDaemonOptions = proto: defaultPort: { 18 + args = mkOption { 19 + type = with types; listOf str; 20 + default = []; 21 + description = "Command-line arguments to pass to ${manref "public-inbox-${proto}d" 1}."; 22 + }; 23 + port = mkOption { 24 + type = with types; nullOr (either str port); 25 + default = defaultPort; 26 + description = '' 27 + Listening port. 28 + Beware that public-inbox uses well-known ports number to decide whether to enable TLS or not. 29 + Set to null and use <code>systemd.sockets.public-inbox-${proto}d.listenStreams</code> 30 + if you need a more advanced listening. 31 + ''; 32 + }; 33 + cert = mkOption { 34 + type = with types; nullOr str; 35 + default = null; 36 + example = "/path/to/fullchain.pem"; 37 + description = "Path to TLS certificate to use for connections to ${manref "public-inbox-${proto}d" 1}."; 38 + }; 39 + key = mkOption { 40 + type = with types; nullOr str; 41 + default = null; 42 + example = "/path/to/key.pem"; 43 + description = "Path to TLS key to use for connections to ${manref "public-inbox-${proto}d" 1}."; 44 + }; 45 + }; 46 + 47 + serviceConfig = srv: 48 + let proto = removeSuffix "d" srv; 49 + needNetwork = builtins.hasAttr proto cfg && cfg.${proto}.port == null; 50 + in { 51 + serviceConfig = { 52 + # Enable JIT-compiled C (via Inline::C) 53 + Environment = [ "PERL_INLINE_DIRECTORY=/run/public-inbox-${srv}/perl-inline" ]; 54 + # NonBlocking is REQUIRED to avoid a race condition 55 + # if running simultaneous services. 56 + NonBlocking = true; 57 + #LimitNOFILE = 30000; 58 + User = config.users.users."public-inbox".name; 59 + Group = config.users.groups."public-inbox".name; 60 + RuntimeDirectory = [ 61 + "public-inbox-${srv}/perl-inline" 62 + ]; 63 + RuntimeDirectoryMode = "700"; 64 + # This is for BindPaths= and BindReadOnlyPaths= 65 + # to allow traversal of directories they create inside RootDirectory= 66 + UMask = "0066"; 67 + StateDirectory = ["public-inbox"]; 68 + StateDirectoryMode = "0750"; 69 + WorkingDirectory = stateDir; 70 + BindReadOnlyPaths = [ 71 + "/etc" 72 + "/run/systemd" 73 + "${config.i18n.glibcLocales}" 74 + ] ++ 75 + mapAttrsToList (name: inbox: inbox.description) cfg.inboxes ++ 76 + # Without confinement the whole Nix store 77 + # is made available to the service 78 + optionals (!config.systemd.services."public-inbox-${srv}".confinement.enable) [ 79 + "${pkgs.dash}/bin/dash:/bin/sh" 80 + builtins.storeDir 81 + ]; 82 + # The following options are only for optimizing: 83 + # systemd-analyze security public-inbox-'*' 84 + AmbientCapabilities = ""; 85 + CapabilityBoundingSet = ""; 86 + # ProtectClock= adds DeviceAllow=char-rtc r 87 + DeviceAllow = ""; 88 + LockPersonality = true; 89 + MemoryDenyWriteExecute = true; 90 + NoNewPrivileges = true; 91 + PrivateNetwork = mkDefault (!needNetwork); 92 + ProcSubset = "pid"; 93 + ProtectClock = true; 94 + ProtectHome = mkDefault true; 95 + ProtectHostname = true; 96 + ProtectKernelLogs = true; 97 + ProtectProc = "invisible"; 98 + #ProtectSystem = "strict"; 99 + RemoveIPC = true; 100 + RestrictAddressFamilies = [ "AF_UNIX" ] ++ 101 + optionals needNetwork [ "AF_INET" "AF_INET6" ]; 102 + RestrictNamespaces = true; 103 + RestrictRealtime = true; 104 + RestrictSUIDSGID = true; 105 + SystemCallFilter = [ 106 + "@system-service" 107 + "~@aio" "~@chown" "~@keyring" "~@memlock" "~@resources" 108 + # Not removing @setuid and @privileged because Inline::C needs them. 109 + # Not removing @timer because git upload-pack needs it. 110 + ]; 111 + SystemCallArchitectures = "native"; 112 + 113 + # The following options are redundant when confinement is enabled 114 + RootDirectory = "/var/empty"; 115 + TemporaryFileSystem = "/"; 116 + PrivateMounts = true; 117 + MountAPIVFS = true; 118 + PrivateDevices = true; 119 + PrivateTmp = true; 120 + PrivateUsers = true; 121 + ProtectControlGroups = true; 122 + ProtectKernelModules = true; 123 + ProtectKernelTunables = true; 124 + }; 125 + confinement = { 126 + # Until we agree upon doing it directly here in NixOS 127 + # https://github.com/NixOS/nixpkgs/pull/104457#issuecomment-1115768447 128 + # let the user choose to enable the confinement with: 129 + # systemd.services.public-inbox-httpd.confinement.enable = true; 130 + # systemd.services.public-inbox-imapd.confinement.enable = true; 131 + # systemd.services.public-inbox-init.confinement.enable = true; 132 + # systemd.services.public-inbox-nntpd.confinement.enable = true; 133 + #enable = true; 134 + mode = "full-apivfs"; 135 + # Inline::C needs a /bin/sh, and dash is enough 136 + binSh = "${pkgs.dash}/bin/dash"; 137 + packages = [ 138 + pkgs.iana-etc 139 + (getLib pkgs.nss) 140 + pkgs.tzdata 141 + ]; 142 + }; 143 + }; 144 + in 145 + 146 + { 147 + options.services.public-inbox = { 148 + enable = mkEnableOption "the public-inbox mail archiver"; 149 + package = mkOption { 150 + type = types.package; 151 + default = pkgs.public-inbox; 152 + defaultText = literalExpression "pkgs.public-inbox"; 153 + description = "public-inbox package to use."; 154 + }; 155 + path = mkOption { 156 + type = with types; listOf package; 157 + default = []; 158 + example = literalExpression "with pkgs; [ spamassassin ]"; 159 + description = '' 160 + Additional packages to place in the path of public-inbox-mda, 161 + public-inbox-watch, etc. 162 + ''; 163 + }; 164 + inboxes = mkOption { 165 + description = '' 166 + Inboxes to configure, where attribute names are inbox names. 167 + ''; 168 + default = {}; 169 + type = types.attrsOf (types.submodule ({name, ...}: { 170 + freeformType = types.attrsOf iniAtom; 171 + options.inboxdir = mkOption { 172 + type = types.str; 173 + default = "${stateDir}/inboxes/${name}"; 174 + description = "The absolute path to the directory which hosts the public-inbox."; 175 + }; 176 + options.address = mkOption { 177 + type = with types; listOf str; 178 + example = "example-discuss@example.org"; 179 + description = "The email addresses of the public-inbox."; 180 + }; 181 + options.url = mkOption { 182 + type = with types; nullOr str; 183 + default = null; 184 + example = "https://example.org/lists/example-discuss"; 185 + description = "URL where this inbox can be accessed over HTTP."; 186 + }; 187 + options.description = mkOption { 188 + type = types.str; 189 + example = "user/dev discussion of public-inbox itself"; 190 + description = "User-visible description for the repository."; 191 + apply = pkgs.writeText "public-inbox-description-${name}"; 192 + }; 193 + options.newsgroup = mkOption { 194 + type = with types; nullOr str; 195 + default = null; 196 + description = "NNTP group name for the inbox."; 197 + }; 198 + options.watch = mkOption { 199 + type = with types; listOf str; 200 + default = []; 201 + description = "Paths for ${manref "public-inbox-watch" 1} to monitor for new mail."; 202 + example = [ "maildir:/path/to/test.example.com.git" ]; 203 + }; 204 + options.watchheader = mkOption { 205 + type = with types; nullOr str; 206 + default = null; 207 + example = "List-Id:<test@example.com>"; 208 + description = '' 209 + If specified, ${manref "public-inbox-watch" 1} will only process 210 + mail containing a matching header. 211 + ''; 212 + }; 213 + options.coderepo = mkOption { 214 + type = (types.listOf (types.enum (attrNames cfg.settings.coderepo))) // { 215 + description = "list of coderepo names"; 216 + }; 217 + default = []; 218 + description = "Nicknames of a 'coderepo' section associated with the inbox."; 219 + }; 220 + })); 221 + }; 222 + imap = { 223 + enable = mkEnableOption "the public-inbox IMAP server"; 224 + } // publicInboxDaemonOptions "imap" 993; 225 + http = { 226 + enable = mkEnableOption "the public-inbox HTTP server"; 227 + mounts = mkOption { 228 + type = with types; listOf str; 229 + default = [ "/" ]; 230 + example = [ "/lists/archives" ]; 231 + description = '' 232 + Root paths or URLs that public-inbox will be served on. 233 + If domain parts are present, only requests to those 234 + domains will be accepted. 235 + ''; 236 + }; 237 + args = (publicInboxDaemonOptions "http" 80).args; 238 + port = mkOption { 239 + type = with types; nullOr (either str port); 240 + default = 80; 241 + example = "/run/public-inbox-httpd.sock"; 242 + description = '' 243 + Listening port or systemd's ListenStream= entry 244 + to be used as a reverse proxy, eg. in nginx: 245 + <code>locations."/inbox".proxyPass = "http://unix:''${config.services.public-inbox.http.port}:/inbox";</code> 246 + Set to null and use <code>systemd.sockets.public-inbox-httpd.listenStreams</code> 247 + if you need a more advanced listening. 248 + ''; 249 + }; 250 + }; 251 + mda = { 252 + enable = mkEnableOption "the public-inbox Mail Delivery Agent"; 253 + args = mkOption { 254 + type = with types; listOf str; 255 + default = []; 256 + description = "Command-line arguments to pass to ${manref "public-inbox-mda" 1}."; 257 + }; 258 + }; 259 + postfix.enable = mkEnableOption "the integration into Postfix"; 260 + nntp = { 261 + enable = mkEnableOption "the public-inbox NNTP server"; 262 + } // publicInboxDaemonOptions "nntp" 563; 263 + spamAssassinRules = mkOption { 264 + type = with types; nullOr path; 265 + default = "${cfg.package.sa_config}/user/.spamassassin/user_prefs"; 266 + defaultText = literalExpression "\${cfg.package.sa_config}/user/.spamassassin/user_prefs"; 267 + description = "SpamAssassin configuration specific to public-inbox."; 268 + }; 269 + settings = mkOption { 270 + description = '' 271 + Settings for the <link xlink:href="https://public-inbox.org/public-inbox-config.html">public-inbox config file</link>. 272 + ''; 273 + default = {}; 274 + type = types.submodule { 275 + freeformType = gitIni.type; 276 + options.publicinbox = mkOption { 277 + default = {}; 278 + description = "public inboxes"; 279 + type = types.submodule { 280 + freeformType = with types; /*inbox name*/attrsOf (/*inbox option name*/attrsOf /*inbox option value*/iniAtom); 281 + options.css = mkOption { 282 + type = with types; listOf str; 283 + default = []; 284 + description = "The local path name of a CSS file for the PSGI web interface."; 285 + }; 286 + options.nntpserver = mkOption { 287 + type = with types; listOf str; 288 + default = []; 289 + example = [ "nntp://news.public-inbox.org" "nntps://news.public-inbox.org" ]; 290 + description = "NNTP URLs to this public-inbox instance"; 291 + }; 292 + options.wwwlisting = mkOption { 293 + type = with types; enum [ "all" "404" "match=domain" ]; 294 + default = "404"; 295 + description = '' 296 + Controls which lists (if any) are listed for when the root 297 + public-inbox URL is accessed over HTTP. 298 + ''; 299 + }; 300 + }; 301 + }; 302 + options.publicinboxmda.spamcheck = mkOption { 303 + type = with types; enum [ "spamc" "none" ]; 304 + default = "none"; 305 + description = '' 306 + If set to spamc, ${manref "public-inbox-watch" 1} will filter spam 307 + using SpamAssassin. 308 + ''; 309 + }; 310 + options.publicinboxwatch.spamcheck = mkOption { 311 + type = with types; enum [ "spamc" "none" ]; 312 + default = "none"; 313 + description = '' 314 + If set to spamc, ${manref "public-inbox-watch" 1} will filter spam 315 + using SpamAssassin. 316 + ''; 317 + }; 318 + options.publicinboxwatch.watchspam = mkOption { 319 + type = with types; nullOr str; 320 + default = null; 321 + example = "maildir:/path/to/spam"; 322 + description = '' 323 + If set, mail in this maildir will be trained as spam and 324 + deleted from all watched inboxes 325 + ''; 326 + }; 327 + options.coderepo = mkOption { 328 + default = {}; 329 + description = "code repositories"; 330 + type = types.attrsOf (types.submodule { 331 + freeformType = types.attrsOf iniAtom; 332 + options.cgitUrl = mkOption { 333 + type = types.str; 334 + description = "URL of a cgit instance"; 335 + }; 336 + options.dir = mkOption { 337 + type = types.str; 338 + description = "Path to a git repository"; 339 + }; 340 + }); 341 + }; 342 + }; 343 + }; 344 + openFirewall = mkEnableOption "opening the firewall when using a port option"; 345 + }; 346 + config = mkIf cfg.enable { 347 + assertions = [ 348 + { assertion = config.services.spamassassin.enable || !useSpamAssassin; 349 + message = '' 350 + public-inbox is configured to use SpamAssassin, but 351 + services.spamassassin.enable is false. If you don't need 352 + spam checking, set `services.public-inbox.settings.publicinboxmda.spamcheck' and 353 + `services.public-inbox.settings.publicinboxwatch.spamcheck' to null. 354 + ''; 355 + } 356 + { assertion = cfg.path != [] || !useSpamAssassin; 357 + message = '' 358 + public-inbox is configured to use SpamAssassin, but there is 359 + no spamc executable in services.public-inbox.path. If you 360 + don't need spam checking, set 361 + `services.public-inbox.settings.publicinboxmda.spamcheck' and 362 + `services.public-inbox.settings.publicinboxwatch.spamcheck' to null. 363 + ''; 364 + } 365 + ]; 366 + services.public-inbox.settings = 367 + filterAttrsRecursive (n: v: v != null) { 368 + publicinbox = mapAttrs (n: filterAttrs (n: v: n != "description")) cfg.inboxes; 369 + }; 370 + users = { 371 + users.public-inbox = { 372 + home = stateDir; 373 + group = "public-inbox"; 374 + isSystemUser = true; 375 + }; 376 + groups.public-inbox = {}; 377 + }; 378 + networking.firewall = mkIf cfg.openFirewall 379 + { allowedTCPPorts = mkMerge 380 + (map (proto: (mkIf (cfg.${proto}.enable && types.port.check cfg.${proto}.port) [ cfg.${proto}.port ])) 381 + ["imap" "http" "nntp"]); 382 + }; 383 + services.postfix = mkIf (cfg.postfix.enable && cfg.mda.enable) { 384 + # Not sure limiting to 1 is necessary, but better safe than sorry. 385 + config.public-inbox_destination_recipient_limit = "1"; 386 + 387 + # Register the addresses as existing 388 + virtual = 389 + concatStringsSep "\n" (mapAttrsToList (_: inbox: 390 + concatMapStringsSep "\n" (address: 391 + "${address} ${address}" 392 + ) inbox.address 393 + ) cfg.inboxes); 394 + 395 + # Deliver the addresses with the public-inbox transport 396 + transport = 397 + concatStringsSep "\n" (mapAttrsToList (_: inbox: 398 + concatMapStringsSep "\n" (address: 399 + "${address} public-inbox:${address}" 400 + ) inbox.address 401 + ) cfg.inboxes); 402 + 403 + # The public-inbox transport 404 + masterConfig.public-inbox = { 405 + type = "unix"; 406 + privileged = true; # Required for user= 407 + command = "pipe"; 408 + args = [ 409 + "flags=X" # Report as a final delivery 410 + "user=${with config.users; users."public-inbox".name + ":" + groups."public-inbox".name}" 411 + # Specifying a nexthop when using the transport 412 + # (eg. test public-inbox:test) allows to 413 + # receive mails with an extension (eg. test+foo). 414 + "argv=${pkgs.writeShellScript "public-inbox-transport" '' 415 + export HOME="${stateDir}" 416 + export ORIGINAL_RECIPIENT="''${2:-1}" 417 + export PATH="${makeBinPath cfg.path}:$PATH" 418 + exec ${cfg.package}/bin/public-inbox-mda ${escapeShellArgs cfg.mda.args} 419 + ''} \${original_recipient} \${nexthop}" 420 + ]; 421 + }; 422 + }; 423 + systemd.sockets = mkMerge (map (proto: 424 + mkIf (cfg.${proto}.enable && cfg.${proto}.port != null) 425 + { "public-inbox-${proto}d" = { 426 + listenStreams = [ (toString cfg.${proto}.port) ]; 427 + wantedBy = [ "sockets.target" ]; 428 + }; 429 + } 430 + ) [ "imap" "http" "nntp" ]); 431 + systemd.services = mkMerge [ 432 + (mkIf cfg.imap.enable 433 + { public-inbox-imapd = mkMerge [(serviceConfig "imapd") { 434 + after = [ "public-inbox-init.service" "public-inbox-watch.service" ]; 435 + requires = [ "public-inbox-init.service" ]; 436 + serviceConfig = { 437 + ExecStart = escapeShellArgs ( 438 + [ "${cfg.package}/bin/public-inbox-imapd" ] ++ 439 + cfg.imap.args ++ 440 + optionals (cfg.imap.cert != null) [ "--cert" cfg.imap.cert ] ++ 441 + optionals (cfg.imap.key != null) [ "--key" cfg.imap.key ] 442 + ); 443 + }; 444 + }]; 445 + }) 446 + (mkIf cfg.http.enable 447 + { public-inbox-httpd = mkMerge [(serviceConfig "httpd") { 448 + after = [ "public-inbox-init.service" "public-inbox-watch.service" ]; 449 + requires = [ "public-inbox-init.service" ]; 450 + serviceConfig = { 451 + ExecStart = escapeShellArgs ( 452 + [ "${cfg.package}/bin/public-inbox-httpd" ] ++ 453 + cfg.http.args ++ 454 + # See https://public-inbox.org/public-inbox.git/tree/examples/public-inbox.psgi 455 + # for upstream's example. 456 + [ (pkgs.writeText "public-inbox.psgi" '' 457 + #!${cfg.package.fullperl} -w 458 + use strict; 459 + use warnings; 460 + use Plack::Builder; 461 + use PublicInbox::WWW; 462 + 463 + my $www = PublicInbox::WWW->new; 464 + $www->preload; 465 + 466 + builder { 467 + # If reached through a reverse proxy, 468 + # make it transparent by resetting some HTTP headers 469 + # used by public-inbox to generate URIs. 470 + enable 'ReverseProxy'; 471 + 472 + # No need to send a response body if it's an HTTP HEAD requests. 473 + enable 'Head'; 474 + 475 + # Route according to configured domains and root paths. 476 + ${concatMapStrings (path: '' 477 + mount q(${path}) => sub { $www->call(@_); }; 478 + '') cfg.http.mounts} 479 + } 480 + '') ] 481 + ); 482 + }; 483 + }]; 484 + }) 485 + (mkIf cfg.nntp.enable 486 + { public-inbox-nntpd = mkMerge [(serviceConfig "nntpd") { 487 + after = [ "public-inbox-init.service" "public-inbox-watch.service" ]; 488 + requires = [ "public-inbox-init.service" ]; 489 + serviceConfig = { 490 + ExecStart = escapeShellArgs ( 491 + [ "${cfg.package}/bin/public-inbox-nntpd" ] ++ 492 + cfg.nntp.args ++ 493 + optionals (cfg.nntp.cert != null) [ "--cert" cfg.nntp.cert ] ++ 494 + optionals (cfg.nntp.key != null) [ "--key" cfg.nntp.key ] 495 + ); 496 + }; 497 + }]; 498 + }) 499 + (mkIf (any (inbox: inbox.watch != []) (attrValues cfg.inboxes) 500 + || cfg.settings.publicinboxwatch.watchspam != null) 501 + { public-inbox-watch = mkMerge [(serviceConfig "watch") { 502 + inherit (cfg) path; 503 + wants = [ "public-inbox-init.service" ]; 504 + requires = [ "public-inbox-init.service" ] ++ 505 + optional (cfg.settings.publicinboxwatch.spamcheck == "spamc") "spamassassin.service"; 506 + wantedBy = [ "multi-user.target" ]; 507 + serviceConfig = { 508 + ExecStart = "${cfg.package}/bin/public-inbox-watch"; 509 + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 510 + }; 511 + }]; 512 + }) 513 + ({ public-inbox-init = let 514 + PI_CONFIG = gitIni.generate "public-inbox.ini" 515 + (filterAttrsRecursive (n: v: v != null) cfg.settings); 516 + in mkMerge [(serviceConfig "init") { 517 + wantedBy = [ "multi-user.target" ]; 518 + restartIfChanged = true; 519 + restartTriggers = [ PI_CONFIG ]; 520 + script = '' 521 + set -ux 522 + install -D -p ${PI_CONFIG} ${stateDir}/.public-inbox/config 523 + '' + optionalString useSpamAssassin '' 524 + install -m 0700 -o spamd -d ${stateDir}/.spamassassin 525 + ${optionalString (cfg.spamAssassinRules != null) '' 526 + ln -sf ${cfg.spamAssassinRules} ${stateDir}/.spamassassin/user_prefs 527 + ''} 528 + '' + concatStrings (mapAttrsToList (name: inbox: '' 529 + if [ ! -e ${stateDir}/inboxes/${escapeShellArg name} ]; then 530 + # public-inbox-init creates an inbox and adds it to a config file. 531 + # It tries to atomically write the config file by creating 532 + # another file in the same directory, and renaming it. 533 + # This has the sad consequence that we can't use 534 + # /dev/null, or it would try to create a file in /dev. 535 + conf_dir="$(mktemp -d)" 536 + 537 + PI_CONFIG=$conf_dir/conf \ 538 + ${cfg.package}/bin/public-inbox-init -V2 \ 539 + ${escapeShellArgs ([ name "${stateDir}/inboxes/${name}" inbox.url ] ++ inbox.address)} 540 + 541 + rm -rf $conf_dir 542 + fi 543 + 544 + ln -sf ${inbox.description} \ 545 + ${stateDir}/inboxes/${escapeShellArg name}/description 546 + 547 + export GIT_DIR=${stateDir}/inboxes/${escapeShellArg name}/all.git 548 + if test -d "$GIT_DIR"; then 549 + # Config is inherited by each epoch repository, 550 + # so just needs to be set for all.git. 551 + ${pkgs.git}/bin/git config core.sharedRepository 0640 552 + fi 553 + '') cfg.inboxes 554 + ) + '' 555 + shopt -s nullglob 556 + for inbox in ${stateDir}/inboxes/*/; do 557 + # This should be idempotent, but only do it for new 558 + # inboxes anyway because it's only needed once, and could 559 + # be slow for large pre-existing inboxes. 560 + ls -1 "$inbox" | grep -q '^xap' || 561 + ${cfg.package}/bin/public-inbox-index "$inbox" 562 + done 563 + ''; 564 + serviceConfig = { 565 + Type = "oneshot"; 566 + RemainAfterExit = true; 567 + StateDirectory = [ 568 + "public-inbox/.public-inbox" 569 + "public-inbox/.public-inbox/emergency" 570 + "public-inbox/inboxes" 571 + ]; 572 + }; 573 + }]; 574 + }) 575 + ]; 576 + environment.systemPackages = with pkgs; [ cfg.package ]; 577 + }; 578 + meta.maintainers = with lib.maintainers; [ julm qyliss ]; 579 + }
+1
nixos/tests/all-tests.nix
··· 456 456 proxy = handleTest ./proxy.nix {}; 457 457 prowlarr = handleTest ./prowlarr.nix {}; 458 458 pt2-clone = handleTest ./pt2-clone.nix {}; 459 + public-inbox = handleTest ./public-inbox.nix {}; 459 460 pulseaudio = discoverTests (import ./pulseaudio.nix); 460 461 qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {}; 461 462 quorum = handleTest ./quorum.nix {};
+227
nixos/tests/public-inbox.nix
··· 1 + import ./make-test-python.nix ({ pkgs, lib, ... }: 2 + let 3 + orga = "example"; 4 + domain = "${orga}.localdomain"; 5 + 6 + tls-cert = pkgs.runCommand "selfSignedCert" { buildInputs = [ pkgs.openssl ]; } '' 7 + openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 36500 \ 8 + -subj '/CN=machine.${domain}' 9 + install -D -t $out key.pem cert.pem 10 + ''; 11 + in 12 + { 13 + name = "public-inbox"; 14 + 15 + meta.maintainers = with pkgs.lib.maintainers; [ julm ]; 16 + 17 + machine = { config, pkgs, nodes, ... }: let 18 + inherit (config.services) gitolite public-inbox; 19 + # Git repositories paths in Gitolite. 20 + # Only their baseNameOf is used for configuring public-inbox. 21 + repositories = [ 22 + "user/repo1" 23 + "user/repo2" 24 + ]; 25 + in 26 + { 27 + virtualisation.diskSize = 1 * 1024; 28 + virtualisation.memorySize = 1 * 1024; 29 + networking.domain = domain; 30 + 31 + security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ]; 32 + # If using security.acme: 33 + #security.acme.certs."${domain}".postRun = '' 34 + # systemctl try-restart public-inbox-nntpd public-inbox-imapd 35 + #''; 36 + 37 + services.public-inbox = { 38 + enable = true; 39 + postfix.enable = true; 40 + openFirewall = true; 41 + settings.publicinbox = { 42 + css = [ "href=https://machine.${domain}/style/light.css" ]; 43 + nntpserver = [ "nntps://machine.${domain}" ]; 44 + wwwlisting = "match=domain"; 45 + }; 46 + mda = { 47 + enable = true; 48 + args = [ "--no-precheck" ]; # Allow Bcc: 49 + }; 50 + http = { 51 + enable = true; 52 + port = "/run/public-inbox-http.sock"; 53 + #port = 8080; 54 + args = ["-W0"]; 55 + mounts = [ 56 + "https://machine.${domain}/inbox" 57 + ]; 58 + }; 59 + nntp = { 60 + enable = true; 61 + #port = 563; 62 + args = ["-W0"]; 63 + cert = "${tls-cert}/cert.pem"; 64 + key = "${tls-cert}/key.pem"; 65 + }; 66 + imap = { 67 + enable = true; 68 + #port = 993; 69 + args = ["-W0"]; 70 + cert = "${tls-cert}/cert.pem"; 71 + key = "${tls-cert}/key.pem"; 72 + }; 73 + inboxes = lib.recursiveUpdate (lib.genAttrs (map baseNameOf repositories) (repo: { 74 + address = [ 75 + # Routed to the "public-inbox:" transport in services.postfix.transport 76 + "${repo}@${domain}" 77 + ]; 78 + description = '' 79 + ${repo}@${domain} : 80 + discussions about ${repo}. 81 + ''; 82 + url = "https://machine.${domain}/inbox/${repo}"; 83 + newsgroup = "inbox.comp.${orga}.${repo}"; 84 + coderepo = [ repo ]; 85 + })) 86 + { 87 + repo2 = { 88 + hide = [ 89 + "imap" # FIXME: doesn't work for IMAP as of public-inbox 1.6.1 90 + "manifest" 91 + "www" 92 + ]; 93 + }; 94 + }; 95 + settings.coderepo = lib.listToAttrs (map (path: lib.nameValuePair (baseNameOf path) { 96 + dir = "/var/lib/gitolite/repositories/${path}.git"; 97 + cgitUrl = "https://git.${domain}/${path}.git"; 98 + }) repositories); 99 + }; 100 + 101 + # Use gitolite to store Git repositories listed in coderepo entries 102 + services.gitolite = { 103 + enable = true; 104 + adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJmoTOQnGqX+//us5oye8UuE+tQBx9QEM7PN13jrwgqY root@localhost"; 105 + }; 106 + systemd.services.public-inbox-httpd = { 107 + serviceConfig.SupplementaryGroups = [ gitolite.group ]; 108 + }; 109 + 110 + # Use nginx as a reverse proxy for public-inbox-httpd 111 + services.nginx = { 112 + enable = true; 113 + recommendedGzipSettings = true; 114 + recommendedOptimisation = true; 115 + recommendedTlsSettings = true; 116 + recommendedProxySettings = true; 117 + virtualHosts."machine.${domain}" = { 118 + forceSSL = true; 119 + sslCertificate = "${tls-cert}/cert.pem"; 120 + sslCertificateKey = "${tls-cert}/key.pem"; 121 + locations."/".return = "302 /inbox"; 122 + locations."= /inbox".return = "302 /inbox/"; 123 + locations."/inbox".proxyPass = "http://unix:${public-inbox.http.port}:/inbox"; 124 + # If using TCP instead of a Unix socket: 125 + #locations."/inbox".proxyPass = "http://127.0.0.1:${toString public-inbox.http.port}/inbox"; 126 + # Referred to by settings.publicinbox.css 127 + # See http://public-inbox.org/meta/_/text/color/ 128 + locations."= /style/light.css".alias = pkgs.writeText "light.css" '' 129 + * { background:#fff; color:#000 } 130 + 131 + a { color:#00f; text-decoration:none } 132 + a:visited { color:#808 } 133 + 134 + *.q { color:#008 } 135 + 136 + *.add { color:#060 } 137 + *.del {color:#900 } 138 + *.head { color:#000 } 139 + *.hunk { color:#960 } 140 + 141 + .hl.num { color:#f30 } /* number */ 142 + .hl.esc { color:#f0f } /* escape character */ 143 + .hl.str { color:#f30 } /* string */ 144 + .hl.ppc { color:#c3c } /* preprocessor */ 145 + .hl.pps { color:#f30 } /* preprocessor string */ 146 + .hl.slc { color:#099 } /* single-line comment */ 147 + .hl.com { color:#099 } /* multi-line comment */ 148 + /* .hl.opt { color:#ccc } */ /* operator */ 149 + /* .hl.ipl { color:#ccc } */ /* interpolation */ 150 + 151 + /* keyword groups kw[a-z] */ 152 + .hl.kwa { color:#f90 } 153 + .hl.kwb { color:#060 } 154 + .hl.kwc { color:#f90 } 155 + /* .hl.kwd { color:#ccc } */ 156 + ''; 157 + }; 158 + }; 159 + 160 + services.postfix = { 161 + enable = true; 162 + setSendmail = true; 163 + #sslCert = "${tls-cert}/cert.pem"; 164 + #sslKey = "${tls-cert}/key.pem"; 165 + recipientDelimiter = "+"; 166 + }; 167 + 168 + environment.systemPackages = [ 169 + pkgs.mailutils 170 + pkgs.openssl 171 + ]; 172 + 173 + }; 174 + 175 + testScript = '' 176 + start_all() 177 + machine.wait_for_unit("multi-user.target") 178 + machine.wait_for_unit("public-inbox-init.service") 179 + 180 + # Very basic check that Gitolite can work; 181 + # Gitolite is not needed for the rest of this testScript 182 + machine.wait_for_unit("gitolite-init.service") 183 + 184 + # List inboxes through public-inbox-httpd 185 + machine.wait_for_unit("nginx.service") 186 + machine.succeed("curl -L https://machine.${domain} | grep repo1@${domain}") 187 + # The repo2 inbox is hidden 188 + machine.fail("curl -L https://machine.${domain} | grep repo2@${domain}") 189 + machine.wait_for_unit("public-inbox-httpd.service") 190 + 191 + # Send a mail and read it through public-inbox-httpd 192 + # Must work too when using a recipientDelimiter. 193 + machine.wait_for_unit("postfix.service") 194 + machine.succeed("mail -t <${pkgs.writeText "mail" '' 195 + Subject: Testing mail 196 + From: root@localhost 197 + To: repo1+extension@${domain} 198 + Message-ID: <repo1@root-1> 199 + Content-Type: text/plain; charset=utf-8 200 + Content-Disposition: inline 201 + 202 + This is a testing mail. 203 + ''}") 204 + machine.sleep(5) 205 + machine.succeed("curl -L 'https://machine.${domain}/inbox/repo1/repo1@root-1/T/#u' | grep 'This is a testing mail.'") 206 + 207 + # Read a mail through public-inbox-imapd 208 + machine.wait_for_open_port(993) 209 + machine.wait_for_unit("public-inbox-imapd.service") 210 + machine.succeed("openssl s_client -ign_eof -crlf -connect machine.${domain}:993 <${pkgs.writeText "imap-commands" '' 211 + tag login anonymous@${domain} anonymous 212 + tag SELECT INBOX.comp.${orga}.repo1.0 213 + tag FETCH 1 (BODY[HEADER]) 214 + tag LOGOUT 215 + ''} | grep '^Message-ID: <repo1@root-1>'") 216 + 217 + # TODO: Read a mail through public-inbox-nntpd 218 + #machine.wait_for_open_port(563) 219 + #machine.wait_for_unit("public-inbox-nntpd.service") 220 + 221 + # Delete a mail. 222 + # Note that the use of an extension not listed in the addresses 223 + # require to use --all 224 + machine.succeed("curl -L https://machine.example.localdomain/inbox/repo1/repo1@root-1/raw | sudo -u public-inbox public-inbox-learn rm --all") 225 + machine.fail("curl -L https://machine.example.localdomain/inbox/repo1/repo1@root-1/T/#u | grep 'This is a testing mail.'") 226 + ''; 227 + })
+11
pkgs/pkgs-lib/formats.nix
··· 135 135 136 136 }; 137 137 138 + gitIni = { listsAsDuplicateKeys ? false, ... }@args: { 139 + 140 + type = with lib.types; let 141 + 142 + iniAtom = (ini args).type/*attrsOf*/.functor.wrapped/*attrsOf*/.functor.wrapped; 143 + 144 + in attrsOf (attrsOf (either iniAtom (attrsOf iniAtom))); 145 + 146 + generate = name: value: pkgs.writeText name (lib.generators.toGitINI value); 147 + }; 148 + 138 149 toml = {}: json {} // { 139 150 type = with lib.types; let 140 151 valueType = oneOf [
-172
pkgs/servers/mail/public-inbox/0002-msgtime-drop-Date-Parse-for-RFC2822.patch
··· 1 - From c9b5164c954cd0de80d971f1c4ced16bf41ea81b Mon Sep 17 00:00:00 2001 2 - From: Eric Wong <e@80x24.org> 3 - Date: Fri, 29 Nov 2019 12:25:07 +0000 4 - Subject: [PATCH 2/2] msgtime: drop Date::Parse for RFC2822 5 - 6 - Date::Parse is not optimized for RFC2822 dates and isn't 7 - packaged on OpenBSD. It's still useful for historical 8 - email when email clients were less conformant, but is 9 - less relevant for new emails. 10 - --- 11 - lib/PublicInbox/MsgTime.pm | 115 ++++++++++++++++++++++++++++++++----- 12 - t/msgtime.t | 6 ++ 13 - 2 files changed, 107 insertions(+), 14 deletions(-) 14 - 15 - diff --git a/lib/PublicInbox/MsgTime.pm b/lib/PublicInbox/MsgTime.pm 16 - index 58e11d72..e9b27a49 100644 17 - --- a/lib/PublicInbox/MsgTime.pm 18 - +++ b/lib/PublicInbox/MsgTime.pm 19 - @@ -7,24 +7,114 @@ use strict; 20 - use warnings; 21 - use base qw(Exporter); 22 - our @EXPORT_OK = qw(msg_timestamp msg_datestamp); 23 - -use Date::Parse qw(str2time strptime); 24 - +use Time::Local qw(timegm); 25 - +my @MoY = qw(january february march april may june 26 - + july august september october november december); 27 - +my %MoY; 28 - +@MoY{@MoY} = (0..11); 29 - +@MoY{map { substr($_, 0, 3) } @MoY} = (0..11); 30 - + 31 - +my %OBSOLETE_TZ = ( # RFC2822 4.3 (Obsolete Date and Time) 32 - + EST => '-0500', EDT => '-0400', 33 - + CST => '-0600', CDT => '-0500', 34 - + MST => '-0700', MDT => '-0600', 35 - + PST => '-0800', PDT => '-0700', 36 - + UT => '+0000', GMT => '+0000', Z => '+0000', 37 - + 38 - + # RFC2822 states: 39 - + # The 1 character military time zones were defined in a non-standard 40 - + # way in [RFC822] and are therefore unpredictable in their meaning. 41 - +); 42 - +my $OBSOLETE_TZ = join('|', keys %OBSOLETE_TZ); 43 - 44 - sub str2date_zone ($) { 45 - my ($date) = @_; 46 - + my ($ts, $zone); 47 - + 48 - + # RFC822 is most likely for email, but we can tolerate an extra comma 49 - + # or punctuation as long as all the data is there. 50 - + # We'll use '\s' since Unicode spaces won't affect our parsing. 51 - + # SpamAssassin ignores commas and redundant spaces, too. 52 - + if ($date =~ /(?:[A-Za-z]+,?\s+)? # day-of-week 53 - + ([0-9]+),?\s+ # dd 54 - + ([A-Za-z]+)\s+ # mon 55 - + ([0-9]{2,})\s+ # YYYY or YY (or YYY :P) 56 - + ([0-9]+)[:\.] # HH: 57 - + ((?:[0-9]{2})|(?:\s?[0-9])) # MM 58 - + (?:[:\.]((?:[0-9]{2})|(?:\s?[0-9])))? # :SS 59 - + \s+ # a TZ offset is required: 60 - + ([\+\-])? # TZ sign 61 - + [\+\-]* # I've seen extra "-" e.g. "--500" 62 - + ([0-9]+|$OBSOLETE_TZ)(?:\s|$) # TZ offset 63 - + /xo) { 64 - + my ($dd, $m, $yyyy, $hh, $mm, $ss, $sign, $tz) = 65 - + ($1, $2, $3, $4, $5, $6, $7, $8); 66 - + # don't accept non-English months 67 - + defined(my $mon = $MoY{lc($m)}) or return; 68 - + 69 - + if (defined(my $off = $OBSOLETE_TZ{$tz})) { 70 - + $sign = substr($off, 0, 1); 71 - + $tz = substr($off, 1); 72 - + } 73 - + 74 - + # Y2K problems: 3-digit years, follow RFC2822 75 - + if (length($yyyy) <= 3) { 76 - + $yyyy += 1900; 77 - + 78 - + # and 2-digit years from '09 (2009) (0..49) 79 - + $yyyy += 100 if $yyyy < 1950; 80 - + } 81 - + 82 - + $ts = timegm($ss // 0, $mm, $hh, $dd, $mon, $yyyy); 83 - 84 - - my $ts = str2time($date); 85 - - return undef unless(defined $ts); 86 - + # Compute the time offset from [+-]HHMM 87 - + $tz //= 0; 88 - + my ($tz_hh, $tz_mm); 89 - + if (length($tz) == 1) { 90 - + $tz_hh = $tz; 91 - + $tz_mm = 0; 92 - + } elsif (length($tz) == 2) { 93 - + $tz_hh = 0; 94 - + $tz_mm = $tz; 95 - + } else { 96 - + $tz_hh = $tz; 97 - + $tz_hh =~ s/([0-9]{2})\z//; 98 - + $tz_mm = $1; 99 - + } 100 - + while ($tz_mm >= 60) { 101 - + $tz_mm -= 60; 102 - + $tz_hh += 1; 103 - + } 104 - + $sign //= '+'; 105 - + my $off = $sign . ($tz_mm * 60 + ($tz_hh * 60 * 60)); 106 - + $ts -= $off; 107 - + $sign = '+' if $off == 0; 108 - + $zone = sprintf('%s%02d%02d', $sign, $tz_hh, $tz_mm); 109 - 110 - - # off is the time zone offset in seconds from GMT 111 - - my ($ss,$mm,$hh,$day,$month,$year,$off) = strptime($date); 112 - - return undef unless(defined $off); 113 - + # Time::Zone and Date::Parse are part of the same distibution, 114 - + # and we need Time::Zone to deal with tz names like "EDT" 115 - + } elsif (eval { require Date::Parse }) { 116 - + $ts = Date::Parse::str2time($date); 117 - + return undef unless(defined $ts); 118 - 119 - - # Compute the time zone from offset 120 - - my $sign = ($off < 0) ? '-' : '+'; 121 - - my $hour = abs(int($off / 3600)); 122 - - my $min = ($off / 60) % 60; 123 - - my $zone = sprintf('%s%02d%02d', $sign, $hour, $min); 124 - + # off is the time zone offset in seconds from GMT 125 - + my ($ss,$mm,$hh,$day,$month,$year,$off) = 126 - + Date::Parse::strptime($date); 127 - + return undef unless(defined $off); 128 - + 129 - + # Compute the time zone from offset 130 - + my $sign = ($off < 0) ? '-' : '+'; 131 - + my $hour = abs(int($off / 3600)); 132 - + my $min = ($off / 60) % 60; 133 - + 134 - + $zone = sprintf('%s%02d%02d', $sign, $hour, $min); 135 - + } else { 136 - + warn "Date::Parse missing for non-RFC822 date: $date\n"; 137 - + return undef; 138 - + } 139 - 140 - + # Note: we've already applied the offset to $ts at this point, 141 - + # but we want to keep "git fsck" happy. 142 - # "-1200" is the furthest westermost zone offset, 143 - # but git fast-import is liberal so we use "-1400" 144 - if ($zone >= 1400 || $zone <= -1400) { 145 - @@ -59,9 +149,6 @@ sub msg_date_only ($) { 146 - my @date = $hdr->header_raw('Date'); 147 - my ($ts); 148 - foreach my $d (@date) { 149 - - # Y2K problems: 3-digit years 150 - - $d =~ s!([A-Za-z]{3}) ([0-9]{3}) ([0-9]{2}:[0-9]{2}:[0-9]{2})! 151 - - my $yyyy = $2 + 1900; "$1 $yyyy $3"!e; 152 - $ts = eval { str2date_zone($d) } and return $ts; 153 - if ($@) { 154 - my $mid = $hdr->header_raw('Message-ID'); 155 - diff --git a/t/msgtime.t b/t/msgtime.t 156 - index 6b396602..d9643b65 100644 157 - --- a/t/msgtime.t 158 - +++ b/t/msgtime.t 159 - @@ -84,4 +84,10 @@ is_deeply(datestamp('Fri, 28 Jun 2002 12:54:40 -700'), [1025294080, '-0700']); 160 - is_deeply(datestamp('Sat, 12 Jan 2002 12:52:57 -200'), [1010847177, '-0200']); 161 - is_deeply(datestamp('Mon, 05 Nov 2001 10:36:16 -800'), [1004985376, '-0800']); 162 - 163 - +# obsolete formats described in RFC2822 164 - +for (qw(UT GMT Z)) { 165 - + is_deeply(datestamp('Fri, 02 Oct 1993 00:00:00 '.$_), [ 749520000, '+0000']); 166 - +} 167 - +is_deeply(datestamp('Fri, 02 Oct 1993 00:00:00 EDT'), [ 749534400, '-0400']); 168 - + 169 - done_testing(); 170 - -- 171 - 2.24.1 172 -
+115 -28
pkgs/servers/mail/public-inbox/default.nix
··· 1 - { buildPerlPackage, lib, fetchurl, fetchpatch, makeWrapper 2 - , DBDSQLite, EmailMIME, IOSocketSSL, IPCRun, Plack, PlackMiddlewareReverseProxy 3 - , SearchXapian, TimeDate, URI 4 - , git, highlight, openssl, xapian 1 + { stdenv, lib, fetchurl, makeWrapper, nixosTests 2 + , buildPerlPackage 3 + , coreutils 4 + , curl 5 + , git 6 + , gnumake 7 + , highlight 8 + , libgit2 9 + , man 10 + , openssl 11 + , pkg-config 12 + , sqlite 13 + , xapian 14 + , AnyURIEscape 15 + , DBDSQLite 16 + , DBI 17 + , EmailAddressXS 18 + , EmailMIME 19 + , IOSocketSSL 20 + , IPCRun 21 + , Inline 22 + , InlineC 23 + , LinuxInotify2 24 + , MailIMAPClient 25 + , ParseRecDescent 26 + , Plack 27 + , PlackMiddlewareReverseProxy 28 + , SearchXapian 29 + , TimeDate 30 + , URI 5 31 }: 6 32 7 33 let 8 34 9 - # These tests would fail, and produce "Operation not permitted" 10 - # errors from git, because they use git init --shared. This tries 11 - # to set the setgid bit, which isn't permitted inside build 12 - # sandboxes. 13 - # 14 - # These tests were indentified with 15 - # grep -r shared t/ 16 - skippedTests = [ "convert-compact" "search" "v2writable" "www_listing" ]; 35 + skippedTests = [ 36 + # These tests would fail, and produce "Operation not permitted" 37 + # errors from git, because they use git init --shared. This tries 38 + # to set the setgid bit, which isn't permitted inside build 39 + # sandboxes. 40 + # 41 + # These tests were indentified with 42 + # grep -r shared t/ 43 + "convert-compact" "search" "v2writable" "www_listing" 44 + # perl5.32.0-public-inbox> t/eml.t ...................... 1/? Cannot parse parameter '=?ISO-8859-1?Q?=20charset=3D=1BOF?=' at t/eml.t line 270. 45 + # perl5.32.0-public-inbox> # Failed test 'got wide character by assuming utf-8' 46 + # perl5.32.0-public-inbox> # at t/eml.t line 272. 47 + # perl5.32.0-public-inbox> Wide character in print at /nix/store/38vxlxrvg3yji3jms44qn94lxdysbj5j-perl-5.32.0/lib/perl5/5.32.0/Test2/Formatter/TAP.pm line 125. 48 + "eml" 49 + # Failed test 'Makefile OK' 50 + # at t/hl_mod.t line 19. 51 + # got: 'makefile' 52 + # expected: 'make' 53 + "hl_mod" 54 + # Failed test 'clone + index v1 synced ->created_at' 55 + # at t/lei-mirror.t line 175. 56 + # got: '1638378723' 57 + # expected: undef 58 + # Failed test 'clone + index v1 synced ->created_at' 59 + # at t/lei-mirror.t line 178. 60 + # got: '1638378723' 61 + # expected: undef 62 + # May be due to the use of $ENV{HOME}. 63 + "lei-mirror" 64 + # Failed test 'child error (pure-Perl)' 65 + # at t/spawn.t line 33. 66 + # got: '0' 67 + # expected: anything else 68 + # waiting for child to reap grandchild... 69 + "spawn" 70 + ]; 17 71 18 72 testConditions = with lib; 19 73 concatMapStringsSep " " (n: "! -name ${escapeShellArg n}.t") skippedTests; ··· 22 76 23 77 buildPerlPackage rec { 24 78 pname = "public-inbox"; 25 - version = "1.2.0"; 79 + version = "1.8.0"; 26 80 27 81 src = fetchurl { 28 - url = "https://public-inbox.org/releases/public-inbox-${version}.tar.gz"; 29 - sha256 = "0sa2m4f2x7kfg3mi4im7maxqmqvawafma8f7g92nyfgybid77g6s"; 82 + url = "https://public-inbox.org/public-inbox.git/snapshot/public-inbox-${version}.tar.gz"; 83 + sha256 = "sha256-laJOOCk5NecIGWesv4D30cLGfijQHVkeo55eNqNKzew="; 30 84 }; 31 85 32 - patches = [ 33 - (fetchpatch { 34 - url = "https://public-inbox.org/meta/20200101032822.GA13063@dcvr/raw"; 35 - sha256 = "0ncxqqkvi5lwi8zaa7lk7l8mf8h278raxsvbvllh3z7jhfb48r3l"; 36 - }) 37 - ./0002-msgtime-drop-Date-Parse-for-RFC2822.patch 38 - ]; 39 - 40 86 outputs = [ "out" "devdoc" "sa_config" ]; 41 87 42 88 postConfigure = '' 43 89 substituteInPlace Makefile --replace 'TEST_FILES = t/*.t' \ 44 90 'TEST_FILES = $(shell find t -name *.t ${testConditions})' 91 + substituteInPlace lib/PublicInbox/TestCommon.pm \ 92 + --replace /bin/cp ${coreutils}/bin/cp 45 93 ''; 46 94 47 95 nativeBuildInputs = [ makeWrapper ]; 48 96 49 97 buildInputs = [ 50 - DBDSQLite EmailMIME IOSocketSSL IPCRun Plack PlackMiddlewareReverseProxy 51 - SearchXapian TimeDate URI highlight 98 + AnyURIEscape 99 + DBDSQLite 100 + DBI 101 + EmailAddressXS 102 + EmailMIME 103 + highlight 104 + IOSocketSSL 105 + IPCRun 106 + Inline 107 + InlineC 108 + ParseRecDescent 109 + Plack 110 + PlackMiddlewareReverseProxy 111 + SearchXapian 112 + TimeDate 113 + URI 114 + libgit2 # For Gcf2 115 + man 52 116 ]; 53 117 54 - checkInputs = [ git openssl xapian ]; 118 + doCheck = !stdenv.isDarwin; 119 + checkInputs = [ 120 + MailIMAPClient 121 + curl 122 + git 123 + openssl 124 + pkg-config 125 + sqlite 126 + xapian 127 + ] ++ lib.optionals stdenv.isLinux [ 128 + LinuxInotify2 129 + ]; 55 130 preCheck = '' 56 131 perl certs/create-certs.perl 132 + export TEST_LEI_ERR_LOUD=1 133 + export HOME="$NIX_BUILD_TOP"/home 134 + mkdir -p "$HOME"/.cache/public-inbox/inline-c 57 135 ''; 58 136 59 137 installTargets = [ "install" ]; 60 138 postInstall = '' 61 139 for prog in $out/bin/*; do 62 - wrapProgram $prog --prefix PATH : ${lib.makeBinPath [ git ]} 140 + wrapProgram $prog --prefix PATH : ${lib.makeBinPath [ 141 + git 142 + /* for InlineC */ 143 + gnumake 144 + stdenv.cc.cc 145 + ]} 63 146 done 64 147 65 148 mv sa_config $sa_config 66 149 ''; 67 150 151 + passthru.tests = { 152 + nixos-public-inbox = nixosTests.public-inbox; 153 + }; 154 + 68 155 meta = with lib; { 69 156 homepage = "https://public-inbox.org/"; 70 157 license = licenses.agpl3Plus; 71 - maintainers = with maintainers; [ qyliss ]; 158 + maintainers = with maintainers; [ julm qyliss ]; 72 159 platforms = platforms.all; 73 160 }; 74 161 }