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