{ config, lib, pkgs, ... }: let # Persoenlicher Gitserver, nur im eigenen pinix-Tailnet erreichbar # (docs/intent/gitserver-pinix.md). Weg einer Anfrage: # # Client im pinix-Tailnet # -> fd7a:115c:a1e0:96::1 (Namespace pinix-dns, tailnet-dns.nix) # -> DNAT 443 -> [fd42:7069:6e69:78::1]:8443 (Traefik web-internal) # -> nginx auf 127.0.0.1 -> fcgiwrap -> cgit # # DNS dazu: local-data in unbound-pinix plus Split-DNS in Headscale, beides # in tailnet-dns.nix -- das Gegenstueck zu AdGuard-Rewrite und # traefik/routes-internal.nix auf blunix. host = "git.${config.domain2}"; repoRoot = "/var/lib/git"; nginxPort = 8090; # Aus dem Tailnet-Namespace kommt jede Verbindung mit dieser Quelle an, # weil tailnet-dns.nix beim Weiterleiten auf sie umschreibt. Der git-Key # gilt nur von dort -- ohne eine Zeile an sshd, also ohne Aussperr-Risiko # fuer die uebrigen Nutzer. tailnetPeer = "fd42:7069:6e69:78::2"; # cgit aus nixpkgs ohne seine Python-Filter. Das Paket wickelt # syntax-highlighting.py und md2html mit Python, Pygments und Markdown ein # (und rst2html mit docutils) -- rund 4 Python-Pakete in der Closure, obwohl # hier kein Filter davon benutzt wird. Nur der Build wird angepasst, der # C-Code bleibt unberuehrt. Am 14.09.2026 per `nix-store -qR` gemessen. cgitPackage = pkgs.cgit.overrideAttrs (_: { nativeBuildInputs = [ pkgs.buildPackages.pkg-config pkgs.buildPackages.asciidoc pkgs.buildPackages.makeWrapper ]; pythonPath = [ ]; postInstall = '' make install-man $makeFlags rm $out/lib/cgit/filters/*.py \ $out/lib/cgit/filters/html-converters/md2html \ $out/lib/cgit/filters/html-converters/rst2html for script in $out/lib/cgit/filters/*.sh $out/lib/cgit/filters/html-converters/txt2html; do wrapProgram $script --prefix PATH : '${lib.makeBinPath [ pkgs.coreutils pkgs.gnused ]}' done ''; }); # README als vorformatierter Text statt Markdown-Rendering. readmeFilter = pkgs.writeShellScript "cgit-readme-pre" '' printf '
'
${pkgs.gnused}/bin/sed -e 's/&/\&/g' -e 's/\</g' -e 's/>/\>/g'
printf '\n'
'';
in
{
users.users.git = {
isSystemUser = true;
group = "git";
home = repoRoot;
createHome = true;
# cgit liest ueber die Gruppe, schreiben kann nur git selbst.
homeMode = "750";
shell = "${pkgs.git}/bin/git-shell";
# Kein Passwort, aber nicht gesperrt: sshd laeuft hier ohne PAM und
# wertet das Default-"!" als gesperrtes Konto ("User git not allowed
# because account is locked", gemessen 14.09.2026). "*" erlaubt keinen
# Passwort-Login und laesst den Schluessel-Login zu.
hashedPassword = "*";
openssh.authorizedKeys.keys = [
# lukas (builder@tnix)
''from="${tailnetPeer}",no-pty,no-port-forwarding,no-agent-forwarding,no-X11-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILrZETtzrSfkomL9H31SuyQJ8O39ujMIjsCkbjL4DgxM''
];
};
users.groups.git = { };
services.cgit.${host} = {
enable = true;
package = cgitPackage;
scanPath = repoRoot;
group = "git";
# Klonen laeuft ueber SSH; Herunterladen ueber Snapshots.
gitHttpBackend.enable = false;
settings = {
root-title = host;
root-desc = "Code lesen und herunterladen";
logo = "";
head-include = "${./head.html}";
noplainemail = true;
enable-index-owner = false;
# Muss vor scan-path stehen, das Modul schreibt settings davor.
remove-suffix = true;
readme = [ ":README.md" ":README" ];
about-filter = "${readmeFilter}";
enable-http-clone = false;
clone-url = "git@${host}:$CGIT_REPO_URL.git";
snapshots = "tar.gz zip";
enable-blame = false;
enable-commit-graph = false;
enable-log-filecount = false;
enable-log-linecount = false;
side-by-side-diffs = false;
};
};
# Das Upstream-Modul haengt nginx sonst an Port 80, den Traefik haelt.
services.nginx.virtualHosts.${host} = {
listen = [
{
addr = "127.0.0.1";
port = nginxPort;
}
];
locations."= /pinix.css".alias = "${./cgit.css}";
};
services.traefik.dynamicConfigOptions.http = {
routers.cgit-pinix = {
rule = "Host(`${host}`)";
entryPoints = [ "web-internal" ];
service = "cgit-pinix";
tls = { };
};
services.cgit-pinix.loadBalancer.servers = [
{ url = "http://127.0.0.1:${toString nginxPort}"; }
];
};
}