1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.hedgedoc;
7
8 # 21.03 will not be an official release - it was instead 21.05. This
9 # versionAtLeast statement remains set to 21.03 for backwards compatibility.
10 # See https://github.com/NixOS/nixpkgs/pull/108899 and
11 # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md.
12 name = if versionAtLeast config.system.stateVersion "21.03"
13 then "hedgedoc"
14 else "codimd";
15
16 settingsFormat = pkgs.formats.json {};
17
18 prettyJSON = conf:
19 pkgs.runCommandLocal "hedgedoc-config.json" {
20 nativeBuildInputs = [ pkgs.jq ];
21 } ''
22 jq '{production:del(.[]|nulls)|del(.[][]?|nulls)}' \
23 < ${settingsFormat.generate "hedgedoc-ugly.json" cfg.settings} \
24 > $out
25 '';
26in
27{
28 imports = [
29 (mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ])
30 (mkRenamedOptionModule
31 [ "services" "hedgedoc" "configuration" ] [ "services" "hedgedoc" "settings" ])
32 ];
33
34 options.services.hedgedoc = {
35 enable = mkEnableOption (lib.mdDoc "the HedgeDoc Markdown Editor");
36
37 groups = mkOption {
38 type = types.listOf types.str;
39 default = [];
40 description = lib.mdDoc ''
41 Groups to which the service user should be added.
42 '';
43 };
44
45 workDir = mkOption {
46 type = types.path;
47 default = "/var/lib/${name}";
48 description = lib.mdDoc ''
49 Working directory for the HedgeDoc service.
50 '';
51 };
52
53 settings = let options = {
54 debug = mkEnableOption (lib.mdDoc "debug mode");
55 domain = mkOption {
56 type = types.nullOr types.str;
57 default = null;
58 example = "hedgedoc.org";
59 description = lib.mdDoc ''
60 Domain name for the HedgeDoc instance.
61 '';
62 };
63 urlPath = mkOption {
64 type = types.nullOr types.str;
65 default = null;
66 example = "/url/path/to/hedgedoc";
67 description = lib.mdDoc ''
68 Path under which HedgeDoc is accessible.
69 '';
70 };
71 host = mkOption {
72 type = types.str;
73 default = "localhost";
74 description = lib.mdDoc ''
75 Address to listen on.
76 '';
77 };
78 port = mkOption {
79 type = types.port;
80 default = 3000;
81 example = 80;
82 description = lib.mdDoc ''
83 Port to listen on.
84 '';
85 };
86 path = mkOption {
87 type = types.nullOr types.str;
88 default = null;
89 example = "/run/hedgedoc.sock";
90 description = lib.mdDoc ''
91 Specify where a UNIX domain socket should be placed.
92 '';
93 };
94 allowOrigin = mkOption {
95 type = types.listOf types.str;
96 default = [];
97 example = [ "localhost" "hedgedoc.org" ];
98 description = lib.mdDoc ''
99 List of domains to whitelist.
100 '';
101 };
102 useSSL = mkOption {
103 type = types.bool;
104 default = false;
105 description = lib.mdDoc ''
106 Enable to use SSL server. This will also enable
107 {option}`protocolUseSSL`.
108 '';
109 };
110 hsts = {
111 enable = mkOption {
112 type = types.bool;
113 default = true;
114 description = lib.mdDoc ''
115 Whether to enable HSTS if HTTPS is also enabled.
116 '';
117 };
118 maxAgeSeconds = mkOption {
119 type = types.int;
120 default = 31536000;
121 description = lib.mdDoc ''
122 Max duration for clients to keep the HSTS status.
123 '';
124 };
125 includeSubdomains = mkOption {
126 type = types.bool;
127 default = true;
128 description = lib.mdDoc ''
129 Whether to include subdomains in HSTS.
130 '';
131 };
132 preload = mkOption {
133 type = types.bool;
134 default = true;
135 description = lib.mdDoc ''
136 Whether to allow preloading of the site's HSTS status.
137 '';
138 };
139 };
140 csp = mkOption {
141 type = types.nullOr types.attrs;
142 default = null;
143 example = literalExpression ''
144 {
145 enable = true;
146 directives = {
147 scriptSrc = "trustworthy.scripts.example.com";
148 };
149 upgradeInsecureRequest = "auto";
150 addDefaults = true;
151 }
152 '';
153 description = lib.mdDoc ''
154 Specify the Content Security Policy which is passed to Helmet.
155 For configuration details see <https://helmetjs.github.io/docs/csp/>.
156 '';
157 };
158 protocolUseSSL = mkOption {
159 type = types.bool;
160 default = false;
161 description = lib.mdDoc ''
162 Enable to use TLS for resource paths.
163 This only applies when {option}`domain` is set.
164 '';
165 };
166 urlAddPort = mkOption {
167 type = types.bool;
168 default = false;
169 description = lib.mdDoc ''
170 Enable to add the port to callback URLs.
171 This only applies when {option}`domain` is set
172 and only for ports other than 80 and 443.
173 '';
174 };
175 useCDN = mkOption {
176 type = types.bool;
177 default = false;
178 description = lib.mdDoc ''
179 Whether to use CDN resources or not.
180 '';
181 };
182 allowAnonymous = mkOption {
183 type = types.bool;
184 default = true;
185 description = lib.mdDoc ''
186 Whether to allow anonymous usage.
187 '';
188 };
189 allowAnonymousEdits = mkOption {
190 type = types.bool;
191 default = false;
192 description = lib.mdDoc ''
193 Whether to allow guests to edit existing notes with the `freely` permission,
194 when {option}`allowAnonymous` is enabled.
195 '';
196 };
197 allowFreeURL = mkOption {
198 type = types.bool;
199 default = false;
200 description = lib.mdDoc ''
201 Whether to allow note creation by accessing a nonexistent note URL.
202 '';
203 };
204 requireFreeURLAuthentication = mkOption {
205 type = types.bool;
206 default = false;
207 description = lib.mdDoc ''
208 Whether to require authentication for FreeURL mode style note creation.
209 '';
210 };
211 defaultPermission = mkOption {
212 type = types.enum [ "freely" "editable" "limited" "locked" "private" ];
213 default = "editable";
214 description = lib.mdDoc ''
215 Default permissions for notes.
216 This only applies for signed-in users.
217 '';
218 };
219 dbURL = mkOption {
220 type = types.nullOr types.str;
221 default = null;
222 example = ''
223 postgres://user:pass@host:5432/dbname
224 '';
225 description = lib.mdDoc ''
226 Specify which database to use.
227 HedgeDoc supports mysql, postgres, sqlite and mssql.
228 See [
229 https://sequelize.readthedocs.io/en/v3/](https://sequelize.readthedocs.io/en/v3/) for more information.
230 Note: This option overrides {option}`db`.
231 '';
232 };
233 db = mkOption {
234 type = types.attrs;
235 default = {};
236 example = literalExpression ''
237 {
238 dialect = "sqlite";
239 storage = "/var/lib/${name}/db.${name}.sqlite";
240 }
241 '';
242 description = lib.mdDoc ''
243 Specify the configuration for sequelize.
244 HedgeDoc supports mysql, postgres, sqlite and mssql.
245 See [
246 https://sequelize.readthedocs.io/en/v3/](https://sequelize.readthedocs.io/en/v3/) for more information.
247 Note: This option overrides {option}`db`.
248 '';
249 };
250 sslKeyPath= mkOption {
251 type = types.nullOr types.str;
252 default = null;
253 example = "/var/lib/hedgedoc/hedgedoc.key";
254 description = lib.mdDoc ''
255 Path to the SSL key. Needed when {option}`useSSL` is enabled.
256 '';
257 };
258 sslCertPath = mkOption {
259 type = types.nullOr types.str;
260 default = null;
261 example = "/var/lib/hedgedoc/hedgedoc.crt";
262 description = lib.mdDoc ''
263 Path to the SSL cert. Needed when {option}`useSSL` is enabled.
264 '';
265 };
266 sslCAPath = mkOption {
267 type = types.listOf types.str;
268 default = [];
269 example = [ "/var/lib/hedgedoc/ca.crt" ];
270 description = lib.mdDoc ''
271 SSL ca chain. Needed when {option}`useSSL` is enabled.
272 '';
273 };
274 dhParamPath = mkOption {
275 type = types.nullOr types.str;
276 default = null;
277 example = "/var/lib/hedgedoc/dhparam.pem";
278 description = lib.mdDoc ''
279 Path to the SSL dh params. Needed when {option}`useSSL` is enabled.
280 '';
281 };
282 tmpPath = mkOption {
283 type = types.str;
284 default = "/tmp";
285 description = lib.mdDoc ''
286 Path to the temp directory HedgeDoc should use.
287 Note that {option}`serviceConfig.PrivateTmp` is enabled for
288 the HedgeDoc systemd service by default.
289 (Non-canonical paths are relative to HedgeDoc's base directory)
290 '';
291 };
292 defaultNotePath = mkOption {
293 type = types.nullOr types.str;
294 default = "${cfg.package}/public/default.md";
295 defaultText = literalExpression "\"\${cfg.package}/public/default.md\"";
296 description = lib.mdDoc ''
297 Path to the default Note file.
298 (Non-canonical paths are relative to HedgeDoc's base directory)
299 '';
300 };
301 docsPath = mkOption {
302 type = types.nullOr types.str;
303 default = "${cfg.package}/public/docs";
304 defaultText = literalExpression "\"\${cfg.package}/public/docs\"";
305 description = lib.mdDoc ''
306 Path to the docs directory.
307 (Non-canonical paths are relative to HedgeDoc's base directory)
308 '';
309 };
310 indexPath = mkOption {
311 type = types.nullOr types.str;
312 default = "${cfg.package}/public/views/index.ejs";
313 defaultText = literalExpression "\"\${cfg.package}/public/views/index.ejs\"";
314 description = lib.mdDoc ''
315 Path to the index template file.
316 (Non-canonical paths are relative to HedgeDoc's base directory)
317 '';
318 };
319 hackmdPath = mkOption {
320 type = types.nullOr types.str;
321 default = "${cfg.package}/public/views/hackmd.ejs";
322 defaultText = literalExpression "\"\${cfg.package}/public/views/hackmd.ejs\"";
323 description = lib.mdDoc ''
324 Path to the hackmd template file.
325 (Non-canonical paths are relative to HedgeDoc's base directory)
326 '';
327 };
328 errorPath = mkOption {
329 type = types.nullOr types.str;
330 default = "${cfg.package}/public/views/error.ejs";
331 defaultText = literalExpression "\"\${cfg.package}/public/views/error.ejs\"";
332 description = lib.mdDoc ''
333 Path to the error template file.
334 (Non-canonical paths are relative to HedgeDoc's base directory)
335 '';
336 };
337 prettyPath = mkOption {
338 type = types.nullOr types.str;
339 default = "${cfg.package}/public/views/pretty.ejs";
340 defaultText = literalExpression "\"\${cfg.package}/public/views/pretty.ejs\"";
341 description = lib.mdDoc ''
342 Path to the pretty template file.
343 (Non-canonical paths are relative to HedgeDoc's base directory)
344 '';
345 };
346 slidePath = mkOption {
347 type = types.nullOr types.str;
348 default = "${cfg.package}/public/views/slide.hbs";
349 defaultText = literalExpression "\"\${cfg.package}/public/views/slide.hbs\"";
350 description = lib.mdDoc ''
351 Path to the slide template file.
352 (Non-canonical paths are relative to HedgeDoc's base directory)
353 '';
354 };
355 uploadsPath = mkOption {
356 type = types.str;
357 default = "${cfg.workDir}/uploads";
358 defaultText = literalExpression "\"\${cfg.workDir}/uploads\"";
359 description = lib.mdDoc ''
360 Path under which uploaded files are saved.
361 '';
362 };
363 sessionName = mkOption {
364 type = types.str;
365 default = "connect.sid";
366 description = lib.mdDoc ''
367 Specify the name of the session cookie.
368 '';
369 };
370 sessionSecret = mkOption {
371 type = types.nullOr types.str;
372 default = null;
373 description = lib.mdDoc ''
374 Specify the secret used to sign the session cookie.
375 If unset, one will be generated on startup.
376 '';
377 };
378 sessionLife = mkOption {
379 type = types.int;
380 default = 1209600000;
381 description = lib.mdDoc ''
382 Session life time in milliseconds.
383 '';
384 };
385 heartbeatInterval = mkOption {
386 type = types.int;
387 default = 5000;
388 description = lib.mdDoc ''
389 Specify the socket.io heartbeat interval.
390 '';
391 };
392 heartbeatTimeout = mkOption {
393 type = types.int;
394 default = 10000;
395 description = lib.mdDoc ''
396 Specify the socket.io heartbeat timeout.
397 '';
398 };
399 documentMaxLength = mkOption {
400 type = types.int;
401 default = 100000;
402 description = lib.mdDoc ''
403 Specify the maximum document length.
404 '';
405 };
406 email = mkOption {
407 type = types.bool;
408 default = true;
409 description = lib.mdDoc ''
410 Whether to enable email sign-in.
411 '';
412 };
413 allowEmailRegister = mkOption {
414 type = types.bool;
415 default = true;
416 description = lib.mdDoc ''
417 Whether to enable email registration.
418 '';
419 };
420 allowGravatar = mkOption {
421 type = types.bool;
422 default = true;
423 description = lib.mdDoc ''
424 Whether to use gravatar as profile picture source.
425 '';
426 };
427 imageUploadType = mkOption {
428 type = types.enum [ "imgur" "s3" "minio" "filesystem" ];
429 default = "filesystem";
430 description = lib.mdDoc ''
431 Specify where to upload images.
432 '';
433 };
434 minio = mkOption {
435 type = types.nullOr (types.submodule {
436 options = {
437 accessKey = mkOption {
438 type = types.str;
439 description = lib.mdDoc ''
440 Minio access key.
441 '';
442 };
443 secretKey = mkOption {
444 type = types.str;
445 description = lib.mdDoc ''
446 Minio secret key.
447 '';
448 };
449 endPoint = mkOption {
450 type = types.str;
451 description = lib.mdDoc ''
452 Minio endpoint.
453 '';
454 };
455 port = mkOption {
456 type = types.port;
457 default = 9000;
458 description = lib.mdDoc ''
459 Minio listen port.
460 '';
461 };
462 secure = mkOption {
463 type = types.bool;
464 default = true;
465 description = lib.mdDoc ''
466 Whether to use HTTPS for Minio.
467 '';
468 };
469 };
470 });
471 default = null;
472 description = lib.mdDoc "Configure the minio third-party integration.";
473 };
474 s3 = mkOption {
475 type = types.nullOr (types.submodule {
476 options = {
477 accessKeyId = mkOption {
478 type = types.str;
479 description = lib.mdDoc ''
480 AWS access key id.
481 '';
482 };
483 secretAccessKey = mkOption {
484 type = types.str;
485 description = lib.mdDoc ''
486 AWS access key.
487 '';
488 };
489 region = mkOption {
490 type = types.str;
491 description = lib.mdDoc ''
492 AWS S3 region.
493 '';
494 };
495 };
496 });
497 default = null;
498 description = lib.mdDoc "Configure the s3 third-party integration.";
499 };
500 s3bucket = mkOption {
501 type = types.nullOr types.str;
502 default = null;
503 description = lib.mdDoc ''
504 Specify the bucket name for upload types `s3` and `minio`.
505 '';
506 };
507 allowPDFExport = mkOption {
508 type = types.bool;
509 default = true;
510 description = lib.mdDoc ''
511 Whether to enable PDF exports.
512 '';
513 };
514 imgur.clientId = mkOption {
515 type = types.nullOr types.str;
516 default = null;
517 description = lib.mdDoc ''
518 Imgur API client ID.
519 '';
520 };
521 azure = mkOption {
522 type = types.nullOr (types.submodule {
523 options = {
524 connectionString = mkOption {
525 type = types.str;
526 description = lib.mdDoc ''
527 Azure Blob Storage connection string.
528 '';
529 };
530 container = mkOption {
531 type = types.str;
532 description = lib.mdDoc ''
533 Azure Blob Storage container name.
534 It will be created if non-existent.
535 '';
536 };
537 };
538 });
539 default = null;
540 description = lib.mdDoc "Configure the azure third-party integration.";
541 };
542 oauth2 = mkOption {
543 type = types.nullOr (types.submodule {
544 options = {
545 authorizationURL = mkOption {
546 type = types.str;
547 description = lib.mdDoc ''
548 Specify the OAuth authorization URL.
549 '';
550 };
551 tokenURL = mkOption {
552 type = types.str;
553 description = lib.mdDoc ''
554 Specify the OAuth token URL.
555 '';
556 };
557 baseURL = mkOption {
558 type = with types; nullOr str;
559 default = null;
560 description = lib.mdDoc ''
561 Specify the OAuth base URL.
562 '';
563 };
564 userProfileURL = mkOption {
565 type = with types; nullOr str;
566 default = null;
567 description = lib.mdDoc ''
568 Specify the OAuth userprofile URL.
569 '';
570 };
571 userProfileUsernameAttr = mkOption {
572 type = with types; nullOr str;
573 default = null;
574 description = lib.mdDoc ''
575 Specify the name of the attribute for the username from the claim.
576 '';
577 };
578 userProfileDisplayNameAttr = mkOption {
579 type = with types; nullOr str;
580 default = null;
581 description = lib.mdDoc ''
582 Specify the name of the attribute for the display name from the claim.
583 '';
584 };
585 userProfileEmailAttr = mkOption {
586 type = with types; nullOr str;
587 default = null;
588 description = lib.mdDoc ''
589 Specify the name of the attribute for the email from the claim.
590 '';
591 };
592 scope = mkOption {
593 type = with types; nullOr str;
594 default = null;
595 description = lib.mdDoc ''
596 Specify the OAuth scope.
597 '';
598 };
599 providerName = mkOption {
600 type = with types; nullOr str;
601 default = null;
602 description = lib.mdDoc ''
603 Specify the name to be displayed for this strategy.
604 '';
605 };
606 rolesClaim = mkOption {
607 type = with types; nullOr str;
608 default = null;
609 description = lib.mdDoc ''
610 Specify the role claim name.
611 '';
612 };
613 accessRole = mkOption {
614 type = with types; nullOr str;
615 default = null;
616 description = lib.mdDoc ''
617 Specify role which should be included in the ID token roles claim to grant access
618 '';
619 };
620 clientID = mkOption {
621 type = types.str;
622 description = lib.mdDoc ''
623 Specify the OAuth client ID.
624 '';
625 };
626 clientSecret = mkOption {
627 type = types.str;
628 description = lib.mdDoc ''
629 Specify the OAuth client secret.
630 '';
631 };
632 };
633 });
634 default = null;
635 description = lib.mdDoc "Configure the OAuth integration.";
636 };
637 facebook = mkOption {
638 type = types.nullOr (types.submodule {
639 options = {
640 clientID = mkOption {
641 type = types.str;
642 description = lib.mdDoc ''
643 Facebook API client ID.
644 '';
645 };
646 clientSecret = mkOption {
647 type = types.str;
648 description = lib.mdDoc ''
649 Facebook API client secret.
650 '';
651 };
652 };
653 });
654 default = null;
655 description = lib.mdDoc "Configure the facebook third-party integration";
656 };
657 twitter = mkOption {
658 type = types.nullOr (types.submodule {
659 options = {
660 consumerKey = mkOption {
661 type = types.str;
662 description = lib.mdDoc ''
663 Twitter API consumer key.
664 '';
665 };
666 consumerSecret = mkOption {
667 type = types.str;
668 description = lib.mdDoc ''
669 Twitter API consumer secret.
670 '';
671 };
672 };
673 });
674 default = null;
675 description = lib.mdDoc "Configure the Twitter third-party integration.";
676 };
677 github = mkOption {
678 type = types.nullOr (types.submodule {
679 options = {
680 clientID = mkOption {
681 type = types.str;
682 description = lib.mdDoc ''
683 GitHub API client ID.
684 '';
685 };
686 clientSecret = mkOption {
687 type = types.str;
688 description = lib.mdDoc ''
689 Github API client secret.
690 '';
691 };
692 };
693 });
694 default = null;
695 description = lib.mdDoc "Configure the GitHub third-party integration.";
696 };
697 gitlab = mkOption {
698 type = types.nullOr (types.submodule {
699 options = {
700 baseURL = mkOption {
701 type = types.str;
702 default = "";
703 description = lib.mdDoc ''
704 GitLab API authentication endpoint.
705 Only needed for other endpoints than gitlab.com.
706 '';
707 };
708 clientID = mkOption {
709 type = types.str;
710 description = lib.mdDoc ''
711 GitLab API client ID.
712 '';
713 };
714 clientSecret = mkOption {
715 type = types.str;
716 description = lib.mdDoc ''
717 GitLab API client secret.
718 '';
719 };
720 scope = mkOption {
721 type = types.enum [ "api" "read_user" ];
722 default = "api";
723 description = lib.mdDoc ''
724 GitLab API requested scope.
725 GitLab snippet import/export requires api scope.
726 '';
727 };
728 };
729 });
730 default = null;
731 description = lib.mdDoc "Configure the GitLab third-party integration.";
732 };
733 mattermost = mkOption {
734 type = types.nullOr (types.submodule {
735 options = {
736 baseURL = mkOption {
737 type = types.str;
738 description = lib.mdDoc ''
739 Mattermost authentication endpoint.
740 '';
741 };
742 clientID = mkOption {
743 type = types.str;
744 description = lib.mdDoc ''
745 Mattermost API client ID.
746 '';
747 };
748 clientSecret = mkOption {
749 type = types.str;
750 description = lib.mdDoc ''
751 Mattermost API client secret.
752 '';
753 };
754 };
755 });
756 default = null;
757 description = lib.mdDoc "Configure the Mattermost third-party integration.";
758 };
759 dropbox = mkOption {
760 type = types.nullOr (types.submodule {
761 options = {
762 clientID = mkOption {
763 type = types.str;
764 description = lib.mdDoc ''
765 Dropbox API client ID.
766 '';
767 };
768 clientSecret = mkOption {
769 type = types.str;
770 description = lib.mdDoc ''
771 Dropbox API client secret.
772 '';
773 };
774 appKey = mkOption {
775 type = types.str;
776 description = lib.mdDoc ''
777 Dropbox app key.
778 '';
779 };
780 };
781 });
782 default = null;
783 description = lib.mdDoc "Configure the Dropbox third-party integration.";
784 };
785 google = mkOption {
786 type = types.nullOr (types.submodule {
787 options = {
788 clientID = mkOption {
789 type = types.str;
790 description = lib.mdDoc ''
791 Google API client ID.
792 '';
793 };
794 clientSecret = mkOption {
795 type = types.str;
796 description = lib.mdDoc ''
797 Google API client secret.
798 '';
799 };
800 };
801 });
802 default = null;
803 description = lib.mdDoc "Configure the Google third-party integration.";
804 };
805 ldap = mkOption {
806 type = types.nullOr (types.submodule {
807 options = {
808 providerName = mkOption {
809 type = types.str;
810 default = "";
811 description = lib.mdDoc ''
812 Optional name to be displayed at login form, indicating the LDAP provider.
813 '';
814 };
815 url = mkOption {
816 type = types.str;
817 example = "ldap://localhost";
818 description = lib.mdDoc ''
819 URL of LDAP server.
820 '';
821 };
822 bindDn = mkOption {
823 type = types.str;
824 description = lib.mdDoc ''
825 Bind DN for LDAP access.
826 '';
827 };
828 bindCredentials = mkOption {
829 type = types.str;
830 description = lib.mdDoc ''
831 Bind credentials for LDAP access.
832 '';
833 };
834 searchBase = mkOption {
835 type = types.str;
836 example = "o=users,dc=example,dc=com";
837 description = lib.mdDoc ''
838 LDAP directory to begin search from.
839 '';
840 };
841 searchFilter = mkOption {
842 type = types.str;
843 example = "(uid={{username}})";
844 description = lib.mdDoc ''
845 LDAP filter to search with.
846 '';
847 };
848 searchAttributes = mkOption {
849 type = types.nullOr (types.listOf types.str);
850 default = null;
851 example = [ "displayName" "mail" ];
852 description = lib.mdDoc ''
853 LDAP attributes to search with.
854 '';
855 };
856 userNameField = mkOption {
857 type = types.str;
858 default = "";
859 description = lib.mdDoc ''
860 LDAP field which is used as the username on HedgeDoc.
861 By default {option}`useridField` is used.
862 '';
863 };
864 useridField = mkOption {
865 type = types.str;
866 example = "uid";
867 description = lib.mdDoc ''
868 LDAP field which is a unique identifier for users on HedgeDoc.
869 '';
870 };
871 tlsca = mkOption {
872 type = types.str;
873 default = "/etc/ssl/certs/ca-certificates.crt";
874 example = "server-cert.pem,root.pem";
875 description = lib.mdDoc ''
876 Root CA for LDAP TLS in PEM format.
877 '';
878 };
879 };
880 });
881 default = null;
882 description = lib.mdDoc "Configure the LDAP integration.";
883 };
884 saml = mkOption {
885 type = types.nullOr (types.submodule {
886 options = {
887 idpSsoUrl = mkOption {
888 type = types.str;
889 example = "https://idp.example.com/sso";
890 description = lib.mdDoc ''
891 IdP authentication endpoint.
892 '';
893 };
894 idpCert = mkOption {
895 type = types.path;
896 example = "/path/to/cert.pem";
897 description = lib.mdDoc ''
898 Path to IdP certificate file in PEM format.
899 '';
900 };
901 issuer = mkOption {
902 type = types.str;
903 default = "";
904 description = lib.mdDoc ''
905 Optional identity of the service provider.
906 This defaults to the server URL.
907 '';
908 };
909 identifierFormat = mkOption {
910 type = types.str;
911 default = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress";
912 description = lib.mdDoc ''
913 Optional name identifier format.
914 '';
915 };
916 groupAttribute = mkOption {
917 type = types.str;
918 default = "";
919 example = "memberOf";
920 description = lib.mdDoc ''
921 Optional attribute name for group list.
922 '';
923 };
924 externalGroups = mkOption {
925 type = types.listOf types.str;
926 default = [];
927 example = [ "Temporary-staff" "External-users" ];
928 description = lib.mdDoc ''
929 Excluded group names.
930 '';
931 };
932 requiredGroups = mkOption {
933 type = types.listOf types.str;
934 default = [];
935 example = [ "Hedgedoc-Users" ];
936 description = lib.mdDoc ''
937 Required group names.
938 '';
939 };
940 providerName = mkOption {
941 type = types.str;
942 default = "";
943 example = "My institution";
944 description = lib.mdDoc ''
945 Optional name to be displayed at login form indicating the SAML provider.
946 '';
947 };
948 attribute = {
949 id = mkOption {
950 type = types.str;
951 default = "";
952 description = lib.mdDoc ''
953 Attribute map for `id`.
954 Defaults to `NameID` of SAML response.
955 '';
956 };
957 username = mkOption {
958 type = types.str;
959 default = "";
960 description = lib.mdDoc ''
961 Attribute map for `username`.
962 Defaults to `NameID` of SAML response.
963 '';
964 };
965 email = mkOption {
966 type = types.str;
967 default = "";
968 description = lib.mdDoc ''
969 Attribute map for `email`.
970 Defaults to `NameID` of SAML response if
971 {option}`identifierFormat` has
972 the default value.
973 '';
974 };
975 };
976 };
977 });
978 default = null;
979 description = lib.mdDoc "Configure the SAML integration.";
980 };
981 }; in lib.mkOption {
982 type = lib.types.submodule {
983 freeformType = settingsFormat.type;
984 inherit options;
985 };
986 description = lib.mdDoc ''
987 HedgeDoc configuration, see
988 <https://docs.hedgedoc.org/configuration/>
989 for documentation.
990 '';
991 };
992
993 environmentFile = mkOption {
994 type = with types; nullOr path;
995 default = null;
996 example = "/var/lib/hedgedoc/hedgedoc.env";
997 description = lib.mdDoc ''
998 Environment file as defined in {manpage}`systemd.exec(5)`.
999
1000 Secrets may be passed to the service without adding them to the world-readable
1001 Nix store, by specifying placeholder variables as the option value in Nix and
1002 setting these variables accordingly in the environment file.
1003
1004 ```
1005 # snippet of HedgeDoc-related config
1006 services.hedgedoc.settings.dbURL = "postgres://hedgedoc:\''${DB_PASSWORD}@db-host:5432/hedgedocdb";
1007 services.hedgedoc.settings.minio.secretKey = "$MINIO_SECRET_KEY";
1008 ```
1009
1010 ```
1011 # content of the environment file
1012 DB_PASSWORD=verysecretdbpassword
1013 MINIO_SECRET_KEY=verysecretminiokey
1014 ```
1015
1016 Note that this file needs to be available on the host on which
1017 `HedgeDoc` is running.
1018 '';
1019 };
1020
1021 package = mkOption {
1022 type = types.package;
1023 default = pkgs.hedgedoc;
1024 defaultText = literalExpression "pkgs.hedgedoc";
1025 description = lib.mdDoc ''
1026 Package that provides HedgeDoc.
1027 '';
1028 };
1029
1030 };
1031
1032 config = mkIf cfg.enable {
1033 assertions = [
1034 { assertion = cfg.settings.db == {} -> (
1035 cfg.settings.dbURL != "" && cfg.settings.dbURL != null
1036 );
1037 message = "Database configuration for HedgeDoc missing."; }
1038 ];
1039 users.groups.${name} = {};
1040 users.users.${name} = {
1041 description = "HedgeDoc service user";
1042 group = name;
1043 extraGroups = cfg.groups;
1044 home = cfg.workDir;
1045 createHome = true;
1046 isSystemUser = true;
1047 };
1048
1049 systemd.services.hedgedoc = {
1050 description = "HedgeDoc Service";
1051 wantedBy = [ "multi-user.target" ];
1052 after = [ "networking.target" ];
1053 preStart = ''
1054 ${pkgs.envsubst}/bin/envsubst \
1055 -o ${cfg.workDir}/config.json \
1056 -i ${prettyJSON cfg.settings}
1057 mkdir -p ${cfg.settings.uploadsPath}
1058 '';
1059 serviceConfig = {
1060 WorkingDirectory = cfg.workDir;
1061 StateDirectory = [ cfg.workDir cfg.settings.uploadsPath ];
1062 ExecStart = "${cfg.package}/bin/hedgedoc";
1063 EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
1064 Environment = [
1065 "CMD_CONFIG_FILE=${cfg.workDir}/config.json"
1066 "NODE_ENV=production"
1067 ];
1068 Restart = "always";
1069 User = name;
1070 PrivateTmp = true;
1071 };
1072 };
1073 };
1074}