303 lines
15 KiB
Markdown
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.
|