This repository has been archived on 2026-09-17. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
nixos-config/hosts/pinix/localServices/gitserver/default.nix
pijukka 823af825f4 host(pinix): add personal git server on pinix's own tailnet
pinix's Headscale now allocates its own range (100.96.0.0/24,
fd7a:115c:a1e0:96::/64). The pinix-dns namespace node forwards exactly
TCP 443 to a new Traefik web-internal entrypoint on the veth host side
(:8443, websecure untouched) and TCP 22 to sshd. unbound-pinix answers
git.lukasrosier.com (AAAA only), distributed via split DNS for that name
only.

gitserver/: cgit via services.cgit, reduced to reading and snapshot
downloads, built without its Python filters; git@ with git-shell and a
from= restricted key. Verified from tnix's pinixnet namespace (DNS,
HTTPS, snapshot, ls-remote, push) and refused from the public IPv6,
the blunix tailnet and the LAN.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGodmgtRqahbT52gApsM8N
2026-09-14 12:55:11 +02:00

130 lines
4.4 KiB
Nix

{ 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 '<pre class="readme">'
${pkgs.gnused}/bin/sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g'
printf '</pre>\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}"; }
];
};
}