diff --git a/modules/web-apps/dokuwiki.nix b/modules/web-apps/dokuwiki.nix new file mode 100644 index 0000000..3e92ac3 --- /dev/null +++ b/modules/web-apps/dokuwiki.nix @@ -0,0 +1,386 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.dokuwiki; + eachSite = cfg.sites; + user = "dokuwiki"; + nginx = config.services.nginx; + stateDir = hostName: "/var/lib/dokuwiki/${hostName}/data"; + + dokuwikiAclAuthConfig = hostName: cfg: pkgs.writeText "acl.auth-${hostName}.php" '' + # acl.auth.php + # + # + # Access Control Lists + # + ${toString cfg.acl} + ''; + + dokuwikiLocalConfig = hostName: cfg: pkgs.writeText "local-${hostName}.php" '' + + Mutually exclusive with services.dokuwiki.aclFile + Set this to a value other than null to take precedence over aclFile option. + + Warning: Consider using aclFile instead if you do not + want to store the ACL in the world-readable Nix store. + ''; + }; + + aclFile = mkOption { + type = with types; nullOr str; + default = if (config.aclUse && config.acl == null) then "/var/lib/dokuwiki/${name}/acl.auth.php" else null; + description = '' + Location of the dokuwiki acl rules. Mutually exclusive with services.dokuwiki.acl + Mutually exclusive with services.dokuwiki.acl which is preferred. + Consult documentation for further instructions. + Example: + ''; + example = "/var/lib/dokuwiki/${name}/acl.auth.php"; + }; + + aclUse = mkOption { + type = types.bool; + default = true; + description = '' + Necessary for users to log in into the system. + Also limits anonymous users. When disabled, + everyone is able to create and edit content. + ''; + }; + + pluginsConfig = mkOption { + type = types.lines; + default = '' + $plugins['authad'] = 0; + $plugins['authldap'] = 0; + $plugins['authmysql'] = 0; + $plugins['authpgsql'] = 0; + ''; + description = '' + List of the dokuwiki (un)loaded plugins. + ''; + }; + + superUser = mkOption { + type = types.nullOr types.str; + default = "@admin"; + description = '' + You can set either a username, a list of usernames (“admin1,admin2”), + or the name of a group by prepending an @ char to the groupname + Consult documentation for further instructions. + ''; + }; + + usersFile = mkOption { + type = with types; nullOr str; + default = if config.aclUse then "/var/lib/dokuwiki/${name}/users.auth.php" else null; + description = '' + Location of the dokuwiki users file. List of users. Format: + login:passwordhash:Real Name:email:groups,comma,separated + Create passwordHash easily by using:$ mkpasswd -5 password `pwgen 8 1` + Example: + ''; + example = "/var/lib/dokuwiki/${name}/users.auth.php"; + }; + + disableActions = mkOption { + type = types.nullOr types.str; + default = ""; + example = "search,register"; + description = '' + Disable individual action modes. Refer to + + for details on supported values. + ''; + }; + + plugins = mkOption { + type = types.listOf types.path; + default = []; + description = '' + List of path(s) to respective plugin(s) which are copied from the 'plugin' directory. + These plugins need to be packaged before use, see example. + ''; + example = literalExpression '' + let + # Let's package the icalevents plugin + plugin-icalevents = pkgs.stdenv.mkDerivation { + name = "icalevents"; + # Download the plugin from the dokuwiki site + src = pkgs.fetchurl { + url = "https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/2017-06-16/dokuwiki-plugin-icalevents-2017-06-16.zip"; + sha256 = "e40ed7dd6bbe7fe3363bbbecb4de481d5e42385b5a0f62f6a6ce6bf3a1f9dfa8"; + }; + sourceRoot = "."; + # We need unzip to build this package + buildInputs = [ pkgs.unzip ]; + # Installing simply means copying all files to the output directory + installPhase = "mkdir -p $out; cp -R * $out/"; + }; + # And then pass this theme to the plugin list like this: + in [ plugin-icalevents ] + ''; + }; + + templates = mkOption { + type = types.listOf types.path; + default = []; + description = '' + List of path(s) to respective template(s) which are copied from the 'tpl' directory. + These templates need to be packaged before use, see example. + ''; + example = literalExpression '' + let + # Let's package the bootstrap3 theme + template-bootstrap3 = pkgs.stdenv.mkDerivation { + name = "bootstrap3"; + # Download the theme from the dokuwiki site + src = pkgs.fetchurl { + url = "https://github.com/giterlizzi/dokuwiki-template-bootstrap3/archive/v2019-05-22.zip"; + sha256 = "4de5ff31d54dd61bbccaf092c9e74c1af3a4c53e07aa59f60457a8f00cfb23a6"; + }; + # We need unzip to build this package + buildInputs = [ pkgs.unzip ]; + # Installing simply means copying all files to the output directory + installPhase = "mkdir -p $out; cp -R * $out/"; + }; + # And then pass this theme to the template list like this: + in [ template-bootstrap3 ] + ''; + }; + + poolConfig = mkOption { + type = with types; attrsOf (oneOf [ str int bool ]); + default = { + "pm" = "dynamic"; + "pm.max_children" = 32; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 2; + "pm.max_spare_servers" = 4; + "pm.max_requests" = 500; + }; + description = '' + Options for the DokuWiki PHP pool. See the documentation on php-fpm.conf + for details on configuration directives. + ''; + }; + + extraConfig = mkOption { + type = types.nullOr types.lines; + default = null; + example = '' + $conf['title'] = 'My Wiki'; + $conf['userewrite'] = 1; + ''; + description = '' + DokuWiki configuration. Refer to + + for details on supported values. + ''; + }; + + }; + + }; +in +{ + disabledModules = [ "services/web-apps/dokuwiki.nix" ]; + options = { + services.dokuwiki = { + + sites = mkOption { + type = types.attrsOf (types.submodule siteOpts); + default = {}; + description = "Specification of one or more DokuWiki sites to serve"; + }; + + }; + }; + + # implementation + config = mkIf (eachSite != {}) (mkMerge [{ + + assertions = flatten (mapAttrsToList (hostName: cfg: + [{ + assertion = cfg.aclUse -> (cfg.acl != null || cfg.aclFile != null); + message = "Either services.dokuwiki.sites.${hostName}.acl or services.dokuwiki.sites.${hostName}.aclFile is mandatory if aclUse true"; + } + { + assertion = cfg.usersFile != null -> cfg.aclUse != false; + message = "services.dokuwiki.sites.${hostName}.aclUse must must be true if usersFile is not null"; + } + ]) eachSite); + + services.phpfpm.pools = mapAttrs' (hostName: cfg: ( + nameValuePair "dokuwiki-${hostName}" { + inherit user; + group = nginx.group; + + # Not yet compatible with php 8 https://www.dokuwiki.org/requirements + # https://github.com/splitbrain/dokuwiki/issues/3545 + phpPackage = pkgs.php74; + phpEnv = { + DOKUWIKI_LOCAL_CONFIG = "${dokuwikiLocalConfig hostName cfg}"; + DOKUWIKI_PLUGINS_LOCAL_CONFIG = "${dokuwikiPluginsLocalConfig hostName cfg}"; + DOKUWIKI_ROOT = "${cfg.finalPackage}/share/dokuwiki/"; + } // optionalAttrs (cfg.usersFile != null) { + DOKUWIKI_USERS_AUTH_CONFIG = "${cfg.usersFile}"; + } //optionalAttrs (cfg.aclUse) { + DOKUWIKI_ACL_AUTH_CONFIG = if (cfg.acl != null) then "${dokuwikiAclAuthConfig hostName cfg}" else "${toString cfg.aclFile}"; + }; + + settings = { + "listen.owner" = nginx.user; + "listen.group" = nginx.group; + } // cfg.poolConfig; + } + )) eachSite; + + } + + { + systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [ + "d ${stateDir hostName}/attic 0750 ${user} ${nginx.group} - -" + "d ${stateDir hostName}/cache 0750 ${user} ${nginx.group} - -" + "d ${stateDir hostName}/index 0750 ${user} ${nginx.group} - -" + "d ${stateDir hostName}/locks 0750 ${user} ${nginx.group} - -" + "d ${stateDir hostName}/media 0750 ${user} ${nginx.group} - -" + "d ${stateDir hostName}/media_attic 0750 ${user} ${nginx.group} - -" + "d ${stateDir hostName}/media_meta 0750 ${user} ${nginx.group} - -" + "d ${stateDir hostName}/meta 0750 ${user} ${nginx.group} - -" + "d ${stateDir hostName}/pages 0750 ${user} ${nginx.group} - -" + "d ${stateDir hostName}/tmp 0750 ${user} ${nginx.group} - -" + ] ++ lib.optional (cfg.aclFile != null) "C ${cfg.aclFile} 0640 ${user} ${nginx.group} - ${pkg hostName cfg}/share/dokuwiki/conf/acl.auth.php.dist" + ++ lib.optional (cfg.usersFile != null) "C ${cfg.usersFile} 0640 ${user} ${nginx.group} - ${pkg hostName cfg}/share/dokuwiki/conf/users.auth.php.dist" + ) eachSite); + + users.users.${user} = { + group = nginx.group; + isSystemUser = true; + }; + } + + { + services.nginx = { + enable = true; + virtualHosts = mapAttrs (hostName: cfg: { + serverName = mkDefault hostName; + root = "${pkg hostName cfg}/share/dokuwiki"; + + locations = { + "~ /(conf/|bin/|inc/|install.php)" = { + extraConfig = "deny all;"; + }; + + "~ ^/data/" = { + root = "${stateDir hostName}"; + extraConfig = "internal;"; + }; + + "~ ^/lib.*\.(js|css|gif|png|ico|jpg|jpeg)$" = { + extraConfig = "expires 365d;"; + }; + + "/" = { + priority = 1; + index = "doku.php"; + extraConfig = ''try_files $uri $uri/ @dokuwiki;''; + }; + + "@dokuwiki" = { + extraConfig = '' + # rewrites "doku.php/" out of the URLs if you set the userwrite setting to .htaccess in dokuwiki config page + rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last; + rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last; + rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last; + rewrite ^/(.*) /doku.php?id=$1&$args last; + ''; + }; + + "~ \\.php$" = { + extraConfig = '' + try_files $uri $uri/ /doku.php; + include ${config.services.nginx.package}/conf/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param REDIRECT_STATUS 200; + fastcgi_pass unix:${config.services.phpfpm.pools."dokuwiki-${hostName}".socket}; + ''; + }; + + }; + }) eachSite; + }; + } + + ]); +}