at 22.05-pre 32 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 = 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}