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