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.int;
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 = "./public/default.md";
295 description = lib.mdDoc ''
296 Path to the default Note file.
297 (Non-canonical paths are relative to HedgeDoc's base directory)
298 '';
299 };
300 docsPath = mkOption {
301 type = types.nullOr types.str;
302 default = "./public/docs";
303 description = lib.mdDoc ''
304 Path to the docs directory.
305 (Non-canonical paths are relative to HedgeDoc's base directory)
306 '';
307 };
308 indexPath = mkOption {
309 type = types.nullOr types.str;
310 default = "./public/views/index.ejs";
311 description = lib.mdDoc ''
312 Path to the index template file.
313 (Non-canonical paths are relative to HedgeDoc's base directory)
314 '';
315 };
316 hackmdPath = mkOption {
317 type = types.nullOr types.str;
318 default = "./public/views/hackmd.ejs";
319 description = lib.mdDoc ''
320 Path to the hackmd template file.
321 (Non-canonical paths are relative to HedgeDoc's base directory)
322 '';
323 };
324 errorPath = mkOption {
325 type = types.nullOr types.str;
326 default = null;
327 defaultText = literalExpression "./public/views/error.ejs";
328 description = lib.mdDoc ''
329 Path to the error template file.
330 (Non-canonical paths are relative to HedgeDoc's base directory)
331 '';
332 };
333 prettyPath = mkOption {
334 type = types.nullOr types.str;
335 default = null;
336 defaultText = literalExpression "./public/views/pretty.ejs";
337 description = lib.mdDoc ''
338 Path to the pretty template file.
339 (Non-canonical paths are relative to HedgeDoc's base directory)
340 '';
341 };
342 slidePath = mkOption {
343 type = types.nullOr types.str;
344 default = null;
345 defaultText = literalExpression "./public/views/slide.hbs";
346 description = lib.mdDoc ''
347 Path to the slide template file.
348 (Non-canonical paths are relative to HedgeDoc's base directory)
349 '';
350 };
351 uploadsPath = mkOption {
352 type = types.str;
353 default = "${cfg.workDir}/uploads";
354 defaultText = literalExpression "/var/lib/${name}/uploads";
355 description = lib.mdDoc ''
356 Path under which uploaded files are saved.
357 '';
358 };
359 sessionName = mkOption {
360 type = types.str;
361 default = "connect.sid";
362 description = lib.mdDoc ''
363 Specify the name of the session cookie.
364 '';
365 };
366 sessionSecret = mkOption {
367 type = types.nullOr types.str;
368 default = null;
369 description = lib.mdDoc ''
370 Specify the secret used to sign the session cookie.
371 If unset, one will be generated on startup.
372 '';
373 };
374 sessionLife = mkOption {
375 type = types.int;
376 default = 1209600000;
377 description = lib.mdDoc ''
378 Session life time in milliseconds.
379 '';
380 };
381 heartbeatInterval = mkOption {
382 type = types.int;
383 default = 5000;
384 description = lib.mdDoc ''
385 Specify the socket.io heartbeat interval.
386 '';
387 };
388 heartbeatTimeout = mkOption {
389 type = types.int;
390 default = 10000;
391 description = lib.mdDoc ''
392 Specify the socket.io heartbeat timeout.
393 '';
394 };
395 documentMaxLength = mkOption {
396 type = types.int;
397 default = 100000;
398 description = lib.mdDoc ''
399 Specify the maximum document length.
400 '';
401 };
402 email = mkOption {
403 type = types.bool;
404 default = true;
405 description = lib.mdDoc ''
406 Whether to enable email sign-in.
407 '';
408 };
409 allowEmailRegister = mkOption {
410 type = types.bool;
411 default = true;
412 description = lib.mdDoc ''
413 Whether to enable email registration.
414 '';
415 };
416 allowGravatar = mkOption {
417 type = types.bool;
418 default = true;
419 description = lib.mdDoc ''
420 Whether to use gravatar as profile picture source.
421 '';
422 };
423 imageUploadType = mkOption {
424 type = types.enum [ "imgur" "s3" "minio" "filesystem" ];
425 default = "filesystem";
426 description = lib.mdDoc ''
427 Specify where to upload images.
428 '';
429 };
430 minio = mkOption {
431 type = types.nullOr (types.submodule {
432 options = {
433 accessKey = mkOption {
434 type = types.str;
435 description = lib.mdDoc ''
436 Minio access key.
437 '';
438 };
439 secretKey = mkOption {
440 type = types.str;
441 description = lib.mdDoc ''
442 Minio secret key.
443 '';
444 };
445 endPoint = mkOption {
446 type = types.str;
447 description = lib.mdDoc ''
448 Minio endpoint.
449 '';
450 };
451 port = mkOption {
452 type = types.int;
453 default = 9000;
454 description = lib.mdDoc ''
455 Minio listen port.
456 '';
457 };
458 secure = mkOption {
459 type = types.bool;
460 default = true;
461 description = lib.mdDoc ''
462 Whether to use HTTPS for Minio.
463 '';
464 };
465 };
466 });
467 default = null;
468 description = lib.mdDoc "Configure the minio third-party integration.";
469 };
470 s3 = mkOption {
471 type = types.nullOr (types.submodule {
472 options = {
473 accessKeyId = mkOption {
474 type = types.str;
475 description = lib.mdDoc ''
476 AWS access key id.
477 '';
478 };
479 secretAccessKey = mkOption {
480 type = types.str;
481 description = lib.mdDoc ''
482 AWS access key.
483 '';
484 };
485 region = mkOption {
486 type = types.str;
487 description = lib.mdDoc ''
488 AWS S3 region.
489 '';
490 };
491 };
492 });
493 default = null;
494 description = lib.mdDoc "Configure the s3 third-party integration.";
495 };
496 s3bucket = mkOption {
497 type = types.nullOr types.str;
498 default = null;
499 description = lib.mdDoc ''
500 Specify the bucket name for upload types `s3` and `minio`.
501 '';
502 };
503 allowPDFExport = mkOption {
504 type = types.bool;
505 default = true;
506 description = lib.mdDoc ''
507 Whether to enable PDF exports.
508 '';
509 };
510 imgur.clientId = mkOption {
511 type = types.nullOr types.str;
512 default = null;
513 description = lib.mdDoc ''
514 Imgur API client ID.
515 '';
516 };
517 azure = mkOption {
518 type = types.nullOr (types.submodule {
519 options = {
520 connectionString = mkOption {
521 type = types.str;
522 description = lib.mdDoc ''
523 Azure Blob Storage connection string.
524 '';
525 };
526 container = mkOption {
527 type = types.str;
528 description = lib.mdDoc ''
529 Azure Blob Storage container name.
530 It will be created if non-existent.
531 '';
532 };
533 };
534 });
535 default = null;
536 description = lib.mdDoc "Configure the azure third-party integration.";
537 };
538 oauth2 = mkOption {
539 type = types.nullOr (types.submodule {
540 options = {
541 authorizationURL = mkOption {
542 type = types.str;
543 description = lib.mdDoc ''
544 Specify the OAuth authorization URL.
545 '';
546 };
547 tokenURL = mkOption {
548 type = types.str;
549 description = lib.mdDoc ''
550 Specify the OAuth token URL.
551 '';
552 };
553 baseURL = mkOption {
554 type = with types; nullOr str;
555 default = null;
556 description = lib.mdDoc ''
557 Specify the OAuth base URL.
558 '';
559 };
560 userProfileURL = mkOption {
561 type = with types; nullOr str;
562 default = null;
563 description = lib.mdDoc ''
564 Specify the OAuth userprofile URL.
565 '';
566 };
567 userProfileUsernameAttr = mkOption {
568 type = with types; nullOr str;
569 default = null;
570 description = lib.mdDoc ''
571 Specify the name of the attribute for the username from the claim.
572 '';
573 };
574 userProfileDisplayNameAttr = mkOption {
575 type = with types; nullOr str;
576 default = null;
577 description = lib.mdDoc ''
578 Specify the name of the attribute for the display name from the claim.
579 '';
580 };
581 userProfileEmailAttr = mkOption {
582 type = with types; nullOr str;
583 default = null;
584 description = lib.mdDoc ''
585 Specify the name of the attribute for the email from the claim.
586 '';
587 };
588 scope = mkOption {
589 type = with types; nullOr str;
590 default = null;
591 description = lib.mdDoc ''
592 Specify the OAuth scope.
593 '';
594 };
595 providerName = mkOption {
596 type = with types; nullOr str;
597 default = null;
598 description = lib.mdDoc ''
599 Specify the name to be displayed for this strategy.
600 '';
601 };
602 rolesClaim = mkOption {
603 type = with types; nullOr str;
604 default = null;
605 description = lib.mdDoc ''
606 Specify the role claim name.
607 '';
608 };
609 accessRole = mkOption {
610 type = with types; nullOr str;
611 default = null;
612 description = lib.mdDoc ''
613 Specify role which should be included in the ID token roles claim to grant access
614 '';
615 };
616 clientID = mkOption {
617 type = types.str;
618 description = lib.mdDoc ''
619 Specify the OAuth client ID.
620 '';
621 };
622 clientSecret = mkOption {
623 type = types.str;
624 description = lib.mdDoc ''
625 Specify the OAuth client secret.
626 '';
627 };
628 };
629 });
630 default = null;
631 description = lib.mdDoc "Configure the OAuth integration.";
632 };
633 facebook = mkOption {
634 type = types.nullOr (types.submodule {
635 options = {
636 clientID = mkOption {
637 type = types.str;
638 description = lib.mdDoc ''
639 Facebook API client ID.
640 '';
641 };
642 clientSecret = mkOption {
643 type = types.str;
644 description = lib.mdDoc ''
645 Facebook API client secret.
646 '';
647 };
648 };
649 });
650 default = null;
651 description = lib.mdDoc "Configure the facebook third-party integration";
652 };
653 twitter = mkOption {
654 type = types.nullOr (types.submodule {
655 options = {
656 consumerKey = mkOption {
657 type = types.str;
658 description = lib.mdDoc ''
659 Twitter API consumer key.
660 '';
661 };
662 consumerSecret = mkOption {
663 type = types.str;
664 description = lib.mdDoc ''
665 Twitter API consumer secret.
666 '';
667 };
668 };
669 });
670 default = null;
671 description = lib.mdDoc "Configure the Twitter third-party integration.";
672 };
673 github = mkOption {
674 type = types.nullOr (types.submodule {
675 options = {
676 clientID = mkOption {
677 type = types.str;
678 description = lib.mdDoc ''
679 GitHub API client ID.
680 '';
681 };
682 clientSecret = mkOption {
683 type = types.str;
684 description = lib.mdDoc ''
685 Github API client secret.
686 '';
687 };
688 };
689 });
690 default = null;
691 description = lib.mdDoc "Configure the GitHub third-party integration.";
692 };
693 gitlab = mkOption {
694 type = types.nullOr (types.submodule {
695 options = {
696 baseURL = mkOption {
697 type = types.str;
698 default = "";
699 description = lib.mdDoc ''
700 GitLab API authentication endpoint.
701 Only needed for other endpoints than gitlab.com.
702 '';
703 };
704 clientID = mkOption {
705 type = types.str;
706 description = lib.mdDoc ''
707 GitLab API client ID.
708 '';
709 };
710 clientSecret = mkOption {
711 type = types.str;
712 description = lib.mdDoc ''
713 GitLab API client secret.
714 '';
715 };
716 scope = mkOption {
717 type = types.enum [ "api" "read_user" ];
718 default = "api";
719 description = lib.mdDoc ''
720 GitLab API requested scope.
721 GitLab snippet import/export requires api scope.
722 '';
723 };
724 };
725 });
726 default = null;
727 description = lib.mdDoc "Configure the GitLab third-party integration.";
728 };
729 mattermost = mkOption {
730 type = types.nullOr (types.submodule {
731 options = {
732 baseURL = mkOption {
733 type = types.str;
734 description = lib.mdDoc ''
735 Mattermost authentication endpoint.
736 '';
737 };
738 clientID = mkOption {
739 type = types.str;
740 description = lib.mdDoc ''
741 Mattermost API client ID.
742 '';
743 };
744 clientSecret = mkOption {
745 type = types.str;
746 description = lib.mdDoc ''
747 Mattermost API client secret.
748 '';
749 };
750 };
751 });
752 default = null;
753 description = lib.mdDoc "Configure the Mattermost third-party integration.";
754 };
755 dropbox = mkOption {
756 type = types.nullOr (types.submodule {
757 options = {
758 clientID = mkOption {
759 type = types.str;
760 description = lib.mdDoc ''
761 Dropbox API client ID.
762 '';
763 };
764 clientSecret = mkOption {
765 type = types.str;
766 description = lib.mdDoc ''
767 Dropbox API client secret.
768 '';
769 };
770 appKey = mkOption {
771 type = types.str;
772 description = lib.mdDoc ''
773 Dropbox app key.
774 '';
775 };
776 };
777 });
778 default = null;
779 description = lib.mdDoc "Configure the Dropbox third-party integration.";
780 };
781 google = mkOption {
782 type = types.nullOr (types.submodule {
783 options = {
784 clientID = mkOption {
785 type = types.str;
786 description = lib.mdDoc ''
787 Google API client ID.
788 '';
789 };
790 clientSecret = mkOption {
791 type = types.str;
792 description = lib.mdDoc ''
793 Google API client secret.
794 '';
795 };
796 };
797 });
798 default = null;
799 description = lib.mdDoc "Configure the Google third-party integration.";
800 };
801 ldap = mkOption {
802 type = types.nullOr (types.submodule {
803 options = {
804 providerName = mkOption {
805 type = types.str;
806 default = "";
807 description = lib.mdDoc ''
808 Optional name to be displayed at login form, indicating the LDAP provider.
809 '';
810 };
811 url = mkOption {
812 type = types.str;
813 example = "ldap://localhost";
814 description = lib.mdDoc ''
815 URL of LDAP server.
816 '';
817 };
818 bindDn = mkOption {
819 type = types.str;
820 description = lib.mdDoc ''
821 Bind DN for LDAP access.
822 '';
823 };
824 bindCredentials = mkOption {
825 type = types.str;
826 description = lib.mdDoc ''
827 Bind credentials for LDAP access.
828 '';
829 };
830 searchBase = mkOption {
831 type = types.str;
832 example = "o=users,dc=example,dc=com";
833 description = lib.mdDoc ''
834 LDAP directory to begin search from.
835 '';
836 };
837 searchFilter = mkOption {
838 type = types.str;
839 example = "(uid={{username}})";
840 description = lib.mdDoc ''
841 LDAP filter to search with.
842 '';
843 };
844 searchAttributes = mkOption {
845 type = types.nullOr (types.listOf types.str);
846 default = null;
847 example = [ "displayName" "mail" ];
848 description = lib.mdDoc ''
849 LDAP attributes to search with.
850 '';
851 };
852 userNameField = mkOption {
853 type = types.str;
854 default = "";
855 description = lib.mdDoc ''
856 LDAP field which is used as the username on HedgeDoc.
857 By default {option}`useridField` is used.
858 '';
859 };
860 useridField = mkOption {
861 type = types.str;
862 example = "uid";
863 description = lib.mdDoc ''
864 LDAP field which is a unique identifier for users on HedgeDoc.
865 '';
866 };
867 tlsca = mkOption {
868 type = types.str;
869 default = "/etc/ssl/certs/ca-certificates.crt";
870 example = "server-cert.pem,root.pem";
871 description = lib.mdDoc ''
872 Root CA for LDAP TLS in PEM format.
873 '';
874 };
875 };
876 });
877 default = null;
878 description = lib.mdDoc "Configure the LDAP integration.";
879 };
880 saml = mkOption {
881 type = types.nullOr (types.submodule {
882 options = {
883 idpSsoUrl = mkOption {
884 type = types.str;
885 example = "https://idp.example.com/sso";
886 description = lib.mdDoc ''
887 IdP authentication endpoint.
888 '';
889 };
890 idpCert = mkOption {
891 type = types.path;
892 example = "/path/to/cert.pem";
893 description = lib.mdDoc ''
894 Path to IdP certificate file in PEM format.
895 '';
896 };
897 issuer = mkOption {
898 type = types.str;
899 default = "";
900 description = lib.mdDoc ''
901 Optional identity of the service provider.
902 This defaults to the server URL.
903 '';
904 };
905 identifierFormat = mkOption {
906 type = types.str;
907 default = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress";
908 description = lib.mdDoc ''
909 Optional name identifier format.
910 '';
911 };
912 groupAttribute = mkOption {
913 type = types.str;
914 default = "";
915 example = "memberOf";
916 description = lib.mdDoc ''
917 Optional attribute name for group list.
918 '';
919 };
920 externalGroups = mkOption {
921 type = types.listOf types.str;
922 default = [];
923 example = [ "Temporary-staff" "External-users" ];
924 description = lib.mdDoc ''
925 Excluded group names.
926 '';
927 };
928 requiredGroups = mkOption {
929 type = types.listOf types.str;
930 default = [];
931 example = [ "Hedgedoc-Users" ];
932 description = lib.mdDoc ''
933 Required group names.
934 '';
935 };
936 providerName = mkOption {
937 type = types.str;
938 default = "";
939 example = "My institution";
940 description = lib.mdDoc ''
941 Optional name to be displayed at login form indicating the SAML provider.
942 '';
943 };
944 attribute = {
945 id = mkOption {
946 type = types.str;
947 default = "";
948 description = lib.mdDoc ''
949 Attribute map for `id'.
950 Defaults to `NameID' of SAML response.
951 '';
952 };
953 username = mkOption {
954 type = types.str;
955 default = "";
956 description = lib.mdDoc ''
957 Attribute map for `username'.
958 Defaults to `NameID' of SAML response.
959 '';
960 };
961 email = mkOption {
962 type = types.str;
963 default = "";
964 description = lib.mdDoc ''
965 Attribute map for `email`.
966 Defaults to `NameID` of SAML response if
967 {option}`identifierFormat` has
968 the default value.
969 '';
970 };
971 };
972 };
973 });
974 default = null;
975 description = lib.mdDoc "Configure the SAML integration.";
976 };
977 }; in lib.mkOption {
978 type = lib.types.submodule {
979 freeformType = settingsFormat.type;
980 inherit options;
981 };
982 description = lib.mdDoc ''
983 HedgeDoc configuration, see
984 <https://docs.hedgedoc.org/configuration/>
985 for documentation.
986 '';
987 };
988
989 environmentFile = mkOption {
990 type = with types; nullOr path;
991 default = null;
992 example = "/var/lib/hedgedoc/hedgedoc.env";
993 description = lib.mdDoc ''
994 Environment file as defined in {manpage}`systemd.exec(5)`.
995
996 Secrets may be passed to the service without adding them to the world-readable
997 Nix store, by specifying placeholder variables as the option value in Nix and
998 setting these variables accordingly in the environment file.
999
1000 ```
1001 # snippet of HedgeDoc-related config
1002 services.hedgedoc.configuration.dbURL = "postgres://hedgedoc:\''${DB_PASSWORD}@db-host:5432/hedgedocdb";
1003 services.hedgedoc.configuration.minio.secretKey = "$MINIO_SECRET_KEY";
1004 ```
1005
1006 ```
1007 # content of the environment file
1008 DB_PASSWORD=verysecretdbpassword
1009 MINIO_SECRET_KEY=verysecretminiokey
1010 ```
1011
1012 Note that this file needs to be available on the host on which
1013 `HedgeDoc` is running.
1014 '';
1015 };
1016
1017 package = mkOption {
1018 type = types.package;
1019 default = pkgs.hedgedoc;
1020 defaultText = literalExpression "pkgs.hedgedoc";
1021 description = lib.mdDoc ''
1022 Package that provides HedgeDoc.
1023 '';
1024 };
1025
1026 };
1027
1028 config = mkIf cfg.enable {
1029 assertions = [
1030 { assertion = cfg.settings.db == {} -> (
1031 cfg.settings.dbURL != "" && cfg.settings.dbURL != null
1032 );
1033 message = "Database configuration for HedgeDoc missing."; }
1034 ];
1035 users.groups.${name} = {};
1036 users.users.${name} = {
1037 description = "HedgeDoc service user";
1038 group = name;
1039 extraGroups = cfg.groups;
1040 home = cfg.workDir;
1041 createHome = true;
1042 isSystemUser = true;
1043 };
1044
1045 systemd.services.hedgedoc = {
1046 description = "HedgeDoc Service";
1047 wantedBy = [ "multi-user.target" ];
1048 after = [ "networking.target" ];
1049 preStart = ''
1050 ${pkgs.envsubst}/bin/envsubst \
1051 -o ${cfg.workDir}/config.json \
1052 -i ${prettyJSON cfg.settings}
1053 mkdir -p ${cfg.settings.uploadsPath}
1054 '';
1055 serviceConfig = {
1056 WorkingDirectory = cfg.workDir;
1057 StateDirectory = [ cfg.workDir cfg.settings.uploadsPath ];
1058 ExecStart = "${cfg.package}/bin/hedgedoc";
1059 EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
1060 Environment = [
1061 "CMD_CONFIG_FILE=${cfg.workDir}/config.json"
1062 "NODE_ENV=production"
1063 ];
1064 Restart = "always";
1065 User = name;
1066 PrivateTmp = true;
1067 };
1068 };
1069 };
1070}