greenhouse

kore

my public infrastructure monorepo

git clone [email protected]:kore.git

kore / nixos / roles / git.nix151 lines · 4573B · raw

1{ inputs, config, lib, pkgs, ... }:
2
3let
4  inherit (config.kore) identities repos;
5  inherit (config.networking) domain;
6  root = "/srv/git";
7
8  petal = pkgs.stdenv.mkDerivation {
9    pname = "petal";
10    version = "0-${inputs.greenhouse.shortRev or "dirty"}";
11
12    src = "${inputs.greenhouse}/petal";
13
14    buildInputs = [ pkgs.libgit2 ];
15
16    makeFlags = [
17      "PREFIX=$(out)"
18      "CC=${pkgs.stdenv.cc.targetPrefix}cc"
19      "GIT2INC=${lib.getDev pkgs.libgit2}/include"
20      "GIT2LIB=${lib.getLib pkgs.libgit2}/lib"
21    ];
22  };
23
24  petalHome = pkgs.writeTextDir ".gitconfig" (lib.generators.toGitINI {
25    safe.directory = map (repo: "${root}/${repo}.git") (lib.attrNames repos);
26  });
27
28  owner = access: if access.owner != null then access.owner else lib.findFirst (_: true) "" access.write;
29
30  acl = pkgs.writeText "kore-git-acl" (lib.concatLines (lib.concatLists (lib.mapAttrsToList (repo: access:
31    map (id: "${repo} write ${id}") access.write
32    ++ map (id: "${repo} read ${id}") access.read
33  ) repos)));
34
35  kore-git = pkgs.writeShellApplication {
36    name = "kore-git";
37    runtimeInputs = [ pkgs.git pkgs.gnugrep ];
38    text = ''
39      id="$1"
40      cmd="''${SSH_ORIGINAL_COMMAND:-}"
41
42      deny() {
43        echo "kore-git: access denied" >&2
44        exit 1
45      }
46
47      verb="''${cmd%% *}"
48      case "$verb" in
49        git-upload-pack | git-upload-archive) perm="read" ;;
50        git-receive-pack) perm="write" ;;
51        *)
52          echo "kore-git: hi $id, shell access is not provided" >&2
53          exit 1
54          ;;
55      esac
56
57      path="''${cmd#"$verb" }"
58      path="''${path#\'}"
59      path="''${path%\'}"
60      path="''${path#/}"
61      path="''${path%/}"
62      path="''${path%.git}"
63
64      [[ "$path" =~ ^[A-Za-z0-9_][A-Za-z0-9._-]*(/[A-Za-z0-9_][A-Za-z0-9._-]*)*$ ]] || deny
65
66      grep -qFx "$path write $id" ${acl} \
67        || { [ "$perm" = read ] && grep -qFx "$path read $id" ${acl}; } \
68        || deny
69
70      exec git-shell -c "$verb '${root}/$path.git'"
71    '';
72  };
73
74  members = lib.unique (lib.concatLists (lib.mapAttrsToList (_: access: access.write ++ access.read) repos));
75in {
76  assertions = [{
77    assertion = lib.elem "web" config.kore.currentHost.roles;
78    message = "kore: the git role needs the web role for petal";
79  }];
80
81  users.groups.git = {};
82  users.users.git = {
83    isSystemUser = true;
84    group = "git";
85    home = root;
86    homeMode = "750";
87    createHome = true;
88    shell = pkgs.bash;
89
90    openssh.authorizedKeys.keys = lib.concatMap (id:
91      map (key: ''command="${kore-git}/bin/kore-git ${id}",restrict ${key}'') (identities.${id}.sshKeys or [])
92    ) members;
93  };
94
95  systemd.services.kore-git-repos = {
96    wantedBy = [ "multi-user.target" ];
97    after = [ "local-fs.target" ];
98    serviceConfig = {
99      Type = "oneshot";
100      User = "git";
101      Group = "git";
102    };
103    path = [ pkgs.git pkgs.coreutils ];
104    script = lib.concatStrings (lib.mapAttrsToList (repo: access: ''
105      if [ ! -d '${root}/${repo}.git' ]; then
106        mkdir -p "$(dirname '${root}/${repo}.git')"
107        git init --quiet --bare --initial-branch=main '${root}/${repo}.git'
108      fi
109      printf '%s\n' ${lib.escapeShellArg access.description} > '${root}/${repo}.git/description'
110      printf '%s\n' ${lib.escapeShellArg (owner access)} > '${root}/${repo}.git/owner'
111    '') repos);
112  };
113
114  systemd.tmpfiles.rules = [ "z ${root} 0750 git git -" ];
115
116  users.users.petal = {
117    isSystemUser = true;
118    group = "git";
119  };
120
121  services.fcgiwrap.instances.petal = {
122    process.user = "petal";
123    process.group = "git";
124    socket.user = config.services.nginx.user;
125    socket.group = config.services.nginx.group;
126  };
127
128  services.nginx.virtualHosts."git.${domain}" = {
129    enableACME = true;
130    forceSSL = true;
131
132    locations."= /style.css".alias = "${petal}/share/petal/style.css";
133
134    locations."/".extraConfig = ''
135      fastcgi_param GATEWAY_INTERFACE CGI/1.1;
136      fastcgi_param SERVER_PROTOCOL $server_protocol;
137      fastcgi_param SERVER_NAME $server_name;
138      fastcgi_param SERVER_PORT $server_port;
139      fastcgi_param REQUEST_METHOD $request_method;
140      fastcgi_param REQUEST_URI $request_uri;
141      fastcgi_param QUERY_STRING $query_string;
142      fastcgi_param REMOTE_ADDR $remote_addr;
143      fastcgi_param HTTPS $https if_not_empty;
144      fastcgi_param SCRIPT_FILENAME ${petal}/bin/petal;
145      fastcgi_param SCRIPT_NAME "";
146      fastcgi_param PATH_INFO $uri;
147      fastcgi_param HOME ${petalHome};
148      fastcgi_pass unix:${config.services.fcgiwrap.instances.petal.socket.address};
149    '';
150  };
151}