Skip to content

Instantly share code, notes, and snippets.

@hmenke
Last active January 21, 2021 21:32
Show Gist options
  • Save hmenke/de2db3e303192a29e4bb6837e0bf48ff to your computer and use it in GitHub Desktop.
Save hmenke/de2db3e303192a29e4bb6837e0bf48ff to your computer and use it in GitHub Desktop.
cgit for nginx on NixOS
{ config, lib, pkgs, ... }:
with lib;
let
globalConfig = config;
settingsFormat = {
type = with lib.types; let
value = oneOf [ int str ] // {
description = "INI-like atom (int or string)";
};
values = coercedTo value lib.singleton (listOf value) // {
description = value.description + " or a list of them for duplicate keys";
};
in
attrsOf (values);
generate = name: values:
pkgs.writeText name (lib.generators.toKeyValue { listsAsDuplicateKeys = true; } values);
};
in
{
options.services.nginx.virtualHosts = mkOption {
type = types.attrsOf (types.submodule ({ config, ... }:
let
cfg = config.cgit;
# These are the global options for this submodule, but for nicer UX they
# are inlined into the freeform settings. Hence they MUST NOT INTERSECT
# with any settings from cgitrc!
options = {
enable = mkEnableOption "cgit";
location = mkOption {
default = "/";
type = types.str;
description = ''
Location to serve cgit on.
'';
};
};
# Remove the global options for serialization into cgitrc
settings = removeAttrs cfg (attrNames options);
in
{
options.cgit = mkOption {
type = types.submodule {
freeformType = settingsFormat.type;
inherit options;
config = {
css = mkDefault "/cgit.css";
logo = mkDefault "/cgit.png";
favicon = mkDefault "/favicon.ico";
};
};
default = { };
example = literalExample ''
{
enable = true;
virtual-root = "/";
source-filter = "''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py";
about-filter = "''${pkgs.cgit}/lib/cgit/filters/about-formatting.sh";
cache-size = 1000;
scan-path = "/srv/git";
include = [
(builtins.toFile "cgitrc-extra-1" '''
# Anything that has to be in a particular order
''')
(builtins.toFile "cgitrc-extra-2" '''
# Anything that has to be in a particular order
''')
];
}
'';
description = ''
Verbatim contents of the cgit runtime configuration file. Documentation
(with cgitrc example file) is available in "man cgitrc". Or online:
http://git.zx2c4.com/cgit/tree/cgitrc.5.txt
'';
};
config = let
location = removeSuffix "/" cfg.location;
in mkIf cfg.enable {
locations."${location}/" = {
root = "${pkgs.cgit}/cgit/";
tryFiles = "$uri @cgit";
};
locations."~ ^${location}/(cgit.(css|png)|favicon.ico|robots.txt)$" = {
alias = "${pkgs.cgit}/cgit/$1";
};
locations."@cgit" = {
extraConfig = ''
include ${pkgs.nginx}/conf/fastcgi_params;
fastcgi_param CGIT_CONFIG ${settingsFormat.generate "cgitrc" settings};
fastcgi_param SCRIPT_FILENAME ${pkgs.cgit}/cgit/cgit.cgi;
fastcgi_param QUERY_STRING $args;
fastcgi_param HTTP_HOST $server_name;
fastcgi_pass unix:${globalConfig.services.fcgiwrap.socketAddress};
'' + (
if cfg.location == "/"
then
''
fastcgi_param PATH_INFO $uri;
''
else
''
fastcgi_split_path_info ^(${location}/)(/?.+)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
''
);
};
};
}));
};
config =
let
vhosts = config.services.nginx.virtualHosts;
in
mkIf (any (name: vhosts.${name}.cgit.enable) (attrNames vhosts)) {
# make the cgitrc manpage available
environment.systemPackages = [ pkgs.cgit ];
services.fcgiwrap.enable = true;
};
meta = {
maintainers = with lib.maintainers; [ afix-space hmenke ];
};
}
# nix-build test.nix -A driver
# ./result/bin/nixos-test-driver
# >>> test_script()
{
"http-clone" = import <nixpkgs/nixos/tests/make-test-python.nix> ({ lib, ... }: {
name = "nginx-cgit-http-clone";
meta = {
maintainers = with lib.maintainers; [ afix-space hmenke ];
};
machine = { pkgs, ... }: {
imports = [
./cgit.nix
];
environment.systemPackages = [ pkgs.git ];
services.nginx.enable = true;
services.nginx.virtualHosts."localhost" = {
cgit = {
enable = true;
virtual-root = "/";
include = [
(builtins.toFile "cgitrc-extra-1" ''
repo.url=test-repo.git
repo.path=/srv/git/test-repo.git
repo.desc=the master foo repository
repo.owner=fooman@example.com
'')
(builtins.toFile "cgitrc-extra-2" ''
# Allow http transport git clone
enable-http-clone=1
'')
];
};
};
};
testScript = ''
# Set up a test repository with only a single file that contains the string
# "Hello World!".
machine.succeed(
"""
git config --global user.name "NixOS Test"
git config --global user.email "NixOS Test"
git init
echo -n "Hello world!" > test.txt
git add test.txt
git commit -am "Test commit"
git init --bare /srv/git/test-repo.git
git push /srv/git/test-repo.git master
"""
)
machine.wait_for_unit("nginx.service")
# Clone the repo on the client through cgit's http clone interface and
# verify that the test file string is correct.
text = machine.succeed(
"""
git clone http://127.0.0.1/test-repo.git /tmp/test-repo
cat /tmp/test-repo/test.txt
"""
)
assert text == "Hello world!", "Defective clone from cgit"
'';
});
"location" = import <nixpkgs/nixos/tests/make-test-python.nix> ({ lib, ... }: {
name = "nginx-cgit-location";
meta = {
maintainers = with lib.maintainers; [ afix-space hmenke ];
};
machine = { pkgs, ... }: {
imports = [
./cgit.nix
];
environment.systemPackages = [ pkgs.git ];
services.nginx.enable = true;
services.nginx.virtualHosts."localhost" = {
cgit = {
enable = true;
location = "/somewhere/else/";
};
};
};
testScript = ''
machine.wait_for_unit("nginx.service")
machine.succeed("curl -fo /dev/null http://127.0.0.1/somewhere/else/cgit.png")
machine.succeed("curl -fo /dev/null http://127.0.0.1/somewhere/else/cgit.css")
machine.succeed("curl -fo /dev/null http://127.0.0.1/somewhere/else/favicon.ico")
machine.succeed("curl -fo /dev/null http://127.0.0.1/somewhere/else/robots.txt")
'';
});
}
@afix-space
Copy link

there is a need to add mkIf line 62, otherwise cgit is always configured :
config = mkIf cfg.enable {

otherwise I am not able to run test with option -p nixos-shell.override.
error: cannot coerce a set to a string, at /nix/store/1lmicz4kfxk407gx0rjq79biwx7j9bp1-nixos-20.09.2624.1eff582e7ea/nixos/pkgs/build-support/trivial-builders.nix:7:7

but I've not yet learned much about this...

Thanks for this gist, it seems to me that this is a good example of best practice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment