at 21.11-pre 30 kB view raw
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 = literalExample '' 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 = literalExample '' 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 = "./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 = "./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 = "./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 = "/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 clientID = mkOption { 543 type = types.str; 544 description = '' 545 Specify the OAuth client ID. 546 ''; 547 }; 548 clientSecret = mkOption { 549 type = types.str; 550 description = '' 551 Specify the OAuth client secret. 552 ''; 553 }; 554 }; 555 }); 556 default = null; 557 description = "Configure the OAuth integration."; 558 }; 559 facebook = mkOption { 560 type = types.nullOr (types.submodule { 561 options = { 562 clientID = mkOption { 563 type = types.str; 564 description = '' 565 Facebook API client ID. 566 ''; 567 }; 568 clientSecret = mkOption { 569 type = types.str; 570 description = '' 571 Facebook API client secret. 572 ''; 573 }; 574 }; 575 }); 576 default = null; 577 description = "Configure the facebook third-party integration"; 578 }; 579 twitter = mkOption { 580 type = types.nullOr (types.submodule { 581 options = { 582 consumerKey = mkOption { 583 type = types.str; 584 description = '' 585 Twitter API consumer key. 586 ''; 587 }; 588 consumerSecret = mkOption { 589 type = types.str; 590 description = '' 591 Twitter API consumer secret. 592 ''; 593 }; 594 }; 595 }); 596 default = null; 597 description = "Configure the Twitter third-party integration."; 598 }; 599 github = mkOption { 600 type = types.nullOr (types.submodule { 601 options = { 602 clientID = mkOption { 603 type = types.str; 604 description = '' 605 GitHub API client ID. 606 ''; 607 }; 608 clientSecret = mkOption { 609 type = types.str; 610 description = '' 611 Github API client secret. 612 ''; 613 }; 614 }; 615 }); 616 default = null; 617 description = "Configure the GitHub third-party integration."; 618 }; 619 gitlab = mkOption { 620 type = types.nullOr (types.submodule { 621 options = { 622 baseURL = mkOption { 623 type = types.str; 624 default = ""; 625 description = '' 626 GitLab API authentication endpoint. 627 Only needed for other endpoints than gitlab.com. 628 ''; 629 }; 630 clientID = mkOption { 631 type = types.str; 632 description = '' 633 GitLab API client ID. 634 ''; 635 }; 636 clientSecret = mkOption { 637 type = types.str; 638 description = '' 639 GitLab API client secret. 640 ''; 641 }; 642 scope = mkOption { 643 type = types.enum [ "api" "read_user" ]; 644 default = "api"; 645 description = '' 646 GitLab API requested scope. 647 GitLab snippet import/export requires api scope. 648 ''; 649 }; 650 }; 651 }); 652 default = null; 653 description = "Configure the GitLab third-party integration."; 654 }; 655 mattermost = mkOption { 656 type = types.nullOr (types.submodule { 657 options = { 658 baseURL = mkOption { 659 type = types.str; 660 description = '' 661 Mattermost authentication endpoint. 662 ''; 663 }; 664 clientID = mkOption { 665 type = types.str; 666 description = '' 667 Mattermost API client ID. 668 ''; 669 }; 670 clientSecret = mkOption { 671 type = types.str; 672 description = '' 673 Mattermost API client secret. 674 ''; 675 }; 676 }; 677 }); 678 default = null; 679 description = "Configure the Mattermost third-party integration."; 680 }; 681 dropbox = mkOption { 682 type = types.nullOr (types.submodule { 683 options = { 684 clientID = mkOption { 685 type = types.str; 686 description = '' 687 Dropbox API client ID. 688 ''; 689 }; 690 clientSecret = mkOption { 691 type = types.str; 692 description = '' 693 Dropbox API client secret. 694 ''; 695 }; 696 appKey = mkOption { 697 type = types.str; 698 description = '' 699 Dropbox app key. 700 ''; 701 }; 702 }; 703 }); 704 default = null; 705 description = "Configure the Dropbox third-party integration."; 706 }; 707 google = mkOption { 708 type = types.nullOr (types.submodule { 709 options = { 710 clientID = mkOption { 711 type = types.str; 712 description = '' 713 Google API client ID. 714 ''; 715 }; 716 clientSecret = mkOption { 717 type = types.str; 718 description = '' 719 Google API client secret. 720 ''; 721 }; 722 }; 723 }); 724 default = null; 725 description = "Configure the Google third-party integration."; 726 }; 727 ldap = mkOption { 728 type = types.nullOr (types.submodule { 729 options = { 730 providerName = mkOption { 731 type = types.str; 732 default = ""; 733 description = '' 734 Optional name to be displayed at login form, indicating the LDAP provider. 735 ''; 736 }; 737 url = mkOption { 738 type = types.str; 739 example = "ldap://localhost"; 740 description = '' 741 URL of LDAP server. 742 ''; 743 }; 744 bindDn = mkOption { 745 type = types.str; 746 description = '' 747 Bind DN for LDAP access. 748 ''; 749 }; 750 bindCredentials = mkOption { 751 type = types.str; 752 description = '' 753 Bind credentials for LDAP access. 754 ''; 755 }; 756 searchBase = mkOption { 757 type = types.str; 758 example = "o=users,dc=example,dc=com"; 759 description = '' 760 LDAP directory to begin search from. 761 ''; 762 }; 763 searchFilter = mkOption { 764 type = types.str; 765 example = "(uid={{username}})"; 766 description = '' 767 LDAP filter to search with. 768 ''; 769 }; 770 searchAttributes = mkOption { 771 type = types.listOf types.str; 772 example = [ "displayName" "mail" ]; 773 description = '' 774 LDAP attributes to search with. 775 ''; 776 }; 777 userNameField = mkOption { 778 type = types.str; 779 default = ""; 780 description = '' 781 LDAP field which is used as the username on HedgeDoc. 782 By default <option>useridField</option> is used. 783 ''; 784 }; 785 useridField = mkOption { 786 type = types.str; 787 example = "uid"; 788 description = '' 789 LDAP field which is a unique identifier for users on HedgeDoc. 790 ''; 791 }; 792 tlsca = mkOption { 793 type = types.str; 794 example = "server-cert.pem,root.pem"; 795 description = '' 796 Root CA for LDAP TLS in PEM format. 797 ''; 798 }; 799 }; 800 }); 801 default = null; 802 description = "Configure the LDAP integration."; 803 }; 804 saml = mkOption { 805 type = types.nullOr (types.submodule { 806 options = { 807 idpSsoUrl = mkOption { 808 type = types.str; 809 example = "https://idp.example.com/sso"; 810 description = '' 811 IdP authentication endpoint. 812 ''; 813 }; 814 idpCert = mkOption { 815 type = types.path; 816 example = "/path/to/cert.pem"; 817 description = '' 818 Path to IdP certificate file in PEM format. 819 ''; 820 }; 821 issuer = mkOption { 822 type = types.str; 823 default = ""; 824 description = '' 825 Optional identity of the service provider. 826 This defaults to the server URL. 827 ''; 828 }; 829 identifierFormat = mkOption { 830 type = types.str; 831 default = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"; 832 description = '' 833 Optional name identifier format. 834 ''; 835 }; 836 groupAttribute = mkOption { 837 type = types.str; 838 default = ""; 839 example = "memberOf"; 840 description = '' 841 Optional attribute name for group list. 842 ''; 843 }; 844 externalGroups = mkOption { 845 type = types.listOf types.str; 846 default = []; 847 example = [ "Temporary-staff" "External-users" ]; 848 description = '' 849 Excluded group names. 850 ''; 851 }; 852 requiredGroups = mkOption { 853 type = types.listOf types.str; 854 default = []; 855 example = [ "Hedgedoc-Users" ]; 856 description = '' 857 Required group names. 858 ''; 859 }; 860 attribute = { 861 id = mkOption { 862 type = types.str; 863 default = ""; 864 description = '' 865 Attribute map for `id'. 866 Defaults to `NameID' of SAML response. 867 ''; 868 }; 869 username = mkOption { 870 type = types.str; 871 default = ""; 872 description = '' 873 Attribute map for `username'. 874 Defaults to `NameID' of SAML response. 875 ''; 876 }; 877 email = mkOption { 878 type = types.str; 879 default = ""; 880 description = '' 881 Attribute map for `email'. 882 Defaults to `NameID' of SAML response if 883 <option>identifierFormat</option> has 884 the default value. 885 ''; 886 }; 887 }; 888 }; 889 }); 890 default = null; 891 description = "Configure the SAML integration."; 892 }; 893 }; 894 895 environmentFile = mkOption { 896 type = with types; nullOr path; 897 default = null; 898 example = "/var/lib/hedgedoc/hedgedoc.env"; 899 description = '' 900 Environment file as defined in <citerefentry> 901 <refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum> 902 </citerefentry>. 903 904 Secrets may be passed to the service without adding them to the world-readable 905 Nix store, by specifying placeholder variables as the option value in Nix and 906 setting these variables accordingly in the environment file. 907 908 <programlisting> 909 # snippet of HedgeDoc-related config 910 services.hedgedoc.configuration.dbURL = "postgres://hedgedoc:\''${DB_PASSWORD}@db-host:5432/hedgedocdb"; 911 services.hedgedoc.configuration.minio.secretKey = "$MINIO_SECRET_KEY"; 912 </programlisting> 913 914 <programlisting> 915 # content of the environment file 916 DB_PASSWORD=verysecretdbpassword 917 MINIO_SECRET_KEY=verysecretminiokey 918 </programlisting> 919 920 Note that this file needs to be available on the host on which 921 <literal>HedgeDoc</literal> is running. 922 ''; 923 }; 924 925 package = mkOption { 926 type = types.package; 927 default = pkgs.hedgedoc; 928 description = '' 929 Package that provides HedgeDoc. 930 ''; 931 }; 932 }; 933 934 config = mkIf cfg.enable { 935 assertions = [ 936 { assertion = cfg.configuration.db == {} -> ( 937 cfg.configuration.dbURL != "" && cfg.configuration.dbURL != null 938 ); 939 message = "Database configuration for HedgeDoc missing."; } 940 ]; 941 users.groups.${name} = {}; 942 users.users.${name} = { 943 description = "HedgeDoc service user"; 944 group = name; 945 extraGroups = cfg.groups; 946 home = cfg.workDir; 947 createHome = true; 948 isSystemUser = true; 949 }; 950 951 systemd.services.hedgedoc = { 952 description = "HedgeDoc Service"; 953 wantedBy = [ "multi-user.target" ]; 954 after = [ "networking.target" ]; 955 preStart = '' 956 ${pkgs.envsubst}/bin/envsubst \ 957 -o ${cfg.workDir}/config.json \ 958 -i ${prettyJSON cfg.configuration} 959 ''; 960 serviceConfig = { 961 WorkingDirectory = cfg.workDir; 962 ExecStart = "${cfg.package}/bin/hedgedoc"; 963 EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ]; 964 Environment = [ 965 "CMD_CONFIG_FILE=${cfg.workDir}/config.json" 966 "NODE_ENV=production" 967 ]; 968 Restart = "always"; 969 User = name; 970 PrivateTmp = true; 971 }; 972 }; 973 }; 974}