aboutsummaryrefslogtreecommitdiff
path: root/nixos/hosts
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2025-12-29 15:22:46 +0100
committerMartin Fischer <martin@push-f.com>2025-12-29 19:35:28 +0100
commit5d61f19ac636ce99a1291871e4ad3551edcb83df (patch)
tree5cb99477ecc64ba1a11730baa80dbefd55008b66 /nixos/hosts
parent928cf7b7f979270f270f315e0e2f17702c870d88 (diff)
refactor(tente): vendor cgit nixos module
Diffstat (limited to 'nixos/hosts')
-rw-r--r--nixos/hosts/tente/cgit.nix287
-rw-r--r--nixos/hosts/tente/git-web.nix3
2 files changed, 290 insertions, 0 deletions
diff --git a/nixos/hosts/tente/cgit.nix b/nixos/hosts/tente/cgit.nix
new file mode 100644
index 0000000..70ea678
--- /dev/null
+++ b/nixos/hosts/tente/cgit.nix
@@ -0,0 +1,287 @@
+# copied from nixpkgs
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+let
+ cfgs = config.services.cgit;
+
+ settingType =
+ with lib.types;
+ oneOf [
+ bool
+ int
+ str
+ ];
+ repeatedSettingType =
+ with lib.types;
+ oneOf [
+ settingType
+ (listOf settingType)
+ ];
+
+ genAttrs' = names: f: lib.listToAttrs (map f names);
+
+ regexEscape =
+ let
+ # taken from https://github.com/python/cpython/blob/05cb728d68a278d11466f9a6c8258d914135c96c/Lib/re.py#L251-L266
+ special = [
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+ "?"
+ "*"
+ "+"
+ "-"
+ "|"
+ "^"
+ "$"
+ "\\"
+ "."
+ "&"
+ "~"
+ "#"
+ " "
+ "\t"
+ "\n"
+ "\r"
+ " " # \v / 0x0B
+ " " # \f / 0x0C
+ ];
+ in
+ lib.replaceStrings special (map (c: "\\${c}") special);
+
+ stripLocation = cfg: lib.removeSuffix "/" cfg.nginx.location;
+
+ regexLocation = cfg: regexEscape (stripLocation cfg);
+
+ mkFastcgiPass = name: cfg: ''
+ ${
+ if cfg.nginx.location == "/" then
+ ''
+ fastcgi_param PATH_INFO $uri;
+ ''
+ else
+ ''
+ fastcgi_split_path_info ^(${regexLocation cfg})(/.+)$;
+ fastcgi_param PATH_INFO $fastcgi_path_info;
+ ''
+ }fastcgi_pass unix:${config.services.fcgiwrap.instances."cgit-${name}".socket.address};
+ '';
+
+ cgitrcLine =
+ name: value:
+ "${name}=${
+ if value == true then
+ "1"
+ else if value == false then
+ "0"
+ else
+ toString value
+ }";
+
+ # list value as multiple lines (for "readme" for example)
+ cgitrcEntry =
+ name: value: if lib.isList value then map (cgitrcLine name) value else [ (cgitrcLine name value) ];
+
+ mkCgitrc =
+ cfg:
+ pkgs.writeText "cgitrc" ''
+ # global settings
+ ${lib.concatStringsSep "\n" (
+ lib.flatten (
+ lib.mapAttrsToList cgitrcEntry ({ virtual-root = cfg.nginx.location; } // cfg.settings)
+ )
+ )}
+ ${lib.optionalString (cfg.scanPath != null) (cgitrcLine "scan-path" cfg.scanPath)}
+
+ # repository settings
+ ${lib.concatStrings (
+ lib.mapAttrsToList (url: settings: ''
+ ${cgitrcLine "repo.url" url}
+ ${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: cgitrcLine "repo.${name}") settings)}
+ '') cfg.repos
+ )}
+
+ # extra config
+ ${cfg.extraConfig}
+ '';
+
+ fcgiwrapUnitName = name: "fcgiwrap-cgit-${name}";
+ fcgiwrapRuntimeDir = name: "/run/${fcgiwrapUnitName name}";
+ gitProjectRoot =
+ name: cfg: if cfg.scanPath != null then cfg.scanPath else "${fcgiwrapRuntimeDir name}/repos";
+
+in
+{
+ options = {
+ services.cgit = lib.mkOption {
+ description = "Configure cgit instances.";
+ default = { };
+ type = lib.types.attrsOf (
+ lib.types.submodule (
+ { config, ... }:
+ {
+ options = {
+ enable = lib.mkEnableOption "cgit";
+
+ package = lib.mkPackageOption pkgs "cgit" { };
+
+ nginx.virtualHost = lib.mkOption {
+ description = "VirtualHost to serve cgit on, defaults to the attribute name.";
+ type = lib.types.str;
+ default = config._module.args.name;
+ example = "git.example.com";
+ };
+
+ nginx.location = lib.mkOption {
+ description = "Location to serve cgit under.";
+ type = lib.types.str;
+ default = "/";
+ example = "/git/";
+ };
+
+ repos = lib.mkOption {
+ description = "cgit repository settings, see {manpage}`cgitrc(5)`";
+ type = with lib.types; attrsOf (attrsOf settingType);
+ default = { };
+ example = {
+ blah = {
+ path = "/var/lib/git/example";
+ desc = "An example repository";
+ };
+ };
+ };
+
+ scanPath = lib.mkOption {
+ description = "A path which will be scanned for repositories.";
+ type = lib.types.nullOr lib.types.path;
+ default = null;
+ example = "/var/lib/git";
+ };
+
+ settings = lib.mkOption {
+ description = "cgit configuration, see {manpage}`cgitrc(5)`";
+ type = lib.types.attrsOf repeatedSettingType;
+ default = { };
+ example = lib.literalExpression ''
+ {
+ enable-follow-links = true;
+ source-filter = "''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py";
+ }
+ '';
+ };
+
+ extraConfig = lib.mkOption {
+ description = "These lines go to the end of cgitrc verbatim.";
+ type = lib.types.lines;
+ default = "";
+ };
+
+ user = lib.mkOption {
+ description = "User to run the cgit service as.";
+ type = lib.types.str;
+ default = "cgit";
+ };
+
+ group = lib.mkOption {
+ description = "Group to run the cgit service as.";
+ type = lib.types.str;
+ default = "cgit";
+ };
+ };
+ }
+ )
+ );
+ };
+ };
+
+ config = lib.mkIf (lib.any (cfg: cfg.enable) (lib.attrValues cfgs)) {
+ assertions = lib.mapAttrsToList (vhost: cfg: {
+ assertion = !cfg.enable || (cfg.scanPath == null) != (cfg.repos == { });
+ message = "Exactly one of services.cgit.${vhost}.scanPath or services.cgit.${vhost}.repos must be set.";
+ }) cfgs;
+
+ users = lib.mkMerge (
+ lib.flip lib.mapAttrsToList cfgs (
+ _: cfg: {
+ users.${cfg.user} = {
+ isSystemUser = true;
+ inherit (cfg) group;
+ };
+ groups.${cfg.group} = { };
+ }
+ )
+ );
+
+ services.fcgiwrap.instances = lib.flip lib.mapAttrs' cfgs (
+ name: cfg:
+ lib.nameValuePair "cgit-${name}" {
+ process = { inherit (cfg) user group; };
+ socket = { inherit (config.services.nginx) user group; };
+ }
+ );
+
+ systemd.services = lib.flip lib.mapAttrs' cfgs (
+ name: cfg:
+ lib.nameValuePair (fcgiwrapUnitName name) (
+ lib.mkIf (cfg.repos != { }) {
+ serviceConfig.RuntimeDirectory = fcgiwrapUnitName name;
+ preStart = ''
+ GIT_PROJECT_ROOT=${lib.escapeShellArg (gitProjectRoot name cfg)}
+ mkdir -p "$GIT_PROJECT_ROOT"
+ cd "$GIT_PROJECT_ROOT"
+ ${lib.concatLines (
+ lib.flip lib.mapAttrsToList cfg.repos (
+ name: repo: ''
+ ln -s ${lib.escapeShellArg repo.path} ${lib.escapeShellArg name}
+ ''
+ )
+ )}
+ '';
+ }
+ )
+ );
+
+ services.nginx.enable = true;
+
+ services.nginx.virtualHosts = lib.mkMerge (
+ lib.mapAttrsToList (name: cfg: {
+ ${cfg.nginx.virtualHost} = {
+ locations =
+ (genAttrs' [ "cgit.css" "cgit.png" "favicon.ico" "robots.txt" ] (
+ fileName:
+ lib.nameValuePair "= ${stripLocation cfg}/${fileName}" {
+ alias = lib.mkDefault "${cfg.package}/cgit/${fileName}";
+ }
+ ))
+ // {
+ "~ ${regexLocation cfg}/.+/(info/refs|git-upload-pack)" = {
+ fastcgiParams = rec {
+ SCRIPT_FILENAME = "${pkgs.git}/libexec/git-core/git-http-backend";
+ GIT_HTTP_EXPORT_ALL = "1";
+ GIT_PROJECT_ROOT = gitProjectRoot name cfg;
+ HOME = GIT_PROJECT_ROOT;
+ };
+ extraConfig = mkFastcgiPass name cfg;
+ };
+ "${stripLocation cfg}/" = {
+ fastcgiParams = {
+ SCRIPT_FILENAME = "${cfg.package}/cgit/cgit.cgi";
+ QUERY_STRING = "$args";
+ HTTP_HOST = "$server_name";
+ CGIT_CONFIG = mkCgitrc cfg;
+ };
+ extraConfig = mkFastcgiPass name cfg;
+ };
+ };
+ };
+ }) cfgs
+ );
+ };
+}
diff --git a/nixos/hosts/tente/git-web.nix b/nixos/hosts/tente/git-web.nix
index 122157a..1c7ccf7 100644
--- a/nixos/hosts/tente/git-web.nix
+++ b/nixos/hosts/tente/git-web.nix
@@ -20,6 +20,9 @@ in
};
};
+ disabledModules = ["services/networking/cgit.nix"];
+ imports = [./cgit.nix];
+
config = {
services.nginx.virtualHosts.${cfg.domain} = {
enableACME = true;