about summary refs log tree commit diff
path: root/nixos/modules/services
diff options
context:
space:
mode:
authorMoritz 'e1mo' Fromm <git@e1mo.de>2023-01-06 02:36:30 +0100
committerMoritz 'e1mo' Fromm <git@e1mo.de>2023-01-06 22:02:37 +0100
commit236d90fde0b376f00e8eea32ea6af37ec3de4766 (patch)
treeb24fb617272ad5e1c83720b18d06a7b1c59393ca /nixos/modules/services
parent108627107dbfb7ed1906de3760a8dc995947a28e (diff)
nixos/dokuwiki: Overhaul for structured settings
Added the RFC42-style added the posibility to use
`services.dokuwiki.sites.<name>.settings' instead of passing a plain
string to `<name>.extraConfig`. ´<name>.pluginsConfig` now also accepts
structured configuration.
Diffstat (limited to 'nixos/modules/services')
-rw-r--r--nixos/modules/services/web-apps/dokuwiki.nix269
1 files changed, 199 insertions, 70 deletions
diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix
index 1c40441bb55fc..d90dd8ace4125 100644
--- a/nixos/modules/services/web-apps/dokuwiki.nix
+++ b/nixos/modules/services/web-apps/dokuwiki.nix
@@ -15,30 +15,64 @@ let
     extraConfig = mkPhpIni cfg.phpOptions;
   };
 
-  dokuwikiAclAuthConfig = hostName: cfg: pkgs.writeText "acl.auth-${hostName}.php" ''
+  dokuwikiAclAuthConfig = hostName: cfg: let
+    inherit (cfg) acl;
+    acl_gen = concatMapStringsSep "\n" (l: "${l.page} \t ${l.actor} \t ${toString l.level}");
+  in pkgs.writeText "acl.auth-${hostName}.php" ''
     # acl.auth.php
     # <?php exit()?>
     #
     # Access Control Lists
     #
-    ${toString cfg.acl}
+    ${if isString acl then acl else acl_gen acl}
   '';
 
-  dokuwikiLocalConfig = hostName: cfg: pkgs.writeText "local-${hostName}.php" ''
-    <?php
-    $conf['savedir'] = '${cfg.stateDir}';
-    $conf['superuser'] = '${toString cfg.superUser}';
-    $conf['useacl'] = '${toString cfg.aclUse}';
-    $conf['disableactions'] = '${cfg.disableActions}';
+  mergeConfig = cfg: {
+    useacl = false; # Dokuwiki default
+    savedir = cfg.stateDir;
+  } // cfg.settings;
+
+  writePhpFile = name: text: pkgs.writeTextFile {
+    inherit name;
+    text = "<?php\n${text}";
+    checkPhase = "${pkgs.php81}/bin/php --syntax-check $target";
+  };
+
+  mkPhpValue = v: let
+    isHasAttr = s: isAttrs v && hasAttr s v;
+  in
+    if isString v then escapeShellArg v
+    # NOTE: If any value contains a , (comma) this will not get escaped
+    else if isList v && any lib.strings.isCoercibleToString v then escapeShellArg (concatMapStringsSep "," toString v)
+    else if isInt v then toString v
+    else if isBool v then toString (if v then 1 else 0)
+    else if isHasAttr "_file" then "trim(file_get_contents(${lib.escapeShellArg v._file}))"
+    else if isHasAttr "_raw" then v._raw
+    else abort "The dokuwiki localConf value ${lib.generators.toPretty {} v} can not be encoded."
+  ;
+
+  mkPhpAttrVals = v: flatten (mapAttrsToList mkPhpKeyVal v);
+  mkPhpKeyVal = k: v: let
+    values = if (isAttrs v && (hasAttr "_file" v || hasAttr "_raw" v )) || !isAttrs v then
+      [" = ${mkPhpValue v};"]
+    else
+      mkPhpAttrVals v;
+  in map (e: "[${escapeShellArg k}]${e}") (flatten values);
+
+  dokuwikiLocalConfig = hostName: cfg: let
+    conf_gen = c: map (v: "$conf${v}") (mkPhpAttrVals c);
+  in writePhpFile "local-${hostName}.php" ''
+    ${concatStringsSep "\n" (conf_gen cfg.mergedConfig)}
     ${toString cfg.extraConfig}
   '';
 
-  dokuwikiPluginsLocalConfig = hostName: cfg: pkgs.writeText "plugins.local-${hostName}.php" ''
-    <?php
-    ${cfg.pluginsConfig}
+  dokuwikiPluginsLocalConfig = hostName: cfg: let
+    pc = cfg.pluginsConfig;
+    pc_gen = pc: concatStringsSep "\n" (mapAttrsToList (n: v: "$plugins['${n}'] = ${boolToString v};") pc);
+  in writePhpFile "plugins.local-${hostName}.php" ''
+    ${if isString pc then pc else pc_gen pc}
   '';
 
-
   pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
     pname = "dokuwiki-${hostName}";
     version = src.version;
@@ -49,22 +83,82 @@ let
       cp -r * $out/
 
       # symlink the dokuwiki config
-      ln -s ${dokuwikiLocalConfig hostName cfg} $out/share/dokuwiki/local.php
+      ln -sf ${dokuwikiLocalConfig hostName cfg} $out/share/dokuwiki/conf/local.php
 
       # symlink plugins config
-      ln -s ${dokuwikiPluginsLocalConfig hostName cfg} $out/share/dokuwiki/plugins.local.php
+      ln -sf ${dokuwikiPluginsLocalConfig hostName cfg} $out/share/dokuwiki/conf/plugins.local.php
 
-      # symlink acl
-      ln -s ${dokuwikiAclAuthConfig hostName cfg} $out/share/dokuwiki/acl.auth.php
+      # symlink acl (if needed)
+      ${optionalString (cfg.mergedConfig.useacl && cfg.acl != null) "ln -sf ${dokuwikiAclAuthConfig hostName cfg} $out/share/dokuwiki/acl.auth.php"}
 
       # symlink additional plugin(s) and templates(s)
-      ${concatMapStringsSep "\n" (template: "ln -s ${template} $out/share/dokuwiki/lib/tpl/${template.name}") cfg.templates}
-      ${concatMapStringsSep "\n" (plugin: "ln -s ${plugin} $out/share/dokuwiki/lib/plugins/${plugin.name}") cfg.plugins}
+      ${concatMapStringsSep "\n" (template: "ln -sf ${template} $out/share/dokuwiki/lib/tpl/${template.name}") cfg.templates}
+      ${concatMapStringsSep "\n" (plugin: "ln -sf ${plugin} $out/share/dokuwiki/lib/plugins/${plugin.name}") cfg.plugins}
     '';
   };
 
+  aclOpts = { ... }: {
+    options = {
+
+      page = mkOption {
+        type = types.str;
+        description = "Page or namespace to restrict";
+        example = "start";
+      };
+
+      actor = mkOption {
+        type = types.str;
+        description = "User or group to restrict";
+        example = "@external";
+      };
+
+      level = let
+        available = {
+          "none" = 0;
+          "read" = 1;
+          "edit" = 2;
+          "create" = 4;
+          "upload" = 8;
+          "delete" = 16;
+        };
+      in mkOption {
+        type = types.enum ((attrValues available) ++ (attrNames available));
+        apply = x: if isInt x then x else available.${x};
+        description = ''
+          Permission level to restrict the actor(s) to.
+          See <https://www.dokuwiki.org/acl#background_info> for explanation
+        '';
+        example = "read";
+      };
+
+    };
+  };
+
   siteOpts = { config, lib, name, ... }:
     {
+      imports = [
+        # NOTE: These will sadly not print the absolute argument path but only the name. Related to #96006
+        (mkRenamedOptionModule [ "aclUse" ] [ "settings" "useacl" ] )
+        (mkRenamedOptionModule [ "superUser" ] [ "settings" "superuser" ] )
+        (mkRenamedOptionModule [ "disableActions" ] [ "settings" "disableactions" ] )
+        ({ config, options, name, ...}: {
+          config.warnings =
+            (optional (isString config.pluginsConfig) ''
+              Passing plain strings to services.dokuwiki.sites.${name}.pluginsConfig has been deprecated and will not be continue to be supported in the future.
+              Please pass structured settings instead.
+            '')
+            ++ (optional (isString config.acl) ''
+              Passing a plain string to services.dokuwiki.sites.${name}.acl has been deprecated and will not continue to be supported in the future.
+              Please pass structured settings instead.
+            '')
+            ++ (optional (config.extraConfig != null) ''
+              services.dokuwiki.sites.${name}.extraConfig is deprecated and will be removed in the future.
+              Please pass structured settings to services.dokuwiki.sites.${name}.settings instead.
+            '')
+          ;
+        })
+      ];
+
       options = {
         enable = mkEnableOption (lib.mdDoc "DokuWiki web application.");
 
@@ -82,9 +176,22 @@ let
         };
 
         acl = mkOption {
-          type = types.nullOr types.lines;
+          type = with types; nullOr (oneOf [ lines (listOf (submodule aclOpts)) ]);
           default = null;
-          example = "*               @ALL               8";
+          example = literalExpression ''
+            [
+              {
+                page = "start";
+                actor = "@external";
+                level = "read";
+              }
+              {
+                page = "*";
+                actor = "@users";
+                level = "upload";
+              }
+            ]
+          '';
           description = lib.mdDoc ''
             Access Control Lists: see <https://www.dokuwiki.org/acl>
             Mutually exclusive with services.dokuwiki.aclFile
@@ -97,7 +204,7 @@ let
 
         aclFile = mkOption {
           type = with types; nullOr str;
-          default = if (config.aclUse && config.acl == null) then "/var/lib/dokuwiki/${name}/acl.auth.php" else null;
+          default = if (config.mergedConfig.useacl && config.acl == null) then "/var/lib/dokuwiki/${name}/acl.auth.php" else null;
           description = lib.mdDoc ''
             Location of the dokuwiki acl rules. Mutually exclusive with services.dokuwiki.acl
             Mutually exclusive with services.dokuwiki.acl which is preferred.
@@ -107,42 +214,22 @@ let
           example = "/var/lib/dokuwiki/${name}/acl.auth.php";
         };
 
-        aclUse = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            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;
-          '';
+          type = with types; oneOf [lines (attrsOf bool)];
+          default = {
+            authad = false;
+            authldap = false;
+            authmysql = false;
+            authpgsql = false;
+          };
           description = lib.mdDoc ''
             List of the dokuwiki (un)loaded plugins.
           '';
         };
 
-        superUser = mkOption {
-          type = types.nullOr types.str;
-          default = "@admin";
-          description = lib.mdDoc ''
-            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 <https://www.dokuwiki.org/config:superuser> for further instructions.
-          '';
-        };
-
         usersFile = mkOption {
           type = with types; nullOr str;
-          default = if config.aclUse then "/var/lib/dokuwiki/${name}/users.auth.php" else null;
+          default = if config.mergedConfig.useacl then "/var/lib/dokuwiki/${name}/users.auth.php" else null;
           description = lib.mdDoc ''
             Location of the dokuwiki users file. List of users. Format:
 
@@ -157,17 +244,6 @@ let
           example = "/var/lib/dokuwiki/${name}/users.auth.php";
         };
 
-        disableActions = mkOption {
-          type = types.nullOr types.str;
-          default = "";
-          example = "search,register";
-          description = lib.mdDoc ''
-            Disable individual action modes. Refer to
-            <https://www.dokuwiki.org/config:action_modes>
-            for details on supported values.
-          '';
-        };
-
         plugins = mkOption {
           type = types.listOf types.path;
           default = [];
@@ -266,7 +342,50 @@ let
           '';
         };
 
+        settings = mkOption {
+          type = types.attrsOf types.anything;
+          default = {
+            useacl = true;
+            superuser = "admin";
+          };
+          description = lib.mdDoc ''
+            Structural DokuWiki configuration.
+            Refer to <https://www.dokuwiki.org/config>
+            for details and supported values.
+            Settings can either be directly set from nix,
+            loaded from a file using `._file` or obtained from any
+            PHP function calls using `._raw`.
+          '';
+          example = literalExpression ''
+            {
+              title = "My Wiki";
+              userewrite = 1;
+              disableactions = [ "register" ]; # Will be concatenated with commas
+              plugin.smtp = {
+                smtp_pass._file = "/var/run/secrets/dokuwiki/smtp_pass";
+                smtp_user._raw = "getenv('DOKUWIKI_SMTP_USER')";
+              };
+            }
+          '';
+        };
+
+        mergedConfig = mkOption {
+          readOnly = true;
+          default = mergeConfig config;
+          defaultText = literalExpression ''
+            {
+              useacl = true;
+            }
+          '';
+          description = lib.mdDoc ''
+            Read only representation of the final configuration.
+          '';
+        };
+
         extraConfig = mkOption {
+          # This Option is deprecated and only kept until sometime before 23.05 for compatibility reasons
+          # FIXME (@e1mo): Actually remember removing this before 23.05.
+          visible = false;
           type = types.nullOr types.lines;
           default = null;
           example = ''
@@ -277,15 +396,26 @@ let
             DokuWiki configuration. Refer to
             <https://www.dokuwiki.org/config>
             for details on supported values.
+
+            **Note**: Please pass Structured settings via
+            `services.dokuwiki.sites.${name}.settings` instead.
           '';
         };
 
+      # Required for the mkRenamedOptionModule
+      # TODO: Remove me once https://github.com/NixOS/nixpkgs/issues/96006 is fixed
+      # or the aclUse, ... options are removed.
+      warnings = mkOption {
+        type = types.listOf types.unspecified;
+        default = [ ];
+        visible = false;
+        internal = true;
       };
 
     };
+  };
 in
 {
-  # interface
   options = {
     services.dokuwiki = {
 
@@ -315,14 +445,16 @@ in
   # implementation
   config = mkIf (eachSite != {}) (mkMerge [{
 
+    warnings = flatten (mapAttrsToList (_: cfg: cfg.warnings) eachSite);
+
     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.mergedConfig.useacl -> (cfg.acl != null || cfg.aclFile != null);
+      message = "Either services.dokuwiki.sites.${hostName}.acl or services.dokuwiki.sites.${hostName}.aclFile is mandatory if settings.useacl is true";
     }
     {
-      assertion = cfg.usersFile != null -> cfg.aclUse != false;
-      message = "services.dokuwiki.sites.${hostName}.aclUse must must be true if usersFile is not null";
+      assertion = cfg.usersFile != null -> cfg.mergedConfig.useacl != false;
+      message = "services.dokuwiki.sites.${hostName}.settings.useacl must must be true if usersFile is not null";
     }
     ]) eachSite);
 
@@ -332,12 +464,9 @@ in
         group = webserver.group;
 
         phpPackage = mkPhpPackage cfg;
-        phpEnv = {
-          DOKUWIKI_LOCAL_CONFIG = "${dokuwikiLocalConfig hostName cfg}";
-          DOKUWIKI_PLUGINS_LOCAL_CONFIG = "${dokuwikiPluginsLocalConfig hostName cfg}";
-        } // optionalAttrs (cfg.usersFile != null) {
+        phpEnv = optionalAttrs (cfg.usersFile != null) {
           DOKUWIKI_USERS_AUTH_CONFIG = "${cfg.usersFile}";
-        } //optionalAttrs (cfg.aclUse) {
+        } // optionalAttrs (cfg.mergedConfig.useacl) {
           DOKUWIKI_ACL_AUTH_CONFIG = if (cfg.acl != null) then "${dokuwikiAclAuthConfig hostName cfg}" else "${toString cfg.aclFile}";
         };