# 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 + `
`), 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..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.