summary refs log tree commit diff
diff options
context:
space:
mode:
authorMartin Milata <martin@martinmilata.cz>2019-07-16 01:06:19 +0200
committerMartin Milata <martin@martinmilata.cz>2019-10-25 17:51:09 +0200
commitad034104cc97f7d7c4b1f20627f77d9d61f5b182 (patch)
treea3d48645ae3e099b87f936068b6ab15b9a2d6ec7
parent2985a83456fb79727e8aad0321a333d515e222fc (diff)
downloadnixpkgs-ad034104cc97f7d7c4b1f20627f77d9d61f5b182.tar
nixpkgs-ad034104cc97f7d7c4b1f20627f77d9d61f5b182.tar.gz
nixpkgs-ad034104cc97f7d7c4b1f20627f77d9d61f5b182.tar.bz2
nixpkgs-ad034104cc97f7d7c4b1f20627f77d9d61f5b182.tar.lz
nixpkgs-ad034104cc97f7d7c4b1f20627f77d9d61f5b182.tar.xz
nixpkgs-ad034104cc97f7d7c4b1f20627f77d9d61f5b182.tar.zst
nixpkgs-ad034104cc97f7d7c4b1f20627f77d9d61f5b182.zip
nixos/moin: init
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/web-apps/moinmoin.nix303
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/moinmoin.nix24
4 files changed, 329 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 5214126ff7e..652d7b4472d 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -804,6 +804,7 @@
   ./services/web-apps/nexus.nix
   ./services/web-apps/pgpkeyserver-lite.nix
   ./services/web-apps/matomo.nix
+  ./services/web-apps/moinmoin.nix
   ./services/web-apps/restya-board.nix
   ./services/web-apps/tt-rss.nix
   ./services/web-apps/selfoss.nix
diff --git a/nixos/modules/services/web-apps/moinmoin.nix b/nixos/modules/services/web-apps/moinmoin.nix
new file mode 100644
index 00000000000..0fee64be0bb
--- /dev/null
+++ b/nixos/modules/services/web-apps/moinmoin.nix
@@ -0,0 +1,303 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+  cfg = config.services.moinmoin;
+  python = pkgs.python27;
+  pkg = python.pkgs.moinmoin;
+  dataDir = "/var/lib/moin";
+  usingGunicorn = cfg.webServer == "nginx-gunicorn" || cfg.webServer == "gunicorn";
+  usingNginx = cfg.webServer == "nginx-gunicorn";
+  user = "moin";
+  group = "moin";
+
+  uLit = s: ''u"${s}"'';
+  indentLines = n: str: concatMapStrings (line: "${fixedWidthString n " " " "}${line}\n") (splitString "\n" str);
+
+  moinCliWrapper = wikiIdent: pkgs.writeShellScriptBin "moin-${wikiIdent}" ''
+    ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} -c "${pkg}/bin/moin --config-dir=/var/lib/moin/${wikiIdent}/config $*" ${user}
+  '';
+
+  wikiConfig = wikiIdent: w: ''
+    # -*- coding: utf-8 -*-
+
+    from MoinMoin.config import multiconfig, url_prefix_static
+
+    class Config(multiconfig.DefaultConfig):
+        ${optionalString (w.webLocation != "/") ''
+          url_prefix_static = '${w.webLocation}' + url_prefix_static
+        ''}
+
+        sitename = u'${w.siteName}'
+        page_front_page = u'${w.frontPage}'
+
+        data_dir = '${dataDir}/${wikiIdent}/data'
+        data_underlay_dir = '${dataDir}/${wikiIdent}/underlay'
+
+        language_default = u'${w.languageDefault}'
+        ${optionalString (w.superUsers != []) ''
+          superuser = [${concatMapStringsSep ", " uLit w.superUsers}]
+        ''}
+
+    ${indentLines 4 w.extraConfig}
+  '';
+  wikiConfigFile = name: wiki: pkgs.writeText "${name}.py" (wikiConfig name wiki);
+
+in
+{
+  options.services.moinmoin = with types; {
+    enable = mkEnableOption "MoinMoin Wiki Engine";
+
+    webServer = mkOption {
+      type = enum [ "nginx-gunicorn" "gunicorn" "none" ];
+      default = "nginx-gunicorn";
+      example = "none";
+      description = ''
+        Which web server to use to serve the wiki.
+        Use <literal>none</literal> if you want to configure this yourself.
+      '';
+    };
+
+    gunicorn.workers = mkOption {
+      type = ints.positive;
+      default = 3;
+      example = 10;
+      description = ''
+        The number of worker processes for handling requests.
+      '';
+    };
+
+    wikis = mkOption {
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          siteName = mkOption {
+            type = str;
+            default = "Untitled Wiki";
+            example = "ExampleWiki";
+            description = ''
+              Short description of your wiki site, displayed below the logo on each page, and
+              used in RSS documents as the channel title.
+            '';
+          };
+
+          webHost = mkOption {
+            type = str;
+            description = "Host part of the wiki URL. If undefined, the name of the attribute set will be used.";
+            example = "wiki.example.org";
+          };
+
+          webLocation = mkOption {
+            type = str;
+            default = "/";
+            example = "/moin";
+            description = "Location part of the wiki URL.";
+          };
+
+          frontPage = mkOption {
+            type = str;
+            default = "LanguageSetup";
+            example = "FrontPage";
+            description = ''
+              Front page name. Set this to something like <literal>FrontPage</literal> once languages are
+              configured.
+            '';
+          };
+
+          superUsers = mkOption {
+            type = listOf str;
+            default = [];
+            example = [ "elvis" ];
+            description = ''
+              List of trusted user names with wiki system administration super powers.
+
+              Please note that accounts for these users need to be created using the <command>moin</command> command-line utility, e.g.:
+              <command>moin-<replaceable>WIKINAME</replaceable> account create --name=<replaceable>NAME</replaceable> --email=<replaceable>EMAIL</replaceable> --password=<replaceable>PASSWORD</replaceable></command>.
+            '';
+          };
+
+          languageDefault = mkOption {
+            type = str;
+            default = "en";
+            example = "de";
+            description = "The ISO-639-1 name of the main wiki language. Languages that MoinMoin does not support are ignored.";
+          };
+
+          extraConfig = mkOption {
+            type = lines;
+            default = "";
+            example = ''
+              show_hosts = True
+              search_results_per_page = 100
+              acl_rights_default = u"Known:read,write,delete,revert All:read"
+              logo_string = u"<h2>\U0001f639</h2>"
+              theme_default = u"modernized"
+
+              user_checkbox_defaults = {'show_page_trail': 0, 'edit_on_doubleclick': 0}
+              navi_bar = [u'SomePage'] + multiconfig.DefaultConfig.navi_bar
+              actions_excluded = multiconfig.DefaultConfig.actions_excluded + ['newaccount']
+
+              mail_smarthost = "mail.example.org"
+              mail_from = u"Example.Org Wiki <wiki@example.org>"
+            '';
+            description = ''
+              Additional configuration to be appended verbatim to this wiki's config.
+
+              See <link xlink:href='http://moinmo.in/HelpOnConfiguration' /> for documentation.
+            '';
+          };
+
+        };
+        config = {
+          webHost = mkDefault name;
+        };
+      }));
+      example = literalExample ''
+        {
+          "mywiki" = {
+            siteName = "Example Wiki";
+            webHost = "wiki.example.org";
+            superUsers = [ "admin" ];
+            frontPage = "Index";
+            extraConfig = "page_category_regex = ur'(?P<all>(Category|Kategorie)(?P<key>(?!Template)\S+))'"
+          };
+        }
+      '';
+      description = ''
+        Configurations of the individual wikis. Attribute names must be valid Python
+        identifiers of the form <literal>[A-Za-z_][A-Za-z0-9_]*</literal>.
+
+        For every attribute <replaceable>WIKINAME</replaceable>, a helper script
+        moin-<replaceable>WIKINAME</replaceable> is created which runs the
+        <command>moin</command> command under the <literal>moin</literal> user (to avoid
+        file ownership issues) and with the right configuration directory passed to it.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = forEach (attrNames cfg.wikis) (wname:
+      { assertion = builtins.match "[A-Za-z_][A-Za-z0-9_]*" wname != null;
+        message = "${wname} is not valid Python identifier";
+      }
+    );
+
+    users.users = {
+      moin = {
+        description = "MoinMoin wiki";
+        home = dataDir;
+        group = group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = {
+      moin = {
+        members = mkIf usingNginx [ config.services.nginx.user ];
+      };
+    };
+
+    environment.systemPackages = [ pkg ] ++ map moinCliWrapper (attrNames cfg.wikis);
+
+    systemd.services = mkIf usingGunicorn
+      (flip mapAttrs' cfg.wikis (wikiIdent: wiki:
+        nameValuePair "moin-${wikiIdent}"
+          {
+            description = "MoinMoin wiki ${wikiIdent} - gunicorn process";
+            wantedBy = [ "multi-user.target" ];
+            after = [ "network.target" ];
+            restartIfChanged = true;
+            restartTriggers = [ (wikiConfigFile wikiIdent wiki) ];
+
+            environment = let
+              penv = python.buildEnv.override {
+                # setuptools: https://github.com/benoitc/gunicorn/issues/1716
+                extraLibs = [ python.pkgs.gevent python.pkgs.setuptools pkg ];
+              };
+            in {
+              PYTHONPATH = "${dataDir}/${wikiIdent}/config:${penv}/${python.sitePackages}";
+            };
+
+            preStart = ''
+              umask 0007
+              rm -rf ${dataDir}/${wikiIdent}/underlay
+              cp -r ${pkg}/share/moin/underlay ${dataDir}/${wikiIdent}/
+              chmod -R u+w ${dataDir}/${wikiIdent}/underlay
+            '';
+
+            serviceConfig = {
+              User = user;
+              Group = group;
+              WorkingDirectory = "${dataDir}/${wikiIdent}";
+              ExecStart = ''${python.pkgs.gunicorn}/bin/gunicorn moin_wsgi \
+                --name gunicorn-${wikiIdent} \
+                --workers ${toString cfg.gunicorn.workers} \
+                --worker-class gevent \
+                --bind unix:/run/moin/${wikiIdent}/gunicorn.sock
+              '';
+
+              Restart = "on-failure";
+              RestartSec = "2s";
+              StartLimitIntervalSec = "30s";
+
+              StateDirectory = "moin/${wikiIdent}";
+              StateDirectoryMode = "0750";
+              RuntimeDirectory = "moin/${wikiIdent}";
+              RuntimeDirectoryMode = "0750";
+
+              NoNewPrivileges = true;
+              ProtectSystem = "strict";
+              ProtectHome = true;
+              PrivateTmp = true;
+              PrivateDevices = true;
+              PrivateNetwork = true;
+              ProtectKernelTunables = true;
+              ProtectKernelModules = true;
+              ProtectControlGroups = true;
+              RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+              RestrictNamespaces = true;
+              LockPersonality = true;
+              MemoryDenyWriteExecute = true;
+              RestrictRealtime = true;
+            };
+          }
+      ));
+
+    services.nginx = mkIf usingNginx {
+      enable = true;
+      virtualHosts = flip mapAttrs' cfg.wikis (name: w: nameValuePair w.webHost {
+        forceSSL = mkDefault true;
+        enableACME = mkDefault true;
+        locations."${w.webLocation}" = {
+          extraConfig = ''
+            proxy_set_header Host $host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+            proxy_set_header X-Forwarded-Host $host;
+            proxy_set_header X-Forwarded-Server $host;
+
+            proxy_pass http://unix:/run/moin/${name}/gunicorn.sock;
+          '';
+        };
+      });
+    };
+
+    systemd.tmpfiles.rules = [
+      "d  /run/moin            0750 ${user} ${group} - -"
+      "d  ${dataDir}           0550 ${user} ${group} - -"
+    ]
+    ++ (concatLists (flip mapAttrsToList cfg.wikis (wikiIdent: wiki: [
+      "d  ${dataDir}/${wikiIdent}                      0750 ${user} ${group} - -"
+      "d  ${dataDir}/${wikiIdent}/config               0550 ${user} ${group} - -"
+      "L+ ${dataDir}/${wikiIdent}/config/wikiconfig.py -    -       -        - ${wikiConfigFile wikiIdent wiki}"
+      # needed in order to pass module name to gunicorn
+      "L+ ${dataDir}/${wikiIdent}/config/moin_wsgi.py  -    -       -        - ${pkg}/share/moin/server/moin.wsgi"
+      # seed data files
+      "C  ${dataDir}/${wikiIdent}/data                 0770 ${user} ${group} - ${pkg}/share/moin/data"
+      # fix nix store permissions
+      "Z  ${dataDir}/${wikiIdent}/data                 0770 ${user} ${group} - -"
+    ])));
+  };
+
+  meta.maintainers = with lib.maintainers; [ b42 ];
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index e94c9712cbf..50a60049a18 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -166,6 +166,7 @@ in
   minio = handleTest ./minio.nix {};
   minidlna = handleTest ./minidlna.nix {};
   misc = handleTest ./misc.nix {};
+  moinmoin = handleTest ./moinmoin.nix {};
   mongodb = handleTest ./mongodb.nix {};
   moodle = handleTest ./moodle.nix {};
   morty = handleTest ./morty.nix {};
diff --git a/nixos/tests/moinmoin.nix b/nixos/tests/moinmoin.nix
new file mode 100644
index 00000000000..2662b79aa09
--- /dev/null
+++ b/nixos/tests/moinmoin.nix
@@ -0,0 +1,24 @@
+import ./make-test.nix ({ pkgs, lib, ... }: {
+  name = "moinmoin";
+  meta.maintainers = [ ]; # waiting for https://github.com/NixOS/nixpkgs/pull/65397
+
+  machine =
+    { ... }:
+    { services.moinmoin.enable = true;
+      services.moinmoin.wikis.ExampleWiki.superUsers = [ "admin" ];
+      services.moinmoin.wikis.ExampleWiki.webHost = "localhost";
+
+      services.nginx.virtualHosts.localhost.enableACME = false;
+      services.nginx.virtualHosts.localhost.forceSSL = false;
+    };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit('moin-ExampleWiki.service');
+    $machine->waitForUnit('nginx.service');
+    $machine->waitForFile('/run/moin/ExampleWiki/gunicorn.sock');
+    $machine->succeed('curl -L http://localhost/') =~ /If you have just installed/ or die;
+    $machine->succeed('moin-ExampleWiki account create --name=admin --email=admin@example.com --password=foo 2>&1') =~ /status success/ or die;
+  '';
+})