a511480b7711f3bd42d32eb14a85706d2770b355
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a511480b77 |
Instanzmanager Stufe 4: eine Adresse, ein Zugang, alle Instanzen (#528, E-M5)
Der Managerprozess. ClawdDotNet.Instanzmanager ist eine Konsolenanwendung: Lizenz pruefen, uebernehmen was laeuft, Weboberflaeche aufhaengen, warten. Beim Herunterfahren beendet er keine Instanz. Zwei neue Projekte. ClawdDotNet.Manager.Web traegt die Weboberflaeche - ohne jeden Verweis auf ClawdDotNet.Web, denn dort haengt App und daran sechzehn Werkzeugprojekte. ClawdDotNet.ManagerApp ist der Einstiegspunkt. wwwroot ist aus der Instanz in den Manager gezogen. DER SCHNITT DER HTTP-FLAECHE: Unter /api/verwaltung steht, was den Manager angeht - auflisten, anlegen, starten, anhalten. Alles andere unter /api geht unveraendert an eine Instanz. Er leitet weiter, er implementiert nicht. Welche Instanz gemeint ist, sagt die Kopfzeile X-Instanz; laeuft genau eine, ist sie gemeint, und laufen mehrere, wird nachgefragt statt geraten - eine Anfrage an die falsche Instanz ist schlimmer als eine abgelehnte. Der zusammengefuehrte Ereignisstrom haelt je laufender Instanz eine Zuleitung und schreibt an jedes Ereignis, von wem es kommt. Ergaenzt statt verpackt: Ein Umschlag waere einfacher gewesen, haette aber jede Ansicht der Weboberflaeche zum Umschreiben gezwungen. ABNAHME gegen laufende Prozesse: 401 ohne Token, Oberflaeche ohne Token 200, Instanz ueber den Manager gestartet und angehalten, /api/info und /api/status unverfaelscht durchgereicht, /events/logs mit "instanz":"Instance-Test" an jeder Zeile. Manager abgeschossen: Instanz laeuft weiter. Manager neu gestartet: "1 Instanzen bekannt, 1 laufen bereits" - uebernommen. E-M6 damit in beide Richtungen belegt. DREI DEFEKTE, die erst der Lauf gezeigt hat: 1. Der Manager hing an einer Konsolenabfrage nach dem Lizenzschluessel. Ein Test auf Console.IsInputRedirected reicht dafuer NICHT: Er beantwortet "ist die Eingabe umgeleitet", nicht "kann jemand antworten" - ein Prozess ganz ohne Konsole, der gefaehrlichste Fall, ist nicht umgeleitet. Der Manager fragt jetzt gar nicht. 2. Eine unlesbare manager.json wurde stillschweigend durch Vorgaben ersetzt: falsches Instanzverzeichnis, anderer Port, Lizenzfrage - und nichts sagte, woran es lag. LoadProblem meldet den Grund jetzt. 3. Der Instanzprozess erbte CLAWD_CONFIG_DIR vom Manager und legte sich in dessen Konfigurationsverzeichnis eine eigene Settings.json an. Eine Umgebungsvariable vererbt sich, ein Argument nicht - der Manager nimmt sein Verzeichnis jetzt ueber --config. SharedLoggerProvider ist nach Core gezogen: Es gibt jetzt zwei Webhosts, die dasselbe brauchen, und sie duerfen einander nicht kennen. NOCH OFFEN in dieser Stufe: Die Weboberflaeche kennt noch keine Instanzen - Instanzauswahl, X-Instanz und die Verwaltungsansicht sind Arbeit an wwwroot. Und webServerPort steht noch in der Instanz. Build ohne Warnungen, 775 Tests gruen, 8 uebersprungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
cb56175d20 |
Instanzmanager: Lizenz und Sub-Token wandern in den Manager (#528, E-M3)
Neues Projekt ClawdDotNet.Licensing. LicenseGate, LicenseWatch, LicenseInfo und ILicensePrompt sind aus ClawdDotNet.App dorthin gezogen, weil zwei Prozesse dieselbe Lizenzpruefung brauchen und keiner von beiden den anderen kennen darf. In Core konnte es nicht landen - dann zoege jedes der sechzehn Werkzeugprojekte das Deploymentcenter-SDK mit. Dabei ist die Kopplung an SettingsManager verschwunden. LicenseGate kennt jetzt nur ILicenseStore: Serveradresse lesen, Schluessel lesen und schreiben, speichern. Die Anwendung fuehrt ihn in Settings.json, der Manager in manager.json. Der Torwaechter prueft Schluessel; Dateien verwaltet er nicht. Auch die Anwendungsversion kommt als Parameter statt aus einem globalen Zugriff - diese Schicht weiss nicht, welche Anwendung sie benutzt. ManagerCore ist die Klammer: eigene Einstellungen, Lizenz, Register, Meldeweg. Drei Regeln: - Ohne gueltige Lizenz startet er keine Instanz. Laufende werden trotzdem in die Liste genommen - man soll sie sehen und anhalten koennen. - Ein Widerruf haelt alle an. Der einzige Fall, in dem der Manager das von sich aus tut. Ein Serverausfall gehoert ausdruecklich nicht dazu; das SDK trennt Urteil und gescheiterten Versuch, und nur das Urteil kommt hier an. Sonst naehme ein Netzproblem saemtliche Installationen mit. - Ohne hinterlegte Lizenzpruefung sperrt er nichts, sonst gaebe es eine Henne-Ei-Sperre, bevor jemand einen Schluessel eintragen kann. Sub-Token je Instanz: Der Manager haelt das Master-Token und tauscht es beim ANLEGEN einer Instanz gegen ein eigenes (watchdog:ping, bugtracker:report, einzeln widerrufbar). Beim Anlegen und nicht beim ersten Start, weil die Instanzdatei dann zweifelsfrei niemandem gehoert - sie spaeter zu schreiben, waehrend der Instanzprozess laeuft, hiesse zwei Schreiber auf einer Datei. Fuer Instanzen, die es vor dem Manager schon gab, wird es vor dem Start nachgeholt. Danach fragt er ueber whoami nach, ob das Token an ein Projekt gebunden ist (SDK 2.8.1). Ein fehlgeschlagener Tokenbezug haelt die Instanz nicht auf. NOCH NICHT VOLLSTAENDIG: Die Instanzen pruefen die Lizenz weiterhin selbst. Das laesst sich erst abschalten, wenn eine Instanz feststellen kann, ob ein Manager da ist - vorher entstuende eine Fassung ganz ohne Durchsetzung. Und ein Testfehler, der zweimal derselbe war: Die Socket-Antwort war hinterlegt, BEVOR der Manager hochfuhr. Damit sagt man "diese Instanz laeuft bereits", der Manager uebernimmt sie, der Starter wird nie angefasst - und der Test prueft etwas anderes, als er behauptet. Es gibt jetzt eine Hilfsmethode, die das ausschliesst. Build ohne Warnungen, 774 Tests gruen, 8 uebersprungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
72b0764d1a |
Instanzmanager: Register, Einstellungen und stellvertretende Meldung (#528)
Der zweite Teil von Stufe 3. Die Aufsicht ueber eine einzelne Instanz stand schon; jetzt gibt es die Klammer darum und den Weg nach draussen. InstanceRegistry haelt je Instanz eine Aufsicht. Zwei Zusagen, beide aus E-M6: Beim Start wird UEBERNOMMEN, nicht gestartet - ein Manager, der beim Hochkommen alles hochfaehrt, was er findet, startet nach einem Neustart der Maschine auch das, was jemand mit Absicht angehalten hat. Und beim Beenden wird nichts beendet: Wer den Manager schliesst, will den Manager schliessen. Einzige Ausnahme ist StopAllAsync fuer einen Grund, der alle betrifft - und der heisst Lizenzwiderruf. SupervisionReporter macht aus einem Befund eine Bugtracker-Meldung, stellvertretend: mit Prozesskennung, Abschiedscode und dem Ausschnitt aus dem Instanzprotokoll, der noch auf der Platte liegt. Der Idempotenzschluessel ist stabil je Instanz und Art - stuerzt dieselbe Instanz wiederholt ab, zaehlt der Server hoch, statt den Bugtracker mit Zwillingen zu fuellen. Ein leerer Protokollauszug ist dabei die aussagekraeftigste Zeile der Meldung und wird ausdruecklich als solche geschrieben. Genau das war der Zustand am 2026-09-08: Der Prozess wurde erschlagen, bevor sein Schreibpuffer geleert war, und hinterliess nichts. manager.json traegt die Felder aus Abschnitt 6 des Konzepts und sonst nichts. Lizenzschluessel, Master-Token und Web-Token liegen darin verschluesselt und zur Laufzeit im Klartext - dieselbe Bauart wie die Anwendungseinstellungen, mit Absicht. Unsinnige Aufsichtswerte werden eingefangen, weil die Datei von Hand bearbeitbar ist. InstanceLayout ist nach Core gezogen (ResolveLogDirectory, ResolveBackupDirectory). Der Manager braucht das Protokollverzeichnis einer Instanz fuer den Auszug in der Meldung und darf ClawdDotNet.App nicht kennen; dieselbe Rechnung an zwei Stellen zu fuehren waere die schlechtere Antwort. AppHost leitet nur noch weiter. EIN DEFEKT, den erst die eigenen Tests gezeigt haben: Eine angehaltene Instanz erschien im Register als "Instance-Alpha" statt "Alpha". Die Aufsicht kennt den Namen erst, wenn die Instanz einmal geantwortet hat, und eine angehaltene tut das nie - waehrend genau diese Ansicht der Manager anzeigt. Das Register fuehrt den Namen jetzt aus InstanceSettings.json mit. Und ein Testdouble, das zu viel behauptet hat: Der falsche Socket antwortete fuer jedes Verzeichnis dieselbe Instanz. Das laesst Tests gruen werden, die im Betrieb zwei Instanzen verwechseln wuerden; er antwortet jetzt je Verzeichnis. 29 neue Tests. Build ohne Warnungen, 766 Tests gruen, 8 uebersprungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4ef6d714a8 |
Instanzmanager Stufe 3: Prozessaufsicht, Start und Stopp (#528)
Der Anlass des ganzen Vorhabens: Am 2026-09-08 hat sich eine Instanz aufgehaengt und wurde von Windows beendet. Zu diesem Vorgang steht keine einzige Zeile in den Instanzprotokollen, kein Bugtracker-Eintrag, kein Watchdog-Alarm - weil Meldeweg und Watchdog in genau dem Prozess lebten, ueber den sie haetten berichten sollen. Ab jetzt sieht jemand anders hin. Neu ist ClawdDotNet.Manager mit genau EINEM Projektverweis: Core. Das ist keine Sparsamkeit, sondern die halbe Architektur - App schleppt sechzehn Werkzeugprojekte hinter sich her, und "keine Werkzeuge im Manager" soll eine Eigenschaft des Bauplans sein und keine Absichtserklaerung. DIE ENTSCHEIDUNG, AN DER ALLES HAENGT: Die Aufsicht fragt den Socket, nicht die Prozessliste. Die Prozessliste sagt, dass etwas da ist. Sie sagt nicht, ob es noch arbeitet - und genau das war der Zustand, den niemand bemerkt hat. Ein Durchgang kennt drei Antworten: Socket antwortet, sie laeuft. Socket schweigt und der Prozess ist weg, Absturz mit Abschiedscode. Socket schweigt und der Prozess lebt, verstummt - aber erst nach mehreren Fehlversuchen in Folge, denn ein Alarm bei jedem Aussetzer ist nach einer Woche der Alarm, den niemand mehr liest. Ein Start gilt erst, wenn die Instanz antwortet. Zwischen "der Prozess laeuft" und "die Instanz laeuft" liegen Datenbank, Agenten und Kestrel; wer nur auf Process.Start wartet, haelt eine Instanz fuer gestartet, die gleich an einer kaputten Konfiguration scheitert. Angehalten wird durch Bitten. Neu auf der Instanzseite: POST /api/instanz/beenden, ausschliesslich ueber den Socket - ueber TCP gibt es den Endpunkt nicht, auch mit gueltigem Token nicht, und er antwortet dort 404 statt 403. Die Lebensdauer einer Instanz gehoert dem Manager; eine Browsersitzung soll den Prozess, der sie bedient, nicht beenden koennen. Ausgeloest wird der Wunsch erst nach dem Absenden der Antwort (Response.OnCompleted), sonst bekaeme der Manager statt einer Zusage einen Verbindungsabbruch und hielte ihn fuer einen Fehlschlag. Wiederfinden nach einem Managerausfall (E-M6): Die Zustandsdatei instance-state.json ist der Hinweis, der Socket ist der Beweis. Erst wird gefragt, wer da antwortet; nur bei gleicher Instanzkennung wird die Prozesskennung aus der Datei angefasst. Andersherum fuehrt eine wiederverwendete Nummer nach einem Neustart der Maschine geradewegs dazu, ein fremdes Programm zu beaufsichtigen und beim Anhalten zu beenden. Selbsttaetiger Neustart ist bewusst NICHT gebaut. Die Abnahmebedingung lautet "merkt es und meldet es", und zum Neustarten trifft das Konzept keine Entscheidung; eine Instanz in einer Schleife wieder hochzufahren, die jedes Mal abstuerzt, waere die falsche Vorgabe fuer etwas, das niemand entschieden hat. Als offene Frage aufgeschrieben. ABNAHME, mit echten Prozessen gemessen: Manager startet Instanz Test in 0,7 s, identifiziert sie ueber die Named Pipe, schreibt die Zustandsdatei (pid 54476, id d68af6fd). Prozess von aussen abgeschossen. Naechster Durchgang: Crashed, Code -1, meldenswert, Zustandsdatei weggeraeumt. Dazu 20 Tests gegen gesteuerte Prozesse - ein Absturz zum gewuenschten Zeitpunkt und eine wiederverwendete Prozesskennung sind mit einem echten Programm nicht herzustellen. Noch offen in dieser Stufe: Instanzen anlegen, Lizenz im Manager, Sub-Token je Instanz, und der Managerprozess selbst. Build ohne Warnungen, 737 Tests gruen, 8 uebersprungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8686d13999 |
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> |
||
|
|
facd21459c |
Instanzmanager Stufe 1: Protokolle und Sicherungen je Instanz (#528)
Mehrere Instanzen sollen unabhaengig voneinander laufen. Heute tun sie das nicht: Sie teilen sich Protokollverzeichnis, Sicherungsordner und Sicherungszeitplan. In einer gemeinsamen Startup.log standen abwechselnd Zeilen zweier Instanzen, ohne Kennzeichnung, welche gerade spricht -- im Mehrinstanzbetrieb ist damit jede Fehlersuche Ratearbeit. Genau in dieser Lage ist der Aufhaenger aus #529 entstanden. Fuenf Schluessel wandern aus AppSettings nach InstanceSettings.json: logDirectory, backupDirectory, backupKeepCount, autoBackupEnabled, autoBackupTime. Leer heisst jetzt <Instanz>/Logs beziehungsweise <Instanz>/Backups; ein relativer Wert zaehlt weiterhin gegen das Instanzverzeichnis, nicht gegen das Arbeitsverzeichnis des Prozesses (#375 bleibt gewahrt). autoBackupEnabled und autoBackupTime standen in der Planung nicht, sind aber mitgegangen: Ein Schalter, der die taegliche Sicherung fuer eine Instanz einschaltet und sie damit fuer alle einschaltet, ist derselbe Fehler wie ein Ordner fuer alle -- und zwei von vier Sicherungsfeldern zu verschieben hinterliesse eine Einstellungsseite, auf der die Haelfte anwendungsweit gilt. Keine Uebernahme der Altwerte, aber eine Ansage: SettingsManager. AbandonedKeys meldet die aufgegebenen Schluessel, solange sie noch in Settings.json stehen, und der Start protokolliert einmal, was nicht mehr gilt und wohin diese Instanz stattdessen schreibt. Ein anwendungsweiter Pfad, in alle Instanzen kopiert, waere wieder ein Ordner fuer alle. webServerPort bleibt vorerst stehen. Es entfaellt mit Stufe 2, sobald Kestrel auf dem Socket hoert; vorher wegzunehmen hiesse, die Weboberflaeche ohne Ersatz abzuschalten. Korrektur an der eigenen Befundlage: "Instanz A wirft die Sicherungen von Instanz B weg" war zu breit. Beide Rotationen filtern bereits nach dem Praefix backup_<Instanzname>_. Der reale Defekt ist enger, und ein Praefix ist keine Grenze: backup_Test_2_*.zip faengt mit backup_Test_ an, also zaehlt Test im gemeinsamen Ordner die Archive von Test_2 mit. Belegt in BackupSchedulerTests, das den alten Zustand eigens wiederherstellt. Nachgeprueft an der laufenden Installation, nicht nur im Test: Nach dem Umbau schreibt Instance-Test nach Instances/Instance-Test/Logs, das alte gemeinsame Verzeichnis bekommt keine Zeile mehr, und die Warnung zu den aufgegebenen Schluesseln erscheint genau einmal. Enthaelt ausserdem, was seit der Weboberflaeche liegengeblieben war: - #526: Instanzgeheimnisse wurden beim Speichern verschluesselt und beim Laden nie entschluesselt. LoadInstanceConfig hatte kein ConfigSecrets.Unprotect; ConfigLoader.LoadAsync macht es richtig, wird zur Laufzeit aber gar nicht aufgerufen. Der OpenRouter-Schluessel ging so verschluesselt an die API. - Ein offener Ereignisstrom hielt das Beenden fuenf Sekunden auf. Die Stroeme hingen nur an RequestAborted, das beim geordneten Herunterfahren nicht feuert; jetzt zusaetzlich an ApplicationStopping. Der Fang in WebUiHost gilt dazu ohne Bedingung auf RequestAborted -- mit ihr waere jeder offene Strom beim Beenden als Fehler im Bugtracker gelandet. - Nach der Anmeldung blieb die Weboberflaeche leer: #anmeldung { display:flex } schlaegt als ID-Selektor das hidden-Attribut. - docs/Instanzmanager-Konzept.md: Befund, die Entscheidungen E-M1 bis E-M6, der Schnitt zwischen Manager und Instanz und die sechs Stufen. Build ohne Warnungen, 709 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |