at 17.09-beta 25 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.gitit; 8 9 homeDir = "/var/lib/gitit"; 10 11 toYesNo = b: if b then "yes" else "no"; 12 13 gititShared = with cfg.haskellPackages; gitit + "/share/" + pkgs.stdenv.system + "-" + ghc.name + "/" + gitit.pname + "-" + gitit.version; 14 15 gititWithPkgs = hsPkgs: extras: hsPkgs.ghcWithPackages (self: with self; [ gitit ] ++ (extras self)); 16 17 gititSh = hsPkgs: extras: with pkgs; let 18 env = gititWithPkgs hsPkgs extras; 19 in writeScript "gitit" '' 20 #!${stdenv.shell} 21 cd $HOME 22 export NIX_GHC="${env}/bin/ghc" 23 export NIX_GHCPKG="${env}/bin/ghc-pkg" 24 export NIX_GHC_DOCDIR="${env}/share/doc/ghc/html" 25 export NIX_GHC_LIBDIR=$( $NIX_GHC --print-libdir ) 26 ${env}/bin/gitit -f ${configFile} 27 ''; 28 29 gititOptions = { 30 31 enable = mkOption { 32 type = types.bool; 33 default = false; 34 description = "Enable the gitit service."; 35 }; 36 37 haskellPackages = mkOption { 38 default = pkgs.haskellPackages; 39 defaultText = "pkgs.haskellPackages"; 40 example = literalExample "pkgs.haskell.packages.ghc784"; 41 description = "haskellPackages used to build gitit and plugins."; 42 }; 43 44 extraPackages = mkOption { 45 default = self: []; 46 example = literalExample '' 47 haskellPackages: [ 48 haskellPackages.wreq 49 ] 50 ''; 51 description = '' 52 Extra packages available to ghc when running gitit. The 53 value must be a function which receives the attrset defined 54 in <varname>haskellPackages</varname> as the sole argument. 55 ''; 56 }; 57 58 address = mkOption { 59 type = types.str; 60 default = "0.0.0.0"; 61 description = "IP address on which the web server will listen."; 62 }; 63 64 port = mkOption { 65 type = types.int; 66 default = 5001; 67 description = "Port on which the web server will run."; 68 }; 69 70 wikiTitle = mkOption { 71 type = types.str; 72 default = "Gitit!"; 73 description = "The wiki title."; 74 }; 75 76 repositoryType = mkOption { 77 type = types.enum ["git" "darcs" "mercurial"]; 78 default = "git"; 79 description = "Specifies the type of repository used for wiki content."; 80 }; 81 82 repositoryPath = mkOption { 83 type = types.path; 84 default = homeDir + "/wiki"; 85 description = '' 86 Specifies the path of the repository directory. If it does not 87 exist, gitit will create it on startup. 88 ''; 89 }; 90 91 requireAuthentication = mkOption { 92 type = types.enum [ "none" "modify" "read" ]; 93 default = "modify"; 94 description = '' 95 If 'none', login is never required, and pages can be edited 96 anonymously. If 'modify', login is required to modify the wiki 97 (edit, add, delete pages, upload files). If 'read', login is 98 required to see any wiki pages. 99 ''; 100 }; 101 102 authenticationMethod = mkOption { 103 type = types.enum [ "form" "http" "generic" "github" ]; 104 default = "form"; 105 description = '' 106 'form' means that users will be logged in and registered using forms 107 in the gitit web interface. 'http' means that gitit will assume that 108 HTTP authentication is in place and take the logged in username from 109 the "Authorization" field of the HTTP request header (in addition, 110 the login/logout and registration links will be suppressed). 111 'generic' means that gitit will assume that some form of 112 authentication is in place that directly sets REMOTE_USER to the name 113 of the authenticated user (e.g. mod_auth_cas on apache). 'rpx' means 114 that gitit will attempt to log in through https://rpxnow.com. This 115 requires that 'rpx-domain', 'rpx-key', and 'base-url' be set below, 116 and that 'curl' be in the system path. 117 ''; 118 }; 119 120 userFile = mkOption { 121 type = types.path; 122 default = homeDir + "/gitit-users"; 123 description = '' 124 Specifies the path of the file containing user login information. If 125 it does not exist, gitit will create it (with an empty user list). 126 This file is not used if 'http' is selected for 127 authentication-method. 128 ''; 129 }; 130 131 sessionTimeout = mkOption { 132 type = types.int; 133 default = 60; 134 description = '' 135 Number of minutes of inactivity before a session expires. 136 ''; 137 }; 138 139 staticDir = mkOption { 140 type = types.path; 141 default = gititShared + "/data/static"; 142 description = '' 143 Specifies the path of the static directory (containing javascript, 144 css, and images). If it does not exist, gitit will create it and 145 populate it with required scripts, stylesheets, and images. 146 ''; 147 }; 148 149 defaultPageType = mkOption { 150 type = types.enum [ "markdown" "rst" "latex" "html" "markdown+lhs" "rst+lhs" "latex+lhs" ]; 151 default = "markdown"; 152 description = '' 153 Specifies the type of markup used to interpret pages in the wiki. 154 Possible values are markdown, rst, latex, html, markdown+lhs, 155 rst+lhs, and latex+lhs. (the +lhs variants treat the input as 156 literate Haskell. See pandoc's documentation for more details.) If 157 Markdown is selected, pandoc's syntax extensions (for footnotes, 158 delimited code blocks, etc.) will be enabled. Note that pandoc's 159 restructuredtext parser is not complete, so some pages may not be 160 rendered correctly if rst is selected. The same goes for latex and 161 html. 162 ''; 163 }; 164 165 math = mkOption { 166 type = types.enum [ "mathml" "raw" "mathjax" "jsmath" "google" ]; 167 default = "mathml"; 168 description = '' 169 Specifies how LaTeX math is to be displayed. Possible values are 170 mathml, raw, mathjax, jsmath, and google. If mathml is selected, 171 gitit will convert LaTeX math to MathML and link in a script, 172 MathMLinHTML.js, that allows the MathML to be seen in Gecko browsers, 173 IE + mathplayer, and Opera. In other browsers you may get a jumble of 174 characters. If raw is selected, the LaTeX math will be displayed as 175 raw LaTeX math. If mathjax is selected, gitit will link to the 176 remote mathjax script. If jsMath is selected, gitit will link to the 177 script /js/jsMath/easy/load.js, and will assume that jsMath has been 178 installed into the js/jsMath directory. This is the most portable 179 solution. If google is selected, the google chart API is called to 180 render the formula as an image. This requires a connection to google, 181 and might raise a technical or a privacy problem. 182 ''; 183 }; 184 185 mathJaxScript = mkOption { 186 type = types.str; 187 default = "https://d3eoax9i5htok0.cloudfront.net/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"; 188 description = '' 189 Specifies the path to MathJax rendering script. You might want to 190 use your own MathJax script to render formulas without Internet 191 connection or if you want to use some special LaTeX packages. Note: 192 path specified there cannot be an absolute path to a script on your 193 hdd, instead you should run your (local if you wish) HTTP server 194 which will serve the MathJax.js script. You can easily (in four lines 195 of code) serve MathJax.js using 196 http://happstack.com/docs/crashcourse/FileServing.html Do not forget 197 the "http://" prefix (e.g. http://localhost:1234/MathJax.js). 198 ''; 199 }; 200 201 showLhsBirdTracks = mkOption { 202 type = types.bool; 203 default = false; 204 description = '' 205 Specifies whether to show Haskell code blocks in "bird style", with 206 "> " at the beginning of each line. 207 ''; 208 }; 209 210 templatesDir = mkOption { 211 type = types.path; 212 default = gititShared + "/data/templates"; 213 description = '' 214 Specifies the path of the directory containing page templates. If it 215 does not exist, gitit will create it with default templates. Users 216 may wish to edit the templates to customize the appearance of their 217 wiki. The template files are HStringTemplate templates. Variables to 218 be interpolated appear between $\'s. Literal $\'s must be 219 backslash-escaped. 220 ''; 221 }; 222 223 logFile = mkOption { 224 type = types.path; 225 default = homeDir + "/gitit.log"; 226 description = '' 227 Specifies the path of gitit's log file. If it does not exist, gitit 228 will create it. The log is in Apache combined log format. 229 ''; 230 }; 231 232 logLevel = mkOption { 233 type = types.enum [ "DEBUG" "INFO" "NOTICE" "WARNING" "ERROR" "CRITICAL" "ALERT" "EMERGENCY" ]; 234 default = "ERROR"; 235 description = '' 236 Determines how much information is logged. Possible values (from 237 most to least verbose) are DEBUG, INFO, NOTICE, WARNING, ERROR, 238 CRITICAL, ALERT, EMERGENCY. 239 ''; 240 }; 241 242 frontPage = mkOption { 243 type = types.str; 244 default = "Front Page"; 245 description = '' 246 Specifies which wiki page is to be used as the wiki's front page. 247 Gitit creates a default front page on startup, if one does not exist 248 already. 249 ''; 250 }; 251 252 noDelete = mkOption { 253 type = types.str; 254 default = "Front Page, Help"; 255 description = '' 256 Specifies pages that cannot be deleted through the web interface. 257 (They can still be deleted directly using git or darcs.) A 258 comma-separated list of page names. Leave blank to allow every page 259 to be deleted. 260 ''; 261 }; 262 263 noEdit = mkOption { 264 type = types.str; 265 default = "Help"; 266 description = '' 267 Specifies pages that cannot be edited through the web interface. 268 Leave blank to allow every page to be edited. 269 ''; 270 }; 271 272 defaultSummary = mkOption { 273 type = types.str; 274 default = ""; 275 description = '' 276 Specifies text to be used in the change description if the author 277 leaves the "description" field blank. If default-summary is blank 278 (the default), the author will be required to fill in the description 279 field. 280 ''; 281 }; 282 283 tableOfContents = mkOption { 284 type = types.bool; 285 default = true; 286 description = '' 287 Specifies whether to print a tables of contents (with links to 288 sections) on each wiki page. 289 ''; 290 }; 291 292 plugins = mkOption { 293 type = with types; listOf str; 294 default = [ (gititShared + "/plugins/Dot.hs") ]; 295 description = '' 296 Specifies a list of plugins to load. Plugins may be specified either 297 by their path or by their module name. If the plugin name starts 298 with Gitit.Plugin., gitit will assume that the plugin is an installed 299 module and will not try to find a source file. 300 ''; 301 }; 302 303 useCache = mkOption { 304 type = types.bool; 305 default = false; 306 description = '' 307 Specifies whether to cache rendered pages. Note that if use-feed is 308 selected, feeds will be cached regardless of the value of use-cache. 309 ''; 310 }; 311 312 cacheDir = mkOption { 313 type = types.path; 314 default = homeDir + "/cache"; 315 description = "Path where rendered pages will be cached."; 316 }; 317 318 maxUploadSize = mkOption { 319 type = types.str; 320 default = "1000K"; 321 description = '' 322 Specifies an upper limit on the size (in bytes) of files uploaded 323 through the wiki's web interface. To disable uploads, set this to 324 0K. This will result in the uploads link disappearing and the 325 _upload url becoming inactive. 326 ''; 327 }; 328 329 maxPageSize = mkOption { 330 type = types.str; 331 default = "1000K"; 332 description = "Specifies an upper limit on the size (in bytes) of pages."; 333 }; 334 335 debugMode = mkOption { 336 type = types.bool; 337 default = false; 338 description = "Causes debug information to be logged while gitit is running."; 339 }; 340 341 compressResponses = mkOption { 342 type = types.bool; 343 default = true; 344 description = "Specifies whether HTTP responses should be compressed."; 345 }; 346 347 mimeTypesFile = mkOption { 348 type = types.path; 349 default = "/etc/mime/types.info"; 350 description = '' 351 Specifies the path of a file containing mime type mappings. Each 352 line of the file should contain two fields, separated by whitespace. 353 The first field is the mime type, the second is a file extension. 354 For example: 355<programlisting> 356video/x-ms-wmx wmx 357</programlisting> 358 If the file is not found, some simple defaults will be used. 359 ''; 360 }; 361 362 useReCaptcha = mkOption { 363 type = types.bool; 364 default = false; 365 description = '' 366 If true, causes gitit to use the reCAPTCHA service 367 (http://recaptcha.net) to prevent bots from creating accounts. 368 ''; 369 }; 370 371 reCaptchaPrivateKey = mkOption { 372 type = with types; nullOr str; 373 default = null; 374 description = '' 375 Specifies the private key for the reCAPTCHA service. To get 376 these, you need to create an account at http://recaptcha.net. 377 ''; 378 }; 379 380 reCaptchaPublicKey = mkOption { 381 type = with types; nullOr str; 382 default = null; 383 description = '' 384 Specifies the public key for the reCAPTCHA service. To get 385 these, you need to create an account at http://recaptcha.net. 386 ''; 387 }; 388 389 accessQuestion = mkOption { 390 type = types.str; 391 default = "What is the code given to you by Ms. X?"; 392 description = '' 393 Specifies a question that users must answer when they attempt to 394 create an account 395 ''; 396 }; 397 398 accessQuestionAnswers = mkOption { 399 type = types.str; 400 default = "RED DOG, red dog"; 401 description = '' 402 Specifies a question that users must answer when they attempt to 403 create an account, along with a comma-separated list of acceptable 404 answers. This can be used to institute a rudimentary password for 405 signing up as a user on the wiki, or as an alternative to reCAPTCHA. 406 Example: 407 access-question: What is the code given to you by Ms. X? 408 access-question-answers: RED DOG, red dog 409 ''; 410 }; 411 412 rpxDomain = mkOption { 413 type = with types; nullOr str; 414 default = null; 415 description = '' 416 Specifies the domain and key of your RPX account. The domain is just 417 the prefix of the complete RPX domain, so if your full domain is 418 'https://foo.rpxnow.com/', use 'foo' as the value of rpx-domain. 419 ''; 420 }; 421 422 rpxKey = mkOption { 423 type = with types; nullOr str; 424 default = null; 425 description = "RPX account access key."; 426 }; 427 428 mailCommand = mkOption { 429 type = types.str; 430 default = "sendmail %s"; 431 description = '' 432 Specifies the command to use to send notification emails. '%s' will 433 be replaced by the destination email address. The body of the 434 message will be read from stdin. If this field is left blank, 435 password reset will not be offered. 436 ''; 437 }; 438 439 resetPasswordMessage = mkOption { 440 type = types.lines; 441 default = '' 442 > From: gitit@$hostname$ 443 > To: $useremail$ 444 > Subject: Wiki password reset 445 > 446 > Hello $username$, 447 > 448 > To reset your password, please follow the link below: 449 > http://$hostname$:$port$$resetlink$ 450 > 451 > Regards 452 ''; 453 description = '' 454 Gives the text of the message that will be sent to the user should 455 she want to reset her password, or change other registration info. 456 The lines must be indented, and must begin with '>'. The initial 457 spaces and '> ' will be stripped off. $username$ will be replaced by 458 the user's username, $useremail$ by her email address, $hostname$ by 459 the hostname on which the wiki is running (as returned by the 460 hostname system call), $port$ by the port on which the wiki is 461 running, and $resetlink$ by the relative path of a reset link derived 462 from the user's existing hashed password. If your gitit wiki is being 463 proxied to a location other than the root path of $port$, you should 464 change the link to reflect this: for example, to 465 http://$hostname$/path/to/wiki$resetlink$ or 466 http://gitit.$hostname$$resetlink$ 467 ''; 468 }; 469 470 useFeed = mkOption { 471 type = types.bool; 472 default = false; 473 description = '' 474 Specifies whether an ATOM feed should be enabled (for the site and 475 for individual pages). 476 ''; 477 }; 478 479 baseUrl = mkOption { 480 type = with types; nullOr str; 481 default = null; 482 description = '' 483 The base URL of the wiki, to be used in constructing feed IDs and RPX 484 token_urls. Set this if useFeed is false or authentication-method 485 is 'rpx'. 486 ''; 487 }; 488 489 absoluteUrls = mkOption { 490 type = types.bool; 491 default = false; 492 description = '' 493 Make wikilinks absolute with respect to the base-url. So, for 494 example, in a wiki served at the base URL '/wiki', on a page 495 Sub/Page, the wikilink '[Cactus]()' will produce a link to 496 '/wiki/Cactus' if absoluteUrls is true, and a relative link to 497 'Cactus' (referring to '/wiki/Sub/Cactus') if absolute-urls is 'no'. 498 ''; 499 }; 500 501 feedDays = mkOption { 502 type = types.int; 503 default = 14; 504 description = "Number of days to be included in feeds."; 505 }; 506 507 feedRefreshTime = mkOption { 508 type = types.int; 509 default = 60; 510 description = "Number of minutes to cache feeds before refreshing."; 511 }; 512 513 pdfExport = mkOption { 514 type = types.bool; 515 default = false; 516 description = '' 517 If true, PDF will appear in export options. PDF will be created using 518 pdflatex, which must be installed and in the path. Note that PDF 519 exports create significant additional server load. 520 ''; 521 }; 522 523 pandocUserData = mkOption { 524 type = with types; nullOr path; 525 default = null; 526 description = '' 527 If a directory is specified, this will be searched for pandoc 528 customizations. These can include a templates/ directory for custom 529 templates for various export formats, an S5 directory for custom S5 530 styles, and a reference.odt for ODT exports. If no directory is 531 specified, $HOME/.pandoc will be searched. See pandoc's README for 532 more information. 533 ''; 534 }; 535 536 xssSanitize = mkOption { 537 type = types.bool; 538 default = true; 539 description = '' 540 If true, all HTML (including that produced by pandoc) is filtered 541 through xss-sanitize. Set to no only if you trust all of your users. 542 ''; 543 }; 544 545 oauthClientId = mkOption { 546 type = with types; nullOr str; 547 default = null; 548 description = "OAuth client ID"; 549 }; 550 551 oauthClientSecret = mkOption { 552 type = with types; nullOr str; 553 default = null; 554 description = "OAuth client secret"; 555 }; 556 557 oauthCallback = mkOption { 558 type = with types; nullOr str; 559 default = null; 560 description = "OAuth callback URL"; 561 }; 562 563 oauthAuthorizeEndpoint = mkOption { 564 type = with types; nullOr str; 565 default = null; 566 description = "OAuth authorize endpoint"; 567 }; 568 569 oauthAccessTokenEndpoint = mkOption { 570 type = with types; nullOr str; 571 default = null; 572 description = "OAuth access token endpoint"; 573 }; 574 575 githubOrg = mkOption { 576 type = with types; nullOr str; 577 default = null; 578 description = "Github organization"; 579 }; 580 }; 581 582 configFile = pkgs.writeText "gitit.conf" '' 583 address: ${cfg.address} 584 port: ${toString cfg.port} 585 wiki-title: ${cfg.wikiTitle} 586 repository-type: ${cfg.repositoryType} 587 repository-path: ${cfg.repositoryPath} 588 require-authentication: ${cfg.requireAuthentication} 589 authentication-method: ${cfg.authenticationMethod} 590 user-file: ${cfg.userFile} 591 session-timeout: ${toString cfg.sessionTimeout} 592 static-dir: ${cfg.staticDir} 593 default-page-type: ${cfg.defaultPageType} 594 math: ${cfg.math} 595 mathjax-script: ${cfg.mathJaxScript} 596 show-lhs-bird-tracks: ${toYesNo cfg.showLhsBirdTracks} 597 templates-dir: ${cfg.templatesDir} 598 log-file: ${cfg.logFile} 599 log-level: ${cfg.logLevel} 600 front-page: ${cfg.frontPage} 601 no-delete: ${cfg.noDelete} 602 no-edit: ${cfg.noEdit} 603 default-summary: ${cfg.defaultSummary} 604 table-of-contents: ${toYesNo cfg.tableOfContents} 605 plugins: ${concatStringsSep "," cfg.plugins} 606 use-cache: ${toYesNo cfg.useCache} 607 cache-dir: ${cfg.cacheDir} 608 max-upload-size: ${cfg.maxUploadSize} 609 max-page-size: ${cfg.maxPageSize} 610 debug-mode: ${toYesNo cfg.debugMode} 611 compress-responses: ${toYesNo cfg.compressResponses} 612 mime-types-file: ${cfg.mimeTypesFile} 613 use-recaptcha: ${toYesNo cfg.useReCaptcha} 614 recaptcha-private-key: ${toString cfg.reCaptchaPrivateKey} 615 recaptcha-public-key: ${toString cfg.reCaptchaPublicKey} 616 access-question: ${cfg.accessQuestion} 617 access-question-answers: ${cfg.accessQuestionAnswers} 618 rpx-domain: ${toString cfg.rpxDomain} 619 rpx-key: ${toString cfg.rpxKey} 620 mail-command: ${cfg.mailCommand} 621 reset-password-message: ${cfg.resetPasswordMessage} 622 use-feed: ${toYesNo cfg.useFeed} 623 base-url: ${toString cfg.baseUrl} 624 absolute-urls: ${toYesNo cfg.absoluteUrls} 625 feed-days: ${toString cfg.feedDays} 626 feed-refresh-time: ${toString cfg.feedRefreshTime} 627 pdf-export: ${toYesNo cfg.pdfExport} 628 pandoc-user-data: ${toString cfg.pandocUserData} 629 xss-sanitize: ${toYesNo cfg.xssSanitize} 630 631 [Github] 632 oauthclientid: ${toString cfg.oauthClientId} 633 oauthclientsecret: ${toString cfg.oauthClientSecret} 634 oauthcallback: ${toString cfg.oauthCallback} 635 oauthauthorizeendpoint: ${toString cfg.oauthAuthorizeEndpoint} 636 oauthaccesstokenendpoint: ${toString cfg.oauthAccessTokenEndpoint} 637 github-org: ${toString cfg.githubOrg} 638 ''; 639 640in 641 642{ 643 644 options.services.gitit = gititOptions; 645 646 config = mkIf cfg.enable { 647 648 users.extraUsers.gitit = { 649 group = config.users.extraGroups.gitit.name; 650 description = "Gitit user"; 651 home = homeDir; 652 createHome = true; 653 uid = config.ids.uids.gitit; 654 }; 655 656 users.extraGroups.gitit.gid = config.ids.gids.gitit; 657 658 systemd.services.gitit = let 659 uid = toString config.ids.uids.gitit; 660 gid = toString config.ids.gids.gitit; 661 in { 662 description = "Git and Pandoc Powered Wiki"; 663 after = [ "network.target" ]; 664 wantedBy = [ "multi-user.target" ]; 665 path = with pkgs; [ curl ] 666 ++ optional cfg.pdfExport texlive.combined.scheme-basic 667 ++ optional (cfg.repositoryType == "darcs") darcs 668 ++ optional (cfg.repositoryType == "mercurial") mercurial 669 ++ optional (cfg.repositoryType == "git") git; 670 671 preStart = let 672 gm = "gitit@${config.networking.hostName}"; 673 in 674 with cfg; '' 675 chown ${uid}:${gid} -R ${homeDir} 676 for dir in ${repositoryPath} ${staticDir} ${templatesDir} ${cacheDir} 677 do 678 if [ ! -d $dir ] 679 then 680 mkdir -p $dir 681 find $dir -type d -exec chmod 0750 {} + 682 find $dir -type f -exec chmod 0640 {} + 683 fi 684 done 685 cd ${repositoryPath} 686 ${ 687 if repositoryType == "darcs" then 688 '' 689 if [ ! -d _darcs ] 690 then 691 ${pkgs.darcs}/bin/darcs initialize 692 echo "${gm}" > _darcs/prefs/email 693 '' 694 else if repositoryType == "mercurial" then 695 '' 696 if [ ! -d .hg ] 697 then 698 ${pkgs.mercurial}/bin/hg init 699 cat >> .hg/hgrc <<NAMED 700[ui] 701username = gitit ${gm} 702NAMED 703 '' 704 else 705 '' 706 if [ ! -d .git ] 707 then 708 ${pkgs.git}/bin/git init 709 ${pkgs.git}/bin/git config user.email "${gm}" 710 ${pkgs.git}/bin/git config user.name "gitit" 711 ''} 712 chown ${uid}:${gid} -R ${repositoryPath} 713 fi 714 cd - 715 ''; 716 717 serviceConfig = { 718 User = config.users.extraUsers.gitit.name; 719 Group = config.users.extraGroups.gitit.name; 720 ExecStart = with cfg; gititSh haskellPackages extraPackages; 721 }; 722 }; 723 }; 724}