From ed9dc1fd9e1dec564e74f7b95f87f84613c81040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edward=20Tj=C3=B6rnhammar?= Date: Tue, 9 Jun 2015 00:11:24 +0200 Subject: [PATCH] nixos: added gitit service --- nixos/modules/misc/ids.nix | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/misc/gitit.nix | 659 ++++++++++++++++++++++++++ 3 files changed, 662 insertions(+) create mode 100644 nixos/modules/services/misc/gitit.nix diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index e6357d138ca..8a2cde50e1e 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -223,6 +223,7 @@ nix-serve = 199; tvheadend = 200; uwsgi = 201; + gitit = 202; # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399! @@ -424,6 +425,7 @@ #nix-serve = 199; #unused #tvheadend = 200; #unused uwsgi = 201; + gitit = 202; # When adding a gid, make sure it doesn't match an existing # uid. Users and groups with the same name should have equal diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 12a6a9374ff..042824bc829 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -194,6 +194,7 @@ ./services/misc/etcd.nix ./services/misc/felix.nix ./services/misc/folding-at-home.nix + ./services/misc/gitit.nix ./services/misc/gitlab.nix ./services/misc/gitolite.nix ./services/misc/gpsd.nix diff --git a/nixos/modules/services/misc/gitit.nix b/nixos/modules/services/misc/gitit.nix new file mode 100644 index 00000000000..56e735d7356 --- /dev/null +++ b/nixos/modules/services/misc/gitit.nix @@ -0,0 +1,659 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.gitit; + + homeDir = "/var/lib/gitit"; + + gititShared = with cfg.haskellPackages; gitit + "/share/" + pkgs.stdenv.system + "-" + ghc.name + "/" + gitit.pname + "-" + gitit.version; + + gititWithPkgs = hsPkgs: extras: hsPkgs.ghcWithPackages (self: with self; [ gitit ] ++ (extras self)); + + gititSh = hsPkgs: extras: with pkgs; let + env = gititWithPkgs hsPkgs extras; + in writeScript "gitit" '' + #!${stdenv.shell} + cd $HOME + export PATH="${makeSearchPath "bin" ( + [ git curl ] ++ (if cfg.pdfExport == "yes" then [texLiveFull] else []) + )}:$PATH"; + export NIX_GHC="${env}/bin/ghc" + export NIX_GHCPKG="${env}/bin/ghc-pkg" + export NIX_GHC_DOCDIR="${env}/share/doc/ghc/html" + export NIX_GHC_LIBDIR=$( $NIX_GHC --print-libdir ) + ${env}/bin/gitit -f ${configFile} + ''; + + gititOptions = let + + yesNo = types.enum [ "yes" "no" ]; + + in { + + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the gitit service."; + }; + + haskellPackages = mkOption { + default = pkgs.haskellPackages; + defaultText = "pkgs.haskellPackages"; + example = literalExample "pkgs.haskell.packages.ghc784"; + description = "haskellPackages used to build gitit and plugins."; + }; + + extraPackages = mkOption { + default = self: []; + example = literalExample '' + haskellPackages: [ + haskellPackages.wreq + ] + ''; + description = '' + Extra packages available to ghc when running gitit. The + value must be a function which receives the attrset defined + in haskellPackages as the sole argument. + ''; + }; + + address = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "IP address on which the web server will listen."; + }; + + port = mkOption { + type = types.int; + default = 5001; + description = "Port on which the web server will run."; + }; + + wikiTitle = mkOption { + type = types.str; + default = "Gitit!"; + description = "The wiki title."; + }; + + repositoryType = mkOption { + type = types.enum ["git" "darcs" "mercurial"]; + default = "git"; + description = "Specifies the type of repository used for wiki content."; + }; + + repositoryPath = mkOption { + type = types.path; + default = homeDir + "/wiki"; + description = '' + Specifies the path of the repository directory. If it does not + exist, gitit will create it on startup. + ''; + }; + + requireAuthentication = mkOption { + type = types.enum [ "none" "modify" "read" ]; + default = "modify"; + description = '' + If 'none', login is never required, and pages can be edited + anonymously. If 'modify', login is required to modify the wiki + (edit, add, delete pages, upload files). If 'read', login is + required to see any wiki pages. + ''; + }; + + authenticationMethod = mkOption { + type = types.enum [ "form" "http" "generic"]; + default = "form"; + description = '' + 'form' means that users will be logged in and registered using forms + in the gitit web interface. 'http' means that gitit will assume that + HTTP authentication is in place and take the logged in username from + the "Authorization" field of the HTTP request header (in addition, + the login/logout and registration links will be suppressed). + 'generic' means that gitit will assume that some form of + authentication is in place that directly sets REMOTE_USER to the name + of the authenticated user (e.g. mod_auth_cas on apache). 'rpx' means + that gitit will attempt to log in through https://rpxnow.com. This + requires that 'rpx-domain', 'rpx-key', and 'base-url' be set below, + and that 'curl' be in the system path. + ''; + }; + + userFile = mkOption { + type = types.path; + default = homeDir + "/gitit-users"; + description = '' + Specifies the path of the file containing user login information. If + it does not exist, gitit will create it (with an empty user list). + This file is not used if 'http' is selected for + authentication-method. + ''; + }; + + sessionTimeout = mkOption { + type = types.int; + default = 60; + description = '' + Number of minutes of inactivity before a session expires. + ''; + }; + + staticDir = mkOption { + type = types.path; + default = gititShared + "/data/static"; + description = '' + Specifies the path of the static directory (containing javascript, + css, and images). If it does not exist, gitit will create it and + populate it with required scripts, stylesheets, and images. + ''; + }; + + defaultPageType = mkOption { + type = types.enum [ "markdown" "rst" "latex" "html" "markdown+lhs" "rst+lhs" "latex+lhs" ]; + default = "markdown"; + description = '' + Specifies the type of markup used to interpret pages in the wiki. + Possible values are markdown, rst, latex, html, markdown+lhs, + rst+lhs, and latex+lhs. (the +lhs variants treat the input as + literate Haskell. See pandoc's documentation for more details.) If + Markdown is selected, pandoc's syntax extensions (for footnotes, + delimited code blocks, etc.) will be enabled. Note that pandoc's + restructuredtext parser is not complete, so some pages may not be + rendered correctly if rst is selected. The same goes for latex and + html. + ''; + }; + + math = mkOption { + type = types.enum [ "mathml" "raw" "mathjax" "jsmath" "google" ]; + default = "mathml"; + description = '' + Specifies how LaTeX math is to be displayed. Possible values are + mathml, raw, mathjax, jsmath, and google. If mathml is selected, + gitit will convert LaTeX math to MathML and link in a script, + MathMLinHTML.js, that allows the MathML to be seen in Gecko browsers, + IE + mathplayer, and Opera. In other browsers you may get a jumble of + characters. If raw is selected, the LaTeX math will be displayed as + raw LaTeX math. If mathjax is selected, gitit will link to the + remote mathjax script. If jsMath is selected, gitit will link to the + script /js/jsMath/easy/load.js, and will assume that jsMath has been + installed into the js/jsMath directory. This is the most portable + solution. If google is selected, the google chart API is called to + render the formula as an image. This requires a connection to google, + and might raise a technical or a privacy problem. + ''; + }; + + mathJaxScript = mkOption { + type = types.str; + default = "https://d3eoax9i5htok0.cloudfront.net/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"; + description = '' + Specifies the path to MathJax rendering script. You might want to + use your own MathJax script to render formulas without Internet + connection or if you want to use some special LaTeX packages. Note: + path specified there cannot be an absolute path to a script on your + hdd, instead you should run your (local if you wish) HTTP server + which will serve the MathJax.js script. You can easily (in four lines + of code) serve MathJax.js using + http://happstack.com/docs/crashcourse/FileServing.html Do not forget + the "http://" prefix (e.g. http://localhost:1234/MathJax.js). + ''; + }; + + showLhsBirdTracks = mkOption { + type = yesNo; + default = "no"; + description = '' + Specifies whether to show Haskell code blocks in "bird style", with + "> " at the beginning of each line. + ''; + }; + + templatesDir = mkOption { + type = types.path; + default = gititShared + "/data/templates"; + description = '' + Specifies the path of the directory containing page templates. If it + does not exist, gitit will create it with default templates. Users + may wish to edit the templates to customize the appearance of their + wiki. The template files are HStringTemplate templates. Variables to + be interpolated appear between $\'s. Literal $\'s must be + backslash-escaped. + ''; + }; + + logFile = mkOption { + type = types.path; + default = homeDir + "/gitit.log"; + description = '' + Specifies the path of gitit's log file. If it does not exist, gitit + will create it. The log is in Apache combined log format. + ''; + }; + + logLevel = mkOption { + type = types.enum [ "DEBUG" "INFO" "NOTICE" "WARNING" "ERROR" "CRITICAL" "ALERT" "EMERGENCY" ]; + default = "ERROR"; + description = '' + Determines how much information is logged. Possible values (from + most to least verbose) are DEBUG, INFO, NOTICE, WARNING, ERROR, + CRITICAL, ALERT, EMERGENCY. + ''; + }; + + frontPage = mkOption { + type = types.str; + default = "Front Page"; + description = '' + Specifies which wiki page is to be used as the wiki's front page. + Gitit creates a default front page on startup, if one does not exist + already. + ''; + }; + + noDelete = mkOption { + type = types.str; + default = "Front Page, Help"; + description = '' + Specifies pages that cannot be deleted through the web interface. + (They can still be deleted directly using git or darcs.) A + comma-separated list of page names. Leave blank to allow every page + to be deleted. + ''; + }; + + noEdit = mkOption { + type = types.str; + default = "Help"; + description = '' + Specifies pages that cannot be edited through the web interface. + Leave blank to allow every page to be edited. + ''; + }; + + defaultSummary = mkOption { + type = types.str; + default = ""; + description = '' + Specifies text to be used in the change description if the author + leaves the "description" field blank. If default-summary is blank + (the default), the author will be required to fill in the description + field. + ''; + }; + + tableOfContents = mkOption { + type = yesNo; + default = "yes"; + description = '' + Specifies whether to print a tables of contents (with links to + sections) on each wiki page. + ''; + }; + + plugins = mkOption { + type = types.path; + default = gititShared + "/plugins/Dot.hs"; + description = '' + Specifies a list of plugins to load. Plugins may be specified either + by their path or by their module name. If the plugin name starts + with Gitit.Plugin., gitit will assume that the plugin is an installed + module and will not try to find a source file. + Examples: + plugins: plugins/DotPlugin.hs, CapitalizeEmphasisPlugin.hs + plugins: plugins/DotPlugin + plugins: Gitit.Plugin.InterwikiLinks + ''; + }; + + useCache = mkOption { + type = yesNo; + default = "no"; + description = '' + Specifies whether to cache rendered pages. Note that if use-feed is + selected, feeds will be cached regardless of the value of use-cache. + ''; + }; + + cacheDir = mkOption { + type = types.path; + default = homeDir + "/cache"; + description = "Path where rendered pages will be cached."; + }; + + maxUploadSize = mkOption { + type = types.str; + default = "1000K"; + description = '' + Specifies an upper limit on the size (in bytes) of files uploaded + through the wiki's web interface. To disable uploads, set this to + 0K. This will result in the uploads link disappearing and the + _upload url becoming inactive. + ''; + }; + + maxPageSize = mkOption { + type = types.str; + default = "1000K"; + description = "Specifies an upper limit on the size (in bytes) of pages."; + }; + + debugMode = mkOption { + type = yesNo; + default = "no"; + description = "Causes debug information to be logged while gitit is running."; + }; + + compressResponses = mkOption { + type = yesNo; + default = "yes"; + description = "Specifies whether HTTP responses should be compressed."; + }; + + mimeTypesFile = mkOption { + type = types.path; + default = "/etc/mime/types.info"; + description = '' + Specifies the path of a file containing mime type mappings. Each + line of the file should contain two fields, separated by whitespace. + The first field is the mime type, the second is a file extension. + For example: + video/x-ms-wmx wmx + If the file is not found, some simple defaults will be used. + ''; + }; + + useReCaptcha = mkOption { + type = yesNo; + default = "no"; + description = '' + If "yes", causes gitit to use the reCAPTCHA service + (http://recaptcha.net) to prevent bots from creating accounts. + ''; + }; + + reCaptchaPrivateKey = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Specifies the private key for the reCAPTCHA service. To get + these, you need to create an account at http://recaptcha.net. + ''; + }; + + reCaptchaPublicKey = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Specifies the public key for the reCAPTCHA service. To get + these, you need to create an account at http://recaptcha.net. + ''; + }; + + accessQuestion = mkOption { + type = types.str; + default = "What is the code given to you by Ms. X?"; + description = '' + Specifies a question that users must answer when they attempt to + create an account + ''; + }; + + accessQuestionAnswers = mkOption { + type = types.str; + default = "RED DOG, red dog"; + description = '' + Specifies a question that users must answer when they attempt to + create an account, along with a comma-separated list of acceptable + answers. This can be used to institute a rudimentary password for + signing up as a user on the wiki, or as an alternative to reCAPTCHA. + Example: + access-question: What is the code given to you by Ms. X? + access-question-answers: RED DOG, red dog + ''; + }; + + rpxDomain = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Specifies the domain and key of your RPX account. The domain is just + the prefix of the complete RPX domain, so if your full domain is + 'https://foo.rpxnow.com/', use 'foo' as the value of rpx-domain. + ''; + }; + + rpxKey = mkOption { + type = with types; nullOr str; + default = null; + description = "RPX account access key."; + }; + + mailCommand = mkOption { + type = types.str; + default = "sendmail %s"; + description = '' + Specifies the command to use to send notification emails. '%s' will + be replaced by the destination email address. The body of the + message will be read from stdin. If this field is left blank, + password reset will not be offered. + ''; + }; + + resetPasswordMessage = mkOption { + type = types.lines; + default = '' + > From: gitit@$hostname$ + > To: $useremail$ + > Subject: Wiki password reset + > + > Hello $username$, + > + > To reset your password, please follow the link below: + > http://$hostname$:$port$$resetlink$ + > + > Regards + ''; + description = '' + Gives the text of the message that will be sent to the user should + she want to reset her password, or change other registration info. + The lines must be indented, and must begin with '>'. The initial + spaces and '> ' will be stripped off. $username$ will be replaced by + the user's username, $useremail$ by her email address, $hostname$ by + the hostname on which the wiki is running (as returned by the + hostname system call), $port$ by the port on which the wiki is + running, and $resetlink$ by the relative path of a reset link derived + from the user's existing hashed password. If your gitit wiki is being + proxied to a location other than the root path of $port$, you should + change the link to reflect this: for example, to + http://$hostname$/path/to/wiki$resetlink$ or + http://gitit.$hostname$$resetlink$ + ''; + }; + + useFeed = mkOption { + type = yesNo; + default = "no"; + description = '' + Specifies whether an ATOM feed should be enabled (for the site and + for individual pages). + ''; + }; + + baseUrl = mkOption { + type = with types; nullOr str; + default = null; + description = '' + The base URL of the wiki, to be used in constructing feed IDs and RPX + token_urls. Set this if use-feed is 'yes' or authentication-method + is 'rpx'. + ''; + }; + + absoluteUrls = mkOption { + type = yesNo; + default = "no"; + description = '' + Make wikilinks absolute with respect to the base-url. So, for + example, in a wiki served at the base URL '/wiki', on a page + Sub/Page, the wikilink '[Cactus]()' will produce a link to + '/wiki/Cactus' if absolute-urls is 'yes', and a relative link to + 'Cactus' (referring to '/wiki/Sub/Cactus') if absolute-urls is 'no'. + ''; + }; + + feedDays = mkOption { + type = types.int; + default = 14; + description = "Number of days to be included in feeds."; + }; + + feedRefreshTime = mkOption { + type = types.int; + default = 60; + description = "Number of minutes to cache feeds before refreshing."; + }; + + pdfExport = mkOption { + type = yesNo; + default = "no"; + description = '' + If yes, PDF will appear in export options. PDF will be created using + pdflatex, which must be installed and in the path. Note that PDF + exports create significant additional server load. + ''; + }; + + pandocUserData = mkOption { + type = with types; nullOr path; + default = null; + description = '' + If a directory is specified, this will be searched for pandoc + customizations. These can include a templates/ directory for custom + templates for various export formats, an S5 directory for custom S5 + styles, and a reference.odt for ODT exports. If no directory is + specified, $HOME/.pandoc will be searched. See pandoc's README for + more information. + ''; + }; + + xssSanitize = mkOption { + type = yesNo; + default = "yes"; + description = '' + If yes, all HTML (including that produced by pandoc) is filtered + through xss-sanitize. Set to no only if you trust all of your users. + ''; + }; + }; + + configFile = pkgs.writeText "gitit.conf" '' + address: ${cfg.address} + port: ${toString cfg.port} + wiki-title: ${cfg.wikiTitle} + repository-type: ${cfg.repositoryType} + repository-path: ${cfg.repositoryPath} + require-authentication: ${cfg.requireAuthentication} + authentication-method: ${cfg.authenticationMethod} + user-file: ${cfg.userFile} + session-timeout: ${toString cfg.sessionTimeout} + static-dir: ${cfg.staticDir} + default-page-type: ${cfg.defaultPageType} + math: ${cfg.math} + mathjax-script: ${cfg.mathJaxScript} + show-lhs-bird-tracks: ${cfg.showLhsBirdTracks} + templates-dir: ${cfg.templatesDir} + log-file: ${cfg.logFile} + log-level: ${cfg.logLevel} + front-page: ${cfg.frontPage} + no-delete: ${cfg.noDelete} + no-edit: ${cfg.noEdit} + default-summary: ${cfg.defaultSummary} + table-of-contents: ${cfg.tableOfContents} + plugins: ${cfg.plugins} + use-cache: ${cfg.useCache} + cache-dir: ${cfg.cacheDir} + max-upload-size: ${cfg.maxUploadSize} + max-page-size: ${cfg.maxPageSize} + debug-mode: ${cfg.debugMode} + compress-responses: ${cfg.compressResponses} + mime-types-file: ${cfg.mimeTypesFile} + use-recaptcha: ${cfg.useReCaptcha} + recaptcha-private-key: ${toString cfg.reCaptchaPrivateKey} + recaptcha-public-key: ${toString cfg.reCaptchaPublicKey} + access-question: ${cfg.accessQuestion} + access-question-answers: ${cfg.accessQuestionAnswers} + rpx-domain: ${toString cfg.rpxDomain} + rpx-key: ${toString cfg.rpxKey} + mail-command: ${cfg.mailCommand} + reset-password-message: ${cfg.resetPasswordMessage} + use-feed: ${cfg.useFeed} + base-url: ${toString cfg.baseUrl} + absolute-urls: ${cfg.absoluteUrls} + feed-days: ${toString cfg.feedDays} + feed-refresh-time: ${toString cfg.feedRefreshTime} + pdf-export: ${cfg.pdfExport} + pandoc-user-data: ${toString cfg.pandocUserData} + xss-sanitize: ${cfg.xssSanitize} + ''; + +in + +{ + + options.services.gitit = gititOptions; + + config = mkIf cfg.enable { + + users.extraUsers.gitit = { + group = config.users.extraGroups.gitit.name; + description = "Gitit user"; + home = homeDir; + createHome = true; + uid = config.ids.uids.gitit; + }; + + users.extraGroups.gitit.gid = config.ids.gids.gitit; + + systemd.services.gitit = let + uid = toString config.ids.uids.gitit; + gid = toString config.ids.gids.gitit; + in { + description = "Git and Pandoc Powered Wiki"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = with cfg; '' + chown ${uid}:${gid} -R ${homeDir} + for dir in ${repositoryPath} ${staticDir} ${templatesDir} ${cacheDir} + do + if [ ! -d $dir ] + then + mkdir -p $dir + find $dir -type d -exec chmod 0750 {} + + find $dir -type f -exec chmod 0640 {} + + fi + done + cd ${repositoryPath} + if [ ! -d .git ] + then + ${pkgs.git}/bin/git init + ${pkgs.git}/bin/git config user.email "gitit@${config.networking.hostName}" + ${pkgs.git}/bin/git config user.name "gitit" + chown ${uid}:${gid} -R {repositoryPath} + fi + cd - + ''; + + serviceConfig = { + User = config.users.extraUsers.gitit.name; + Group = config.users.extraGroups.gitit.name; + ExecStart = with cfg; gititSh haskellPackages extraPackages; + }; + }; + }; +} +