173 lines
5.9 KiB
Nix
173 lines
5.9 KiB
Nix
{ ... }:
|
|
{
|
|
_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 ];
|
|
};
|
|
};
|
|
};
|
|
}
|