1{ config, pkgs, lib, ... }:
2
3with lib;
4
5let
6 cfg = config.services.dokuwiki;
7 eachSite = cfg.sites;
8 user = "dokuwiki";
9 webserver = config.services.${cfg.webserver};
10
11 dokuwikiAclAuthConfig = hostName: cfg: pkgs.writeText "acl.auth-${hostName}.php" ''
12 # acl.auth.php
13 # <?php exit()?>
14 #
15 # Access Control Lists
16 #
17 ${toString cfg.acl}
18 '';
19
20 dokuwikiLocalConfig = hostName: cfg: pkgs.writeText "local-${hostName}.php" ''
21 <?php
22 $conf['savedir'] = '${cfg.stateDir}';
23 $conf['superuser'] = '${toString cfg.superUser}';
24 $conf['useacl'] = '${toString cfg.aclUse}';
25 $conf['disableactions'] = '${cfg.disableActions}';
26 ${toString cfg.extraConfig}
27 '';
28
29 dokuwikiPluginsLocalConfig = hostName: cfg: pkgs.writeText "plugins.local-${hostName}.php" ''
30 <?php
31 ${cfg.pluginsConfig}
32 '';
33
34
35 pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
36 pname = "dokuwiki-${hostName}";
37 version = src.version;
38 src = cfg.package;
39
40 installPhase = ''
41 mkdir -p $out
42 cp -r * $out/
43
44 # symlink the dokuwiki config
45 ln -s ${dokuwikiLocalConfig hostName cfg} $out/share/dokuwiki/local.php
46
47 # symlink plugins config
48 ln -s ${dokuwikiPluginsLocalConfig hostName cfg} $out/share/dokuwiki/plugins.local.php
49
50 # symlink acl
51 ln -s ${dokuwikiAclAuthConfig hostName cfg} $out/share/dokuwiki/acl.auth.php
52
53 # symlink additional plugin(s) and templates(s)
54 ${concatMapStringsSep "\n" (template: "ln -s ${template} $out/share/dokuwiki/lib/tpl/${template.name}") cfg.templates}
55 ${concatMapStringsSep "\n" (plugin: "ln -s ${plugin} $out/share/dokuwiki/lib/plugins/${plugin.name}") cfg.plugins}
56 '';
57 };
58
59 siteOpts = { config, lib, name, ... }:
60 {
61 options = {
62 enable = mkEnableOption (lib.mdDoc "DokuWiki web application.");
63
64 package = mkOption {
65 type = types.package;
66 default = pkgs.dokuwiki;
67 defaultText = literalExpression "pkgs.dokuwiki";
68 description = lib.mdDoc "Which DokuWiki package to use.";
69 };
70
71 stateDir = mkOption {
72 type = types.path;
73 default = "/var/lib/dokuwiki/${name}/data";
74 description = lib.mdDoc "Location of the DokuWiki state directory.";
75 };
76
77 acl = mkOption {
78 type = types.nullOr types.lines;
79 default = null;
80 example = "* @ALL 8";
81 description = lib.mdDoc ''
82 Access Control Lists: see <https://www.dokuwiki.org/acl>
83 Mutually exclusive with services.dokuwiki.aclFile
84 Set this to a value other than null to take precedence over aclFile option.
85
86 Warning: Consider using aclFile instead if you do not
87 want to store the ACL in the world-readable Nix store.
88 '';
89 };
90
91 aclFile = mkOption {
92 type = with types; nullOr str;
93 default = if (config.aclUse && config.acl == null) then "/var/lib/dokuwiki/${name}/acl.auth.php" else null;
94 description = lib.mdDoc ''
95 Location of the dokuwiki acl rules. Mutually exclusive with services.dokuwiki.acl
96 Mutually exclusive with services.dokuwiki.acl which is preferred.
97 Consult documentation <https://www.dokuwiki.org/acl> for further instructions.
98 Example: <https://github.com/splitbrain/dokuwiki/blob/master/conf/acl.auth.php.dist>
99 '';
100 example = "/var/lib/dokuwiki/${name}/acl.auth.php";
101 };
102
103 aclUse = mkOption {
104 type = types.bool;
105 default = true;
106 description = lib.mdDoc ''
107 Necessary for users to log in into the system.
108 Also limits anonymous users. When disabled,
109 everyone is able to create and edit content.
110 '';
111 };
112
113 pluginsConfig = mkOption {
114 type = types.lines;
115 default = ''
116 $plugins['authad'] = 0;
117 $plugins['authldap'] = 0;
118 $plugins['authmysql'] = 0;
119 $plugins['authpgsql'] = 0;
120 '';
121 description = lib.mdDoc ''
122 List of the dokuwiki (un)loaded plugins.
123 '';
124 };
125
126 superUser = mkOption {
127 type = types.nullOr types.str;
128 default = "@admin";
129 description = lib.mdDoc ''
130 You can set either a username, a list of usernames (“admin1,admin2”),
131 or the name of a group by prepending an @ char to the groupname
132 Consult documentation <https://www.dokuwiki.org/config:superuser> for further instructions.
133 '';
134 };
135
136 usersFile = mkOption {
137 type = with types; nullOr str;
138 default = if config.aclUse then "/var/lib/dokuwiki/${name}/users.auth.php" else null;
139 description = lib.mdDoc ''
140 Location of the dokuwiki users file. List of users. Format:
141
142 login:passwordhash:Real Name:email:groups,comma,separated
143
144 Create passwordHash easily by using:
145
146 mkpasswd -5 password `pwgen 8 1`
147
148 Example: <https://github.com/splitbrain/dokuwiki/blob/master/conf/users.auth.php.dist>
149 '';
150 example = "/var/lib/dokuwiki/${name}/users.auth.php";
151 };
152
153 disableActions = mkOption {
154 type = types.nullOr types.str;
155 default = "";
156 example = "search,register";
157 description = lib.mdDoc ''
158 Disable individual action modes. Refer to
159 <https://www.dokuwiki.org/config:action_modes>
160 for details on supported values.
161 '';
162 };
163
164 plugins = mkOption {
165 type = types.listOf types.path;
166 default = [];
167 description = lib.mdDoc ''
168 List of path(s) to respective plugin(s) which are copied from the 'plugin' directory.
169
170 ::: {.note}
171 These plugins need to be packaged before use, see example.
172 :::
173 '';
174 example = literalExpression ''
175 let
176 # Let's package the icalevents plugin
177 plugin-icalevents = pkgs.stdenv.mkDerivation {
178 name = "icalevents";
179 # Download the plugin from the dokuwiki site
180 src = pkgs.fetchurl {
181 url = "https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/2017-06-16/dokuwiki-plugin-icalevents-2017-06-16.zip";
182 sha256 = "e40ed7dd6bbe7fe3363bbbecb4de481d5e42385b5a0f62f6a6ce6bf3a1f9dfa8";
183 };
184 sourceRoot = ".";
185 # We need unzip to build this package
186 buildInputs = [ pkgs.unzip ];
187 # Installing simply means copying all files to the output directory
188 installPhase = "mkdir -p $out; cp -R * $out/";
189 };
190 # And then pass this theme to the plugin list like this:
191 in [ plugin-icalevents ]
192 '';
193 };
194
195 templates = mkOption {
196 type = types.listOf types.path;
197 default = [];
198 description = lib.mdDoc ''
199 List of path(s) to respective template(s) which are copied from the 'tpl' directory.
200
201 ::: {.note}
202 These templates need to be packaged before use, see example.
203 :::
204 '';
205 example = literalExpression ''
206 let
207 # Let's package the bootstrap3 theme
208 template-bootstrap3 = pkgs.stdenv.mkDerivation {
209 name = "bootstrap3";
210 # Download the theme from the dokuwiki site
211 src = pkgs.fetchurl {
212 url = "https://github.com/giterlizzi/dokuwiki-template-bootstrap3/archive/v2019-05-22.zip";
213 sha256 = "4de5ff31d54dd61bbccaf092c9e74c1af3a4c53e07aa59f60457a8f00cfb23a6";
214 };
215 # We need unzip to build this package
216 buildInputs = [ pkgs.unzip ];
217 # Installing simply means copying all files to the output directory
218 installPhase = "mkdir -p $out; cp -R * $out/";
219 };
220 # And then pass this theme to the template list like this:
221 in [ template-bootstrap3 ]
222 '';
223 };
224
225 poolConfig = mkOption {
226 type = with types; attrsOf (oneOf [ str int bool ]);
227 default = {
228 "pm" = "dynamic";
229 "pm.max_children" = 32;
230 "pm.start_servers" = 2;
231 "pm.min_spare_servers" = 2;
232 "pm.max_spare_servers" = 4;
233 "pm.max_requests" = 500;
234 };
235 description = lib.mdDoc ''
236 Options for the DokuWiki PHP pool. See the documentation on `php-fpm.conf`
237 for details on configuration directives.
238 '';
239 };
240
241 extraConfig = mkOption {
242 type = types.nullOr types.lines;
243 default = null;
244 example = ''
245 $conf['title'] = 'My Wiki';
246 $conf['userewrite'] = 1;
247 '';
248 description = lib.mdDoc ''
249 DokuWiki configuration. Refer to
250 <https://www.dokuwiki.org/config>
251 for details on supported values.
252 '';
253 };
254
255 };
256
257 };
258in
259{
260 # interface
261 options = {
262 services.dokuwiki = {
263
264 sites = mkOption {
265 type = types.attrsOf (types.submodule siteOpts);
266 default = {};
267 description = lib.mdDoc "Specification of one or more DokuWiki sites to serve";
268 };
269
270 webserver = mkOption {
271 type = types.enum [ "nginx" "caddy" ];
272 default = "nginx";
273 description = lib.mdDoc ''
274 Whether to use nginx or caddy for virtual host management.
275
276 Further nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
277 See [](#opt-services.nginx.virtualHosts) for further information.
278
279 Further apache2 configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
280 See [](#opt-services.httpd.virtualHosts) for further information.
281 '';
282 };
283
284 };
285 };
286
287 # implementation
288 config = mkIf (eachSite != {}) (mkMerge [{
289
290 assertions = flatten (mapAttrsToList (hostName: cfg:
291 [{
292 assertion = cfg.aclUse -> (cfg.acl != null || cfg.aclFile != null);
293 message = "Either services.dokuwiki.sites.${hostName}.acl or services.dokuwiki.sites.${hostName}.aclFile is mandatory if aclUse true";
294 }
295 {
296 assertion = cfg.usersFile != null -> cfg.aclUse != false;
297 message = "services.dokuwiki.sites.${hostName}.aclUse must must be true if usersFile is not null";
298 }
299 ]) eachSite);
300
301 services.phpfpm.pools = mapAttrs' (hostName: cfg: (
302 nameValuePair "dokuwiki-${hostName}" {
303 inherit user;
304 group = webserver.group;
305
306 phpPackage = pkgs.php81;
307 phpEnv = {
308 DOKUWIKI_LOCAL_CONFIG = "${dokuwikiLocalConfig hostName cfg}";
309 DOKUWIKI_PLUGINS_LOCAL_CONFIG = "${dokuwikiPluginsLocalConfig hostName cfg}";
310 } // optionalAttrs (cfg.usersFile != null) {
311 DOKUWIKI_USERS_AUTH_CONFIG = "${cfg.usersFile}";
312 } //optionalAttrs (cfg.aclUse) {
313 DOKUWIKI_ACL_AUTH_CONFIG = if (cfg.acl != null) then "${dokuwikiAclAuthConfig hostName cfg}" else "${toString cfg.aclFile}";
314 };
315
316 settings = {
317 "listen.owner" = webserver.user;
318 "listen.group" = webserver.group;
319 } // cfg.poolConfig;
320 }
321 )) eachSite;
322
323 }
324
325 {
326 systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
327 "d ${cfg.stateDir}/attic 0750 ${user} ${webserver.group} - -"
328 "d ${cfg.stateDir}/cache 0750 ${user} ${webserver.group} - -"
329 "d ${cfg.stateDir}/index 0750 ${user} ${webserver.group} - -"
330 "d ${cfg.stateDir}/locks 0750 ${user} ${webserver.group} - -"
331 "d ${cfg.stateDir}/log 0750 ${user} ${webserver.group} - -"
332 "d ${cfg.stateDir}/media 0750 ${user} ${webserver.group} - -"
333 "d ${cfg.stateDir}/media_attic 0750 ${user} ${webserver.group} - -"
334 "d ${cfg.stateDir}/media_meta 0750 ${user} ${webserver.group} - -"
335 "d ${cfg.stateDir}/meta 0750 ${user} ${webserver.group} - -"
336 "d ${cfg.stateDir}/pages 0750 ${user} ${webserver.group} - -"
337 "d ${cfg.stateDir}/tmp 0750 ${user} ${webserver.group} - -"
338 ] ++ lib.optional (cfg.aclFile != null) "C ${cfg.aclFile} 0640 ${user} ${webserver.group} - ${pkg hostName cfg}/share/dokuwiki/conf/acl.auth.php.dist"
339 ++ lib.optional (cfg.usersFile != null) "C ${cfg.usersFile} 0640 ${user} ${webserver.group} - ${pkg hostName cfg}/share/dokuwiki/conf/users.auth.php.dist"
340 ) eachSite);
341
342 users.users.${user} = {
343 group = webserver.group;
344 isSystemUser = true;
345 };
346 }
347
348 (mkIf (cfg.webserver == "nginx") {
349 services.nginx = {
350 enable = true;
351 virtualHosts = mapAttrs (hostName: cfg: {
352 serverName = mkDefault hostName;
353 root = "${pkg hostName cfg}/share/dokuwiki";
354
355 locations = {
356 "~ /(conf/|bin/|inc/|install.php)" = {
357 extraConfig = "deny all;";
358 };
359
360 "~ ^/data/" = {
361 root = "${cfg.stateDir}";
362 extraConfig = "internal;";
363 };
364
365 "~ ^/lib.*\.(js|css|gif|png|ico|jpg|jpeg)$" = {
366 extraConfig = "expires 365d;";
367 };
368
369 "/" = {
370 priority = 1;
371 index = "doku.php";
372 extraConfig = ''try_files $uri $uri/ @dokuwiki;'';
373 };
374
375 "@dokuwiki" = {
376 extraConfig = ''
377 # rewrites "doku.php/" out of the URLs if you set the userwrite setting to .htaccess in dokuwiki config page
378 rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last;
379 rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
380 rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
381 rewrite ^/(.*) /doku.php?id=$1&$args last;
382 '';
383 };
384
385 "~ \\.php$" = {
386 extraConfig = ''
387 try_files $uri $uri/ /doku.php;
388 include ${config.services.nginx.package}/conf/fastcgi_params;
389 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
390 fastcgi_param REDIRECT_STATUS 200;
391 fastcgi_pass unix:${config.services.phpfpm.pools."dokuwiki-${hostName}".socket};
392 '';
393 };
394
395 };
396 }) eachSite;
397 };
398 })
399
400 (mkIf (cfg.webserver == "caddy") {
401 services.caddy = {
402 enable = true;
403 virtualHosts = mapAttrs' (hostName: cfg: (
404 nameValuePair "http://${hostName}" {
405 extraConfig = ''
406 root * ${pkg hostName cfg}/share/dokuwiki
407 file_server
408
409 encode zstd gzip
410 php_fastcgi unix/${config.services.phpfpm.pools."dokuwiki-${hostName}".socket}
411
412 @restrict_files {
413 path /data/* /conf/* /bin/* /inc/* /vendor/* /install.php
414 }
415
416 respond @restrict_files 404
417
418 @allow_media {
419 path_regexp path ^/_media/(.*)$
420 }
421 rewrite @allow_media /lib/exe/fetch.php?media=/{http.regexp.path.1}
422
423 @allow_detail {
424 path /_detail*
425 }
426 rewrite @allow_detail /lib/exe/detail.php?media={path}
427
428 @allow_export {
429 path /_export*
430 path_regexp export /([^/]+)/(.*)
431 }
432 rewrite @allow_export /doku.php?do=export_{http.regexp.export.1}&id={http.regexp.export.2}
433
434 try_files {path} {path}/ /doku.php?id={path}&{query}
435 '';
436 }
437 )) eachSite;
438 };
439 })
440
441 ]);
442
443 meta.maintainers = with maintainers; [
444 _1000101
445 onny
446 dandellion
447 ];
448}