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/docs/intent/gitserver-pinix.md

15 KiB

Intent: Persönlicher Gitserver auf pinix

Ziel

pinix soll einen kleinen, persönlichen Gitserver für ausgewählte Projekte bereitstellen. Die gemeinsame Plattform unter 0ff.space bleibt auf blunix: Forgejo, XMPP, Websites, GoToSocial und Headscale werden durch diesen Intent nicht verschoben.

Der persönliche Gitserver wird unter git.lukasrosier.com erreichbar sein. Der Name wird ausschließlich im eigenen Tailnet von pinix aufgelöst und erreicht (Headscale auf pinix, head.lukasrosier.com), mit einem eigenen Adressbereich. Er ist weder öffentlich noch über das Tailnet von blunix erreichbar.

Geplanter Umfang

  • cgit als schlanke Weboberfläche zum Lesen von Git-Repositories, installiert über das Upstream-NixOS-Modul services.cgit und auf Code-Ansicht und Download reduziert (siehe „Oberfläche“),
  • Git-Zugriff über SSH,
  • nur ein Benutzer mit Schreibzugriff: Lukas,
  • zunächst die Repositories nixos-config und dashboard,
  • Bare-Repositories auf pinix als lokale Spiegel bzw. persönliche Ablage,
  • HTTPS über Traefik auf pinix und das pinix-Zertifikatsmodell (*.lukasrosier.com); internes Routing nach dem Muster von blunix, mit Unbound an der Stelle von AdGuardHome (siehe „Erreichbarkeit im pinix-Tailnet“).

Die initiale Spiegelung von Forgejo auf blunix erfolgt manuell. Eine automatische Aktualisierung über Forgejo-Webhooks wird später als eigener Schritt ergänzt.

Oberfläche

cgit bleibt, weil es leichtgewichtig ist; die Standardoberfläche zeigt aber mehr, als hier gebraucht wird. Die Weboberfläche dient genau zwei Zwecken:

  1. Code lesen: Repository-Liste → Repository mit Dateibaum und README; einzelne Dateien lesbar dargestellt, auf Wunsch als Rohdatei (plain).
  2. Herunterladen: Snapshot-Archive (tar.gz, zip) für Branches und Tags direkt aus der Ansicht.

cgit selbst hat keinen Download-Tab — Snapshot-Links stehen nur auf der Commit-Seite und bei Tags. Ein kleines Inline-Skript in head.html hängt deshalb tar.gz/zip für den gewählten Branch an die Tab-Leiste und zeigt die SSH-Clone-URL darunter an. Ohne JavaScript bleibt cgit voll benutzbar, nur diese Leiste fehlt.

Alles andere tritt zurück: Statistiken, Commit-Graph, Blame, Datei-Zähler im Log und Side-by-Side-Diffs werden in der cgitrc abgeschaltet. Log-, Commit- und Diff-Seiten bleiben per URL erreichbar, stehen aber nicht mehr prominent in der Navigation.

Schlank ohne Python: Es werden keine Python-basierten Filter eingesetzt — kein Syntax-Highlighting über Pygments, kein Markdown-Rendering. Dateien erscheinen als schlichter, sauber gesetzter Text. Die README wird als vorformatierter Text angezeigt; falls cgit sie ohne Filter nicht lesbar ausgibt, übernimmt das ein kleiner Shell-Filter (HTML-Escaping + <pre>), der nur Werkzeuge aus coreutils/sed nutzt.

Das cgit-Paket aus nixpkgs bringt seine Python-Filter (Pygments, Markdown, docutils) selbst in die Closure mit, auch wenn sie nicht konfiguriert sind. Ein Build-Override in gitserver/default.nix entfernt sie; am C-Code ändert sich nichts.

Umsetzung ohne Eingriff in cgit selbst

cgit erzeugt sein HTML im C-Code. Dieser wird nicht gepatcht oder geforkt; die Vereinfachung läuft ausschließlich über die vorgesehenen Stellschrauben, damit nixpkgs-Updates ohne Nacharbeit durchlaufen:

  • services.cgit.<name>.settings (cgitrc): Funktionen abschalten, Snapshots aktivieren, README-Datei festlegen,
  • ein eigenes Stylesheet (css), das die Oberfläche ruhig und schlicht macht und nicht benötigte Navigation ausblendet,
  • head-include, header und footer für kleine HTML-Anpassungen, etwa direkte Links auf Dateibaum und Downloads.

Stylesheet und HTML-Schnipsel liegen als Dateien im Repo neben dem Modul (z. B. hosts/pinix/localServices/cgit/), nicht als Strings im Nix-Code.

services.cgit bindet cgit über nginx und fcgiwrap an und aktiviert nginx mit Standard-Listen auf Port 80, den Traefik auf pinix bereits belegt. nginx lauscht daher nur auf 127.0.0.1 an einem freien Port; Traefik bleibt der einzige Eingang.

Erreichbarkeit im pinix-Tailnet

Das Muster ist dasselbe wie auf blunix:

blunix pinix
Interne Route Traefik-Entrypoint web-internal Traefik-Entrypoint web-internal
Internes DNS AdGuardHome-Rewrite Unbound-local-data
Tailnet Headscale auf blunix, 100.64.0.0/10 Headscale auf pinix, eigener Bereich

Eigener Adressbereich

Das pinix-Tailnet bekommt den bereits in hosts/pinix/localServices/tailnet-dns.nix vorgesehenen Bereich:

  • IPv4 100.96.0.0/24,
  • IPv6 fd7a:115c:a1e0:96::/64.

Stand 14.09.2026 (live geprüft): Headscale auf pinix vergibt aus 100.96.0.0/24 und fd7a:115c:a1e0:96::/64. Die Hauptknoten sind pinix (100.96.0.4), tnix (100.96.0.5) und blunix (100.96.0.6); alle gehören zum Headscale-Nutzer lukas. Der technische Knoten pinix-dns belegt .1 unter einem eigenen Maschinenkonto. Die alte isolierte tnix-Registrierung .2 wurde nach der Migration gelöscht.

Der iPhone-Knoten ist als localhost-1 unter lukas mit .2 registriert; der alte localhost-Eintrag unter admin wurde entfernt. headscale nodes backfillips verschiebt bestehende Adressen nicht; die Migration erfolgte durch Löschen und Neuregistrieren.

Das blunix-Headscale vergibt faktisch nur aus 100.64.0.x; eine Überschneidung mit 100.96.0.0/24 entsteht nicht, solange das so bleibt. Den blunix-Bereich formal einzuschränken ist nicht Teil dieses Intents.

Wo der pinix-Knoten lebt

Beide Tailnets liegen innerhalb der Tailscale-Adressbereiche. Auf pinix bleibt deshalb die technische DNS- und Service-Instanz in einem eigenen Netzwerk-Namespace: tailscaled-pinix und unbound-pinix bilden dort den Knoten pinix-dns (100.96.0.1, fd7a:115c:a1e0:96::1). Das normale tailscaled im Host-Namespace ist dagegen als pinix unter lukas im gemeinsamen pinix-Tailnet angemeldet. Auf tnix gibt es keine zweite isolierte Tailscale-Instanz mehr.

Traefik wie auf blunix

Auf blunix bindet web-internal an die Tailnet-Adresse. Auf pinix liegt die Tailnet-Adresse im Namespace, Traefik im Host-Namespace; beide sind über das bestehende veth-Paar verbunden (fd42:7069:6e69:78::1 Host, fd42:7069:6e69:78::2 Namespace).

  • Entrypoint: web-internal bindet an [fd42:7069:6e69:78::1]:8443. Diese Adresse ist nur aus dem Namespace erreichbar. Ein eigener Port ist nötig, weil websecure auf :443 aller Adressen hört (live geprüft) — so bleibt websecure unverändert, der öffentliche Eingang und der Headscale/Dex-Bootstrap aus dem Namespace werden nicht angefasst.
  • Router: git.lukasrosier.com hängt ausschließlich an web-internal, nach dem mkRoute-Muster aus hosts/blunix/localServices/traefik/routes-internal.nix; Backend ist das lokale nginx.
  • Weiterleitung im Namespace: Eingehendes TCP 443 auf der pinix-Tailnet- Adresse wird auf [fd42:7069:6e69:78::1]:8443 weitergeleitet, TCP 22 auf [fd42:7069:6e69:78::1]:22 (sshd für Git). Die Quelle wird dabei auf fd42:7069:6e69:78::2 umgeschrieben, damit Antworten des Hosts nicht in dessen blunix-Tailnet-Routen laufen. Weitergeleitet werden genau diese zwei Ports; der Namespace bleibt ansonsten ohne Forwarding, kein Subnet-Router, kein Exit-Node.
  • Firewall Host: pdns-host erlaubt zusätzlich TCP 8443 und 22.

Damit ändert sich die Rolle des Namespace-Knotens gegenüber docs/pinix-tailnet-dns.md („kein Proxy“): Er leitet genau diese zwei Ports weiter. Die Doku wird bei der Umsetzung nachgezogen.

Nur IPv6: Das veth-Paar ist IPv6-only, eine Weiterleitung von IPv4 auf IPv6 gibt es nicht. git.lukasrosier.com bekommt deshalb im pinix-Tailnet nur einen AAAA-Eintrag. Das passt zur IPv6-Richtung von pinix (docs/roadmap.md). Falls ein Client ohne IPv6 im Tailnet dazukommt, wird das veth-Paar um IPv4 ergänzt.

DNS wie AdGuardHome, mit Unbound

  • Eintrag: unbound-pinix erhält local-data: "git.lukasrosier.com. AAAA fd7a:115c:a1e0:96::1" — das Gegenstück zum AdGuard-Rewrite auf blunix.
  • Verteilung: Headscale auf pinix verteilt unbound-pinix per Split-DNS nur für git.lukasrosier.com an die Clients. Ein Split für ganz lukasrosier.com würde bei einem Ausfall von unbound-pinix auch head./dex. unauflösbar machen und Clients von Headscale abschneiden.
  • Zertifikat: git.lukasrosier.com liegt unter dem vorhandenen Wildcard-Zertifikat *.lukasrosier.com (acme.nix).

Außerhalb des pinix-Tailnets zeigt git.lukasrosier.com über die öffentliche Zone weiter auf blunix, wo kein Router existiert (404).

Aktueller Zugriff

Die Hauptgeräte können sich über ihre gemeinsamen Tailnet-Adressen direkt untereinander verbinden. git.lukasrosier.com wird über den technischen Knoten pinix-dns aufgelöst und leitet TCP 443 und 22 zum internen Traefik und zum Git-SSH-Dienst auf pinix weiter. Der technische Knoten bleibt dabei ein eigener Headscale-Knoten; die Geräte des Nutzers teilen ausschließlich den Headscale-Nutzer lukas.

Umsetzungsstand 14.09.2026

Umgesetzt und auf pinix per switch aktiv: eigener Adressbereich, pinix-dns als .1, die drei Hauptknoten unter dem Headscale-Nutzer lukas (pinix .4, tnix .5, blunix .6), Weiterleitung 443/22 im Namespace, web-internal auf :8443, Split-DNS, cgit ohne Python und Git über SSH. nixos-config und dashboard sind manuell von Forgejo gespiegelt (nur Branches und Tags). Der direkte Zugriff aus dem normalen tnix-Host-Netz auf https://git.lukasrosier.com wurde nach der Bereinigung der alten isolierten tnix-Instanz geprüft.

Der iPhone-Knoten ist jetzt localhost-1 unter dem Nutzer lukas (.2). Der alte admin-Eintrag wurde nach der erfolgreichen Neuanmeldung gelöscht.

Die alte isolierte tnix-Registrierung (.2) und die dazugehörige zweite Tailscale-Instanz wurden entfernt. Der direkte Zugriff nutzt jetzt das gemeinsame Tailnet; hosts/tnix/pinixnet.nix ist nicht mehr erforderlich.

Ebenfalls nachgezogen: tnix ↔ pinix-dns lief über das DERP-Relay fra (~100 ms), weil der Namespace-Knoten nach außen nur als pinix' maskierte öffentliche IPv6 erschien. Eine Portweiterleitung von UDP 41642 in den Namespace (tailnet-dns.nix) macht die Verbindung direkt (1 ms). Entschieden am 14.09.2026: cgit auf pinix bleibt vorerst eigenständig. Kein automatischer Abgleich mit Forgejo; die Spiegelung von blunix (Ausbauschritt 2) ist bis auf Weiteres zurückgestellt.

Nachgezogen am 14.09.2026: Die bisher pausierte Restic-Sicherung auf pinix ist wieder aktiv und umfasst zusätzlich /var/lib/git. Der erste Lauf wurde sofort ausgeführt und als Snapshot e3d7ebe3 verifiziert; er enthält dashboard.git und nixos-config.git. Das Restic-Repository liegt lokal unter /backup. Die vorhandene tnix-Kopie dieses Verzeichnisses auf einem externen Medium (backup-pi) bleibt ein manueller Schritt; ein getesteter Restore und eine automatische Ausführung dieser Off-Host-Kopie sind damit noch nicht Teil dieses Nachtrags.

Besitz- und Schreibmodell

Forgejo auf blunix bleibt für die bisherigen gemeinsamen Projekte und deren normale Zusammenarbeit maßgeblich. Für die ersten beiden Repositories wird vor der technischen Umsetzung festgelegt, ob pinix ausschließlich eine read-only Kopie von Forgejo erhält oder ob einzelne persönliche Repositories auf pinix führend werden.

Bis diese Frage entschieden ist, darf kein automatischer bidirektionaler Abgleich eingerichtet werden. Dadurch werden divergierende Schreibstände und unbeabsichtigte Überschreibungen vermieden.

Bewusste Nicht-Ziele

  • kein zweites Forgejo auf pinix,
  • kein Patch oder Fork des cgit-C-Codes und kein eigenes Web-Frontend,
  • kein Python in der cgit-Closure (keine Pygments-/Markdown-Filter),
  • keine Erreichbarkeit über das Tailnet von blunix, keine Einträge in AdGuardHome oder Traefik auf blunix,
  • kein zusätzlicher DNS-Dienst (z. B. AdGuardHome) auf pinix,
  • keine Änderung an Traefiks öffentlichem Entrypoint websecure,
  • keine öffentliche Registrierung oder Mehrbenutzerverwaltung,
  • keine Webhook-Annahme im ersten Umsetzungsschritt,
  • keine Verlagerung der bestehenden 0ff.space-Dienste von blunix,
  • kein öffentlicher Internetzugriff ohne spätere ausdrückliche Entscheidung.

Spätere Ausbauschritte

  1. Repositories initial anlegen und den gewünschten Spiegelungsmodus festlegen.
  2. Webhook von Forgejo zu einem kleinen, abgesicherten Pull-Dienst auf pinix einrichten. Der Weg von Forgejo (blunix) zu pinix ist dabei neu zu klären, weil der Gitserver nicht im blunix-Tailnet hängt.
  3. Repositories und Branches für den Spiegel explizit in einer deklarativen Konfiguration oder einer geschützten Allowlist festhalten.
  4. Optional einzelne persönliche Repositories als führende Schreibkopie auf pinix betreiben.
  5. Erst danach prüfen, ob eine öffentliche cgit-Ansicht oder weitere kleine persönliche Dienste wie snac2 sinnvoll sind.

Akzeptanzkriterien für die Umsetzung

  • Headscale auf pinix vergibt ausschließlich aus 100.96.0.0/24 und fd7a:115c:a1e0:96::/64; keine Registrierung trägt noch eine 100.64.0.0/10-Adresse außerhalb davon.
  • git.lukasrosier.com funktioniert von einem Client im pinix-Tailnet über HTTPS.
  • Clients im pinix-Tailnet lösen git.lukasrosier.com über unbound-pinix auf fd7a:115c:a1e0:96::1 auf; Eintrag und Split-DNS sind deklarativ im Repo festgehalten.
  • Der Traefik-Router für git.lukasrosier.com hängt nur an web-internal auf [fd42:7069:6e69:78::1]:8443.
  • Aus dem blunix-Tailnet ist git.lukasrosier.com nicht erreichbar, und auf blunix existiert weder ein Traefik-Router noch ein AdGuard-Rewrite dafür.
  • Headscale, Dex und der öffentliche IPv6-Eingang auf pinix funktionieren unverändert; websecure ist nicht angefasst.
  • Der Namespace leitet nur TCP 443 und 22 weiter und ist ansonsten weder Router noch Proxy.
  • cgit zeigt die beiden ausgewählten Repositories an.
  • cgit ist über services.cgit deklariert; eigenes CSS und HTML-Schnipsel liegen als Dateien im Repo.
  • Die cgit-Closure enthält kein Python.
  • Ein Repository zeigt Dateibaum und README; Dateien sind lesbar und als Rohdatei abrufbar.
  • Snapshots als tar.gz und zip lassen sich für Branches und Tags herunterladen.
  • Statistiken, Commit-Graph und Blame sind abgeschaltet; die Navigation zeigt nur, was zum Lesen und Herunterladen nötig ist.
  • nginx ist nicht von außen erreichbar, nur Traefik nimmt Verbindungen an.
  • SSH akzeptiert Git-Operationen nur für den vorgesehenen Benutzer und Schlüssel, und nur über den Weg aus dem pinix-Tailnet.
  • Ein Zugriff außerhalb des pinix-Tailnets ist nicht möglich, auch nicht direkt gegen die öffentliche IPv6-Adresse von pinix mit passendem Host-Header.
  • Es gibt keinen automatischen Schreib- oder Synchronisationsdienst, bevor der führende Datenbestand geklärt ist.