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.cgitund auf Code-Ansicht und Download reduziert (siehe „Oberfläche“), - Git-Zugriff über SSH,
- nur ein Benutzer mit Schreibzugriff: Lukas,
- zunächst die Repositories
nixos-configunddashboard, - 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:
- Code lesen: Repository-Liste → Repository mit Dateibaum und README;
einzelne Dateien lesbar dargestellt, auf Wunsch als Rohdatei (
plain). - 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,headerundfooterfü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-internalbindet an[fd42:7069:6e69:78::1]:8443. Diese Adresse ist nur aus dem Namespace erreichbar. Ein eigener Port ist nötig, weilwebsecureauf:443aller Adressen hört (live geprüft) — so bleibtwebsecureunverändert, der öffentliche Eingang und der Headscale/Dex-Bootstrap aus dem Namespace werden nicht angefasst. - Router:
git.lukasrosier.comhängt ausschließlich anweb-internal, nach demmkRoute-Muster aushosts/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]:8443weitergeleitet, TCP 22 auf[fd42:7069:6e69:78::1]:22(sshd für Git). Die Quelle wird dabei auffd42:7069:6e69:78::2umgeschrieben, 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-hosterlaubt 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-pinixerhältlocal-data: "git.lukasrosier.com. AAAA fd7a:115c:a1e0:96::1"— das Gegenstück zum AdGuard-Rewrite auf blunix. - Verteilung: Headscale auf pinix verteilt
unbound-pinixper Split-DNS nur fürgit.lukasrosier.coman die Clients. Ein Split für ganzlukasrosier.comwürde bei einem Ausfall vonunbound-pinixauchhead./dex.unauflösbar machen und Clients von Headscale abschneiden. - Zertifikat:
git.lukasrosier.comliegt 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
- Repositories initial anlegen und den gewünschten Spiegelungsmodus festlegen.
- 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.
- Repositories und Branches für den Spiegel explizit in einer deklarativen Konfiguration oder einer geschützten Allowlist festhalten.
- Optional einzelne persönliche Repositories als führende Schreibkopie auf pinix betreiben.
- 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/24undfd7a:115c:a1e0:96::/64; keine Registrierung trägt noch eine100.64.0.0/10-Adresse außerhalb davon. git.lukasrosier.comfunktioniert von einem Client im pinix-Tailnet über HTTPS.- Clients im pinix-Tailnet lösen
git.lukasrosier.comüberunbound-pinixauffd7a:115c:a1e0:96::1auf; Eintrag und Split-DNS sind deklarativ im Repo festgehalten. - Der Traefik-Router für
git.lukasrosier.comhängt nur anweb-internalauf[fd42:7069:6e69:78::1]:8443. - Aus dem blunix-Tailnet ist
git.lukasrosier.comnicht 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;
websecureist 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.cgitdeklariert; 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.gzundziplassen 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.