{ ... }:
{
  _class = "clan.service";
  manifest.name = "git-daemon";
  manifest.description = "a really simple server for git repositories";
  manifest.readme = "a really simple server for git repositories";
  manifest.categories = [ "System" ];

  roles.default = {
    description = "a default server role";
    interface =
      { lib, ... }:
      {
        options = with lib; {
          directory = lib.mkOption {
            type = types.str;
            default = "/var/git";
          };
          repositories = lib.mkOption {
            type =
              with lib.types;
              attrsOf (
                submodule (
                  { name, ... }:
                  {
                    options = {
                      name = lib.mkOption {
                        type = str;
                        default = name;
                      };
                      read-access = lib.mkOption {
                        type = listOf str;
                        default = [ ];
                      };
                      write-access = lib.mkOption {
                        type = listOf str;
                        default = [ ];
                      };
                    };
                  }
                )
              );
            default = { };
          };
        };
      };
    perInstance =
      {
        settings,
        ...
      }:
      {
        nixosModule =
          {
            pkgs,
            lib,
            config,
            ...
          }:
          {
            systemd.services.git-init = {
              serviceConfig = {
                Type = "oneshot";
                User = config.services.gitDaemon.user;
                Group = config.services.gitDaemon.group;
                ExecStartPre = toString [
                  "+${pkgs.coreutils}/bin/install"
                  "--directory"
                  "--owner=${config.services.gitDaemon.user}"
                  "--group=${config.services.gitDaemon.group}"
                  "--mode=0750"
                  settings.directory
                ];
                ExecStart =
                  let
                    git-template = pkgs.stdenv.mkDerivation {
                      name = "git-template";
                      buildCommand = ''
                        cp --no-preserve=mode,ownership --recursive \
                          ${pkgs.git}/share/git-core/templates $out
                        install -m550 $out/hooks/post-update{.sample,}
                      '';
                    };
                    init-script =
                      { name, ... }:
                      pkgs.writeShellScript "git-init-${name}" ''
                        ${pkgs.git}/bin/git init \
                          --bare --template=${git-template} --shared=0660 \
                          ${settings.directory}/${name}.git
                        ${pkgs.git}/bin/git \
                          -C ${settings.directory}/${name}.git \
                          config set receive.denyNonFastforwards false
                      '';
                  in
                  map init-script (lib.attrValues settings.repositories);
              };
            };

            services.gitDaemon = {
              enable = true;
              user = "git";
              group = "git";
              options =
                let
                  firewall = pkgs.writeText "git-daemon-firewall.json" (
                    builtins.toJSON (builtins.attrValues settings.repositories)
                  );
                  hook = pkgs.writers.writePython3 "hook.py" { flakeIgnore = [ "E" ]; } ''
                    import os, sys, enum, pathlib, ipaddress, json

                    class Service(enum.Enum):
                        UploadPack = enum.auto()
                        ReceivePack = enum.auto()
                        UploadArchive = enum.auto()

                        @classmethod
                        def parse(cls, string):
                            return {
                                'upload-pack': cls.UploadPack,
                                'receive-pack': cls.ReceivePack,
                                'upload-archive': cls.UploadArchive
                            }[string]

                        @property
                        def service(self):
                            return {
                                UploadPack: 'read-access',
                                ReceivePack: 'write-access'
                            }[self]
                    UploadPack = Service.UploadPack
                    ReceivePack = Service.ReceivePack

                    def parse_remote_addr(remote_addr):
                        if remote_addr.startswith('[') and remote_addr.endswith(']'):
                            return ipaddress.ip_address(remote_addr[1:-1])
                        return ipaddress.ip_address(remote_addr)

                    service = Service.parse(sys.argv[1])
                    repo = pathlib.Path(sys.argv[2]).stem
                    client = parse_remote_addr(os.environ['REMOTE_ADDR'])

                    with open("${firewall}", 'r') as f:
                        firewall = json.load(f)

                    for rule in firewall:
                        if rule["name"] == repo:
                           for network in rule[service.service]:
                               if client in ipaddress.ip_network(network):
                                   sys.exit(0)
                    print('stairway denied')
                    sys.exit(1)
                  '';
                in
                toString [
                  "--enable=upload-pack"
                  "--enable=receive-pack"
                  "--disable=upload-archive"
                  "--access-hook=${hook}"
                  "--informative-errors"
                ];
              exportAll = true;
              basePath = settings.directory;
            };

            systemd.services.git-daemon = {
              requires = [ "git-init.service" ];
              after = [ "git-init.service" ];
            };

            networking.firewall.allowedTCPPorts = [ 9418 ];
          };
      };
  };
}
