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

303 lines
15 KiB
Markdown

# 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.