From 4e83773e21f66f049418104dd60c1cb3359d437f Mon Sep 17 00:00:00 2001 From: vi Date: Mon, 1 Dec 2025 00:00:00 +0000 Subject: [PATCH] init git-daemon module --- inventories/default.nix | 48 +++++++ modules/clan/git-daemon/default.nix | 172 +++++++++++++++++++++++ modules/clan/git-daemon/flake-module.nix | 9 ++ 3 files changed, 229 insertions(+) create mode 100644 modules/clan/git-daemon/default.nix create mode 100644 modules/clan/git-daemon/flake-module.nix diff --git a/inventories/default.nix b/inventories/default.nix index e5281c9..651086d 100644 --- a/inventories/default.nix +++ b/inventories/default.nix @@ -200,6 +200,54 @@ }; }; }; + git-daemon = { + module = { + name = "git-daemon"; + input = "self"; + }; + roles.default.machines.neptune = { + settings.repositories = + let + defaults = rec { + write-access = [ + "10.0.0.0/24" + "200:d7b1:c5d5:ea7:27ad:6837:40f6:404d/128" + ]; + read-access = write-access; + }; + PUBLIC = { + read-access = [ + "10.0.0.0/24" + "0200::/7" + ]; + }; + in + builtins.mapAttrs (_: override: defaults // override) { + "9e" = PUBLIC; + archive-dl = { }; + barrytown = { }; + cleanroom = PUBLIC; + community-memory = { }; + eris = { }; + ftdi-sd-spi = { }; + go-go-gadget = { }; + hacking-the-kindle = { }; + islands = { }; + kt = { }; + legba = { }; + llb = PUBLIC; + llc = PUBLIC; + lora = { }; + mute = { }; + navi = { }; + notmuch-memoryhole = PUBLIC; + pms5003 = { }; + thinc = { }; + toad = { }; + yggdrasil-erlang = { }; + }; + }; + }; }; }; }; diff --git a/modules/clan/git-daemon/default.nix b/modules/clan/git-daemon/default.nix new file mode 100644 index 0000000..b5071a6 --- /dev/null +++ b/modules/clan/git-daemon/default.nix @@ -0,0 +1,172 @@ +{ ... }: +{ + _class = "clan.service"; + manifest.name = "git-daemon"; + manifest.description = "a really simple server for git repositories"; + manifest.categories = [ "System" ]; + + roles.default = { + 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 ]; + }; + }; + }; +} diff --git a/modules/clan/git-daemon/flake-module.nix b/modules/clan/git-daemon/flake-module.nix new file mode 100644 index 0000000..199f279 --- /dev/null +++ b/modules/clan/git-daemon/flake-module.nix @@ -0,0 +1,9 @@ +{ lib, ... }: +let + module = lib.modules.importApply ./default.nix { }; +in +{ + clan.modules = { + git-daemon = module; + }; +}