From 78340b56f70a0d62628dd26363b8cc9810f80898 Mon Sep 17 00:00:00 2001 From: Mats Rauhala Date: Fri, 31 Dec 2021 13:35:22 +0200 Subject: [PATCH] Module for gitit --- flake.lock | 27 +++++++ flake.nix | 54 +++++++++++++ gitit/cabal.nix | 40 ++++++++++ gitit/default.nix | 62 +++++++++++++++ modules/gitit/default.nix | 159 ++++++++++++++++++++++++++++++++++++++ test.nix | 57 ++++++++++++++ 6 files changed, 399 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 gitit/cabal.nix create mode 100644 gitit/default.nix create mode 100644 modules/gitit/default.nix create mode 100644 test.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..f119d53 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1640871638, + "narHash": "sha256-ty6sGnJUQEkCd43At5U3DRQZD7rPARz5VginSW6hZ3k=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5b091d4fbe3b7b7493c3b46fe0842e4b30ea24b3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..efe3573 --- /dev/null +++ b/flake.nix @@ -0,0 +1,54 @@ +{ + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + outputs = { self, nixpkgs }: rec { + overlay = final: prev: { + gitit = prev.callPackage ./gitit.nix {}; + }; + + # Provide module for gitit + nixosModules = builtins.listToAttrs (map (x: { + name = x; + value = import (./modules + "/${x}"); + }) (builtins.attrNames (builtins.readDir ./modules))); + + # Provide the gitit binary. nixpkgs does provide the binary as well, but + # it's built from a version that has a critical bug, login doesn't work + packages.x86_64-linux.gitit = with nixpkgs.legacyPackages.x86_64-linux; + callPackage ./gitit {}; + defaultPackage.x86_64-linux = packages.x86_64-linux.gitit; + + # Nixos integration tests for basic functionality + checks.x86_64-linux.gitit = + nixpkgs.legacyPackages.x86_64-linux.nixosTest ( + import ./test.nix { modules = builtins.attrValues self.nixosModules; } + ); + + # For playing around in a container + # sudo nixos-container create gitit --flake . + nixosConfigurations.container = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = + [ ({ pkgs, ... }: { + imports = builtins.attrValues self.nixosModules ++ []; + boot.isContainer = true; + environment.systemPackages = [ + ]; + + + # Let 'nixos-version --json' know about the Git revision + # of this flake. + system.configurationRevision = nixpkgs.lib.mkIf (self ? rev) self.rev; + + # Network configuration. + networking.useDHCP = false; + networking.firewall.allowedTCPPorts = [ 5001 ]; + + services.gitit = { + enable = true; + }; + }) + ]; + }; + + }; +} diff --git a/gitit/cabal.nix b/gitit/cabal.nix new file mode 100644 index 0000000..b23db89 --- /dev/null +++ b/gitit/cabal.nix @@ -0,0 +1,40 @@ +{ mkDerivation, aeson, base, base64-bytestring, blaze-html +, bytestring, ConfigFile, containers, directory, doctemplates, feed +, fetchgit, filepath, filestore, ghc, ghc-paths, happstack-server +, hoauth2, hslogger, HStringTemplate, HTTP, http-client-tls +, http-conduit, json, lib, mtl, network, network-bsd, network-uri +, old-locale, old-time, pandoc, pandoc-types, parsec, pretty +, process, random, recaptcha, safe, SHA, skylighting, split, syb +, tagsoup, temporary, text, time, uri-bytestring, url, utf8-string +, uuid, xhtml, xml, xml-conduit, xml-types, xss-sanitize, zlib +}: +mkDerivation { + pname = "gitit"; + version = "0.15.1.0"; + src = fetchgit { + url = "https://github.com/jgm/gitit"; + sha256 = "0rzjgi6q338n6cv74438q81v322x2wjrpa7zdvp47z6hrilwpqaa"; + rev = "11a18b034cc49ddaca652ac745fd308405a1fdad"; + fetchSubmodules = true; + }; + isLibrary = true; + isExecutable = true; + enableSeparateDataOutput = true; + libraryHaskellDepends = [ + aeson base base64-bytestring blaze-html bytestring ConfigFile + containers directory doctemplates feed filepath filestore ghc + ghc-paths happstack-server hoauth2 hslogger HStringTemplate HTTP + http-client-tls http-conduit json mtl network network-bsd + network-uri old-locale old-time pandoc pandoc-types parsec pretty + process random recaptcha safe SHA skylighting split syb tagsoup + temporary text time uri-bytestring url utf8-string uuid xhtml xml + xml-conduit xml-types xss-sanitize zlib + ]; + executableHaskellDepends = [ + base bytestring directory filepath hslogger HTTP mtl network + network-uri syb text url utf8-string + ]; + description = "Wiki using happstack, git or darcs, and pandoc"; + license = "GPL"; +} + diff --git a/gitit/default.nix b/gitit/default.nix new file mode 100644 index 0000000..c07639c --- /dev/null +++ b/gitit/default.nix @@ -0,0 +1,62 @@ +{ lib, haskellPackages, haskell, removeReferencesTo +# “Plugins” are a fancy way of saying gitit will invoke +# GHC at *runtime*, which in turn makes it pull GHC +# into its runtime closure. Only enable if you really need +# that feature. But if you do you’ll want to use gitit +# as a library anyway. +, pluginSupport ? false +}: + +# this is similar to what we do with the pandoc executable + +let + plain = haskellPackages.callPackage ./cabal.nix {}; + plugins = + if pluginSupport + then plain + else haskell.lib.compose.disableCabalFlag "plugins" plain; + static = haskell.lib.compose.justStaticExecutables plugins; + +in + (haskell.lib.compose.overrideCabal (drv: { + buildTools = (drv.buildTools or []) ++ [ removeReferencesTo ]; + }) static).overrideAttrs (drv: { + + # These libraries are still referenced, because they generate + # a `Paths_*` module for figuring out their version. + # The `Paths_*` module is generated by Cabal, and contains the + # version, but also paths to e.g. the data directories, which + # lead to a transitive runtime dependency on the whole GHC distribution. + # This should ideally be fixed in haskellPackages (or even Cabal), + # but a minimal gitit is important enough to patch it manually. + disallowedReferences = [ + haskellPackages.pandoc-types + haskellPackages.HTTP + haskellPackages.pandoc + haskellPackages.happstack-server + haskellPackages.filestore + ]; + postInstall = '' + remove-references-to \ + -t ${haskellPackages.pandoc-types} \ + $out/bin/gitit + remove-references-to \ + -t ${haskellPackages.HTTP} \ + $out/bin/gitit + remove-references-to \ + -t ${haskellPackages.pandoc} \ + $out/bin/gitit + remove-references-to \ + -t ${haskellPackages.happstack-server} \ + $out/bin/gitit + remove-references-to \ + -t ${haskellPackages.filestore} \ + $out/bin/gitit + ''; + + meta = drv.meta // { + maintainers = drv.meta.maintainers or [] + ++ [ lib.maintainers.Profpatsch ]; + }; + }) + diff --git a/modules/gitit/default.nix b/modules/gitit/default.nix new file mode 100644 index 0000000..7a14681 --- /dev/null +++ b/modules/gitit/default.nix @@ -0,0 +1,159 @@ +{ config, lib, pkgs, ...}: + +with lib; + +let + + cfg = config.services.gitit; + yesNo = b: if b then "yes" else "no"; + gititConf = with cfg; pkgs.writeText "gitit.conf" '' + address: ${address} + port: ${toString port} + wiki-title: ${wiki-title} + repository-type: Git + repository-path: /var/lib/gitit/wikidata + require-authentication: ${require-authentication} + authentication-method: ${authentication-method} + static-dir: ${toString static-dir} + templates-dir: ${toString templates-dir} + cache-dir: /var/lib/gitit/cache + log-file: /var/lib/gitit/gitit.log + disable-registration: ${yesNo disable-registration} + access-question: ${access-question} + access-question-answers: ${access-question-answers} + ''; + +in + +{ + options.services.gitit = { + enable = mkEnableOption "gitit"; + address = mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "Sets the IP address on which the web server will listen."; + }; + port = mkOption { + type = lib.types.int; + default = 5001; + description = "Sets the port on which the web server will run."; + }; + disable-registration = mkOption { + type = lib.types.bool; + default = false; + description = "If true, disables registering new users on the wiki"; + }; + wiki-title = mkOption { + type = lib.types.str; + default = "Wiki"; + description = "The title of the wiki."; + }; + access-question = mkOption { + type = lib.types.str; + default = ""; + 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 + ''; + }; + access-question-answers = mkOption { + type = lib.types.str; + default = ""; + description = '' + specifies the answer 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 + ''; + }; + require-authentication = mkOption { + type = lib.types.enum ["none" "read" "modify"]; + 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 + ''; + }; + static-dir = mkOption { + type = lib.types.path; + default = "/var/lib/gitit/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. + ''; + }; + templates-dir = mkOption { + type = lib.types.path; + default = "/var/lib/gitit/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 + with 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. + ''; + }; + authentication-method = mkOption { + type = lib.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. + + 'github' means that you are redirected to github website and need to + avail gitit to use your credential from there (github name and email). + Your email is used to identify you when you push your wiki data to git + to identify you as the author + ''; + }; + }; + + config = lib.mkIf cfg.enable { + users.users.gitit = { + home = "/var/lib/gitit"; + createHome = true; + isSystemUser = true; + group = "gitit"; + }; + users.groups.gitit = {}; + systemd.services.gitit = { + description = "Git and Pandoc Powered Wiki"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ curl git ]; + preStart = '' + chown gitit:gitit -R /var/lib/gitit + ''; + serviceConfig = { + User = config.users.users.gitit.name; + Group = config.users.groups.gitit.name; + ExecStart = "${pkgs.gitit}/bin/gitit -f ${gititConf}"; + }; + }; + }; +} + + diff --git a/test.nix b/test.nix new file mode 100644 index 0000000..ee9c0d8 --- /dev/null +++ b/test.nix @@ -0,0 +1,57 @@ +{ modules }: +{ + machine = { config, pkgs, lib, ... }: + { + imports = modules; + + environment.systemPackages = [ + pkgs.curl + ]; + + services.gitit = { + enable = true; + port = 4001; + wiki-title = "Test Wiki"; + address = "127.0.0.1"; + }; + + networking.extraHosts = '' + 127.0.0.1 wiki.local + ''; + + services.nginx = { + enable = true; + virtualHosts."wiki.local" = { + locations."/" = { + proxyPass = "http://127.0.0.1:4001"; + }; + }; + }; + + }; + + + testScript = + let username = "foo"; + password = "foobar123"; + form = "'username=${username}&email=&full_name_1=&password=${password}&password2=${password}&destination=%2F_index®ister=Register'"; + in + '' + start_all() + + machine.wait_for_unit("gitit.service") + machine.wait_for_open_port(4001) + + if "Test Wiki" not in machine.succeed("curl -sS http://localhost:4001"): + raise Exception("Title not set properly") + machine.succeed("curl --data-raw ${form} 'http://localhost:4001/_register?destination=%2F_index'") + + machine.wait_for_unit("nginx.service") + machine.wait_for_open_port(80) + + if "Test Wiki" not in machine.succeed("curl -sS http://wiki.local:80"): + raise Exception("Wiki not available through nginx") + ''; + +} +