Instanzmanager Stufe 2: Kestrel auf dem Socket, Instanz kopflos (#528)
Eine Instanz ist jetzt ohne Port ansprechbar. InstanceEndpoint liefert die Adresse: unter Linux <Instanz>/manager.sock, unter Windows eine Named Pipe, deren Name aus einem SHA-256-Fingerabdruck des Instanzpfades entsteht. Zwei Instanzen bekommen damit zwangslaeufig zwei Adressen, ohne dass jemand etwas vergeben muss - genau der Unterschied zur Vorbelegung 7001, um die sich zwei Instanzen stillschweigend streiten. Der Socket ist immer da, auch ohne Weboberflaeche. Er gehoert nicht zu #494, sondern ist die Steuerflaeche der Instanz: webUiEnabled schaltet den TCP-Zugang, nicht den Socket. WebUiHost heisst deshalb jetzt InstanceHttpHost - ein Kestrel, zwei Zugaenge. Ueber TCP gilt Token-Pflicht, ueber den Socket nicht: Dort steht die Grenze am Dateisystem (0600 unter Linux, CurrentUserOnly bei der Named Pipe, explizit gesetzt statt auf die Vorgabe vertraut). Wer die Socketdatei oeffnen darf, sieht ohnehin in das Verzeichnis, in dem alles liegt, was ein Token schuetzen wuerde. Woran die beiden Zugaenge auseinandergehalten werden, ist der Punkt, an dem ein Irrtum teuer waere. Bei Socket-Verbindungen stehen in HttpContext.Connection keine IP-Adressen - daraus zu schliessen "keine IP, also Socket" waere eine Vermutung an der Stelle, an der ein Torwaechter steht, und sie irrt in die falsche Richtung: Im Zweifel liesse sie eine TCP-Anfrage ohne Token durch. Der Socket-Zuhoerer haengt deshalb jeder Verbindung selbst eine Marke an (LocalTransport, Verbindungs- Zwischenschicht ueber ListenOptions.Use); wo sie fehlt, gilt Token-Pflicht. Ein belegter Port kostet nur noch die Weboberflaeche. Vorher gab TryStart in dem Fall null zurueck und die ganze HTTP-Flaeche war weg. Seit dem Socket ist das die falsche Rangfolge: Ihn zu verlieren, weil eine zweite Instanz laeuft, hiesse die Instanz unerreichbar zu machen - in genau dem Fall, fuer den es den Manager gibt. Kopfloser Betrieb ueber --headless --instance <Pfad>. Die Verzweigung sitzt in Program.Main vor BuildAvaloniaApp, damit auf einem Server ohne Bildschirmsitzung kein Fenstersystem angefasst wird. Gefragt wird nichts: Instanz aus dem Argument, Lizenz ueber das bereits vorhandene NonInteractiveLicensePrompt, Telegram-Anmeldung aus. Nachgemessen, nicht angenommen: Kopflose Instanz auf Instance-Test gestartet, kein Fenster, und eine rohe HTTP/1.1-Anfrage aus einem fremden Prozess (PowerShell, direkt auf die Pipe) bekam 200 OK auf /api/info, ohne Token. Das ist die Abnahmebedingung fuer Stufe 2. Korrektur zur eigenen Ankuendigung: webServerPort entfaellt nicht mit Stufe 2, sondern mit Stufe 4. Solange kein Manager die Weboberflaeche traegt, hiesse Wegnehmen, sie ersatzlos abzuschalten. Zwei Luecken bleiben offen und gehoeren auf die Linux-Testmaschine: Die Socketdatei-Faelle sind unter Windows gegenstandslos und werden dort mit sichtbarer Begruendung uebersprungen - der Unix-Domain-Socket selbst ist auf der Entwicklungsmaschine also ungeprueft, obwohl er die Zielplattform ist. Und das geordnete Ende auf SIGTERM liess sich hier nicht ausloesen: Windows kennt das Signal fuer einen fensterlosen Prozess nicht. Build ohne Warnungen, 714 Tests gruen, 8 uebersprungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -312,7 +312,7 @@ Verbindung und führt sie zusammen, mit der Instanzkennung an jedem Ereignis.
|
||||
| Stufe | Inhalt | Woran man es misst |
|
||||
|---|---|---|
|
||||
| 1 ✔ | Einstellungen und Protokolle je Instanz trennen (Abschnitt 6) | Zwei Instanzen gleichzeitig, keine schreibt der anderen ins Protokoll |
|
||||
| 2 | Instanzprozess kopflos lauffähig, Kestrel auf dem Socket | Eine Instanz läuft ohne Fenster, der Socket antwortet |
|
||||
| 2 ✔ | Instanzprozess kopflos lauffähig, Kestrel auf dem Socket | Eine Instanz läuft ohne Fenster, der Socket antwortet |
|
||||
| 3 | Manager: anlegen, starten, anhalten, Prozessaufsicht, Lizenz, Sub-Token | Eine Instanz abschießen — der Manager merkt es und meldet es |
|
||||
| 4 | Weboberfläche zieht um, Weiterleitung, ein Ereignisstrom | Eine Adresse, ein Zugang, Wechsel ohne Neustart |
|
||||
| 5 | Instanzfenster als eigener Prozess, Wechsel holt das Fenster nach vorn | Kein Neustart mehr beim Wechseln |
|
||||
@@ -336,11 +336,10 @@ einer alten `Settings.json`, meldet `SettingsManager.AbandonedKeys` sie, und der
|
||||
protokolliert einmal, was nicht mehr gilt und wohin diese Instanz stattdessen schreibt.
|
||||
Beim nächsten Speichern verschwinden sie aus der Datei.
|
||||
|
||||
**`webServerPort` bleibt vorerst stehen.** Es entfällt erst, wenn Kestrel auf dem Socket
|
||||
hört — also mit Stufe 2. Vorher wegzunehmen hieße, die Weboberfläche ohne Ersatz
|
||||
abzuschalten. Der Portstreit zweier Instanzen ist bis dahin gedeckelt, aber nicht gelöst:
|
||||
`WebUiHost.TryStart` gibt bei belegtem Port `null` zurück und protokolliert es, statt den
|
||||
Start zu verhindern.
|
||||
**`webServerPort` bleibt vorerst stehen.** Es entfällt erst mit **Stufe 4**, wenn der
|
||||
Manager den Port übernimmt — nicht schon, sobald Kestrel den Socket hat. Eine frühere
|
||||
Fassung dieses Absatzes sagte „mit Stufe 2"; das war falsch gerechnet: Solange es keinen
|
||||
Manager gibt, der die Weboberfläche trägt, hieße Wegnehmen, sie ersatzlos abzuschalten.
|
||||
|
||||
---
|
||||
|
||||
@@ -357,3 +356,46 @@ Manager läuft immer mit, die Instanzfenster laufen mit, aber eine Instanz *kann
|
||||
ohne Fenster laufen.
|
||||
|
||||
**#218 (SDK 2.6.0 → aktuell)** wird Voraussetzung — siehe Abschnitt 5.
|
||||
|
||||
### 9.2 Was Stufe 2 gebracht hat
|
||||
|
||||
**Kestrel hört auf dem Socket** — `<Instanz>/manager.sock` unter Linux, unter Windows
|
||||
eine Named Pipe, deren Name aus einem Fingerabdruck des Instanzpfades entsteht
|
||||
(`InstanceEndpoint`). Zwei Instanzen bekommen zwangsläufig zwei Adressen, ohne dass
|
||||
jemand etwas vergeben müsste.
|
||||
|
||||
**Der Socket ist immer da, auch ohne Weboberfläche.** Er ist nicht Teil von #494, sondern
|
||||
die Steuerfläche der Instanz: `webUiEnabled` schaltet den TCP-Zugang, nicht den Socket.
|
||||
Was aus `WebUiHost` wurde, heißt deshalb jetzt `InstanceHttpHost` — ein Kestrel, zwei
|
||||
Zugänge.
|
||||
|
||||
**Der Torwächter unterscheidet die Zugänge.** Über TCP gilt Token-Pflicht, über den
|
||||
Socket nicht: Dort steht die Grenze am Dateisystem (0600 beziehungsweise
|
||||
`CurrentUserOnly`), und wer die Socketdatei öffnen darf, sieht ohnehin in das Verzeichnis,
|
||||
in dem alles liegt, was ein Token schützen würde. **Woran die beiden auseinandergehalten
|
||||
werden, ist der Punkt, an dem ein Irrtum teuer wäre:** Bei Socket-Verbindungen stehen in
|
||||
`HttpContext.Connection` keine IP-Adressen — daraus zu schließen „keine IP, also Socket"
|
||||
wäre eine Vermutung an der Stelle, an der ein Torwächter steht, und ließe im Zweifel eine
|
||||
TCP-Anfrage ohne Token durch. Der Socket-Zuhörer hängt deshalb jeder Verbindung selbst
|
||||
eine Marke an (`LocalTransport`); wo sie fehlt, gilt Token-Pflicht.
|
||||
|
||||
**Ein belegter Port kostet nur noch die Weboberfläche.** Vorher gab `TryStart` in dem Fall
|
||||
`null` zurück und die ganze HTTP-Fläche war weg. Das ist seit dem Socket die falsche
|
||||
Rangfolge: Ihn zu verlieren, weil eine zweite Instanz schon läuft, hieße die Instanz
|
||||
unerreichbar zu machen — in genau dem Fall, für den es den Manager gibt.
|
||||
|
||||
**Kopfloser Betrieb** über `--headless --instance <Pfad>`. Die Verzweigung sitzt in
|
||||
`Program.Main` vor `BuildAvaloniaApp`, damit auf einem Server ohne Bildschirmsitzung kein
|
||||
Fenstersystem angefasst wird. Gefragt wird nichts: Instanz aus dem Argument, Lizenz über
|
||||
`NonInteractiveLicensePrompt`, Telegram-Anmeldung aus.
|
||||
|
||||
Nachgemessen, nicht angenommen: Eine kopflose Instanz auf `Instance-Test` lief ohne
|
||||
Fenster, und eine rohe HTTP-Anfrage aus einem fremden Prozess (PowerShell, direkt auf die
|
||||
Pipe) bekam `200 OK` auf `/api/info` — ohne Token.
|
||||
|
||||
**Zwei Lücken, die offen bleiben.** Die Socketdatei-Fälle (liegengebliebene Leiche,
|
||||
Aufräumen beim Beenden) sind unter Windows gegenstandslos und werden dort mit Begründung
|
||||
übersprungen — der Unix-Domain-Socket selbst ist auf der Entwicklungsmaschine also
|
||||
ungeprüft, obwohl er die Zielplattform ist. Und das geordnete Ende auf `SIGTERM` ließ sich
|
||||
hier nicht auslösen: Windows kennt das Signal für einen fensterlosen Prozess nicht.
|
||||
Beides gehört auf die Linux-Testmaschine.
|
||||
|
||||
@@ -78,6 +78,21 @@ Startet sie nicht, steht der Grund im Protokoll (`Logs/`, Modul `Web`). Die
|
||||
häufigsten: Token fehlt, Token kürzer als 20 Zeichen, Port belegt. Ein
|
||||
Fehlschlag hält die Anwendung nie auf — sie läuft dann eben ohne Browser.
|
||||
|
||||
### Der Socket daneben — nicht deine Baustelle, aber gut zu wissen
|
||||
|
||||
Seit #528 (Stufe 2) hört dieselbe Instanz zusätzlich auf einem Socket im
|
||||
Instanzverzeichnis (`manager.sock`, unter Windows eine Named Pipe). Über ihn
|
||||
wird der Instanzmanager die Instanz erreichen. Drei Dinge, die dich betreffen
|
||||
könnten:
|
||||
|
||||
- **Er ist immer da**, auch wenn `webUiEnabled` aus ist. Was du in `Logs/Web.log`
|
||||
als „Instanz hört auf …" siehst, ist er und nicht die Weboberfläche.
|
||||
- **Er kennt kein Token.** Seine Grenze steht am Dateisystem. Wer über den Socket
|
||||
hereinkommt, ist an `TokenGate` vorbei — das ist Absicht und in
|
||||
`LocalTransport` festgehalten.
|
||||
- **Für dich ändert sich nichts.** Endpunkte, Übertragungstypen und der
|
||||
Ereignisstrom sind dieselben; es ist nur eine zweite Tür in denselben Raum.
|
||||
|
||||
---
|
||||
|
||||
## 2 — Die API
|
||||
|
||||
Reference in New Issue
Block a user