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