Der Stand lag ueber eine Bestandsaufnahme, drei Konzeptpapiere, vier Umsetzungsplaene und zwei Deploymentcenter-Dokumente verteilt - jedes mit eigener Reihenfolge, teils widersprueglich. Alle offenen Punkte daraus sind in docs/Roadmap.md zusammengefuehrt. Aufbau der neuen Roadmap - Statusvokabular: erledigt / beschlossen-offen / Entscheidung noetig / zurueckgestellt / Idee. Damit steht das Geparkte sichtbar drin, statt in einem Konzeptpapier zu verschwinden. - Herkunft-Spalte traegt das alte Kuerzel (S4, K2, B9, T4, F-A1, C1, DC1), damit die archivierten Papiere auffindbar bleiben, ohne sie zu lesen. - Abschnitt 1 Reihenfolge, 2 offene Entscheidungen, 3 die Vorhaben nach Bereich, 4 Ideenspeicher, 5 Chronik des Erledigten, 6 Modell-Einstufung, 7 Herkunftskarte. Was dabei sichtbar wurde - Neun Entscheidungen blockieren Arbeit, ohne dass sie Aufwand kosten - allen voran Matrix oder Rocket.Chat. Sie stehen jetzt gesammelt in Abschnitt 2 statt verstreut in den Diskussionsteilen der Konzepte. - B8 (keine Tiefenbegrenzung bei AgentComm) ist unveraendert offen. Das ist keine Theorie: A haelt sein Gate, waehrend es auf B wartet - ruft B nun A, warten beide bis zum Timeout. Der Testfall A13 dafuer fehlt bis heute. - Die vier Umsetzungsplaene vom 2026-08-05 sind alle unumgesetzt und waren in keiner Roadmap verzeichnet. Archiv Sechs Dokumente ziehen nach docs/archiv/: Bestandsaufnahme, Konzepte Backup/Finanz/Analyse, Linux-Portierung-Analyse, Lizenz-HardwareId-v2 (gegenstandslos - LicenseLabrador ist ersetzt), Deploymentcenter-Review und der 2.4-Integrationsplan. Sie bleiben als Begruendung lesbar, werden aber nicht mehr fortgeschrieben; das README ordnet jedes einzeln ein und warnt, dass ihre Quelltext-Verweise ins Leere gehen koennen. Bauplan bleibt Bauplan Taskboard, Audit, Staging, Memory, Agentenkommunikation, RocketChat und Deploymentcenter-Integration bleiben in docs/ - sie sind die Detailvorgabe fuer die Umsetzung, nicht Vorhabenlisten. Jedes bekommt oben eine Zeile, die seine Rolle und den Umsetzungsstand nennt und auf die Roadmap zeigt. Dasselbe fuer die vier Umsetzungsplaene: die Reihenfolge gilt in der Roadmap, nicht im Plan. Nebenbei repariert: Taskboard-Konzept und Teststrategie verwiesen auf AgentScheduler und ToolJobScheduler, die seit der Scanner-Konsolidierung geloescht sind. Alle Dokument-Verweise in docs/ sind geprueft. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
17 KiB
Deploymentcenter-Anbindung — Durchsicht
Nachtrag 2026-08-08 — die Anbindung ist umgestellt, Server und SDK stehen auf 2.1. Abschnitt 4 und 5 sind abgearbeitet; wie es jetzt aussieht, steht in Deploymentcenter-Integration.
Mit SDK 2.1 erledigt (waren Befunde aus Abschnitt 3 bzw. aus der Durchsicht der 2.0-Anbindung):
HttpClientohne Zeitgrenze → intern 15 s. Unsere Umgehung (eigener Client mit 8 s) ist zurückgebaut.- HTTP 429/5xx entzogen die Lizenz, ohne den Zwischenspeicher zu befragen → jeder Nicht-Erfolg führt jetzt in denselben Offline-Zweig,
IsTransientmacht den Unterschied sichtbar. Unsere Behelfsprüfung aufunknown_errorist entfernt.cache_ttl_hourswurde ignoriert, die Gnadenfrist war faktisch unbegrenzt.app_versionfest"1.0.0"→ kommt jetzt ausReleaseInfo.Version.BuildInfo.targetswar nicht einbindbar (CS0433/CS0103) → erzeugt die Klasse im eigenen Namensraum, ist eingebunden.UpdateClient: API-Zweig las snake_case in ein camelCase-Modell → eigenes ModellApiReleaseInfo,is_criticalvon der obersten Ebene.DeactivateAsyncschickte den Shared Key zusätzlich alsX-Watchdog-Key.- Kein
CancellationTokenin der Lizenz-API.Weiterhin offen — betrifft das Deploymentcenter, nicht ClawdDotNet:
- 2.1 (keine Signaturprüfung) — unverändert.
LicenseInfo.PublicKeyBase64ist gestrichen, damit nichts Totes stehenbleibt und niemand Schutz vermutet, wo keiner ist. Kommt die Signatur, kommt das Feld mit ihr zurück.- 2.2 (v1-Ersatzhash) und 2.3 (Klartext-Rückfall) — unverändert, beides im SDK zu beheben.
- 3 (HW-ID bei jedem Aufruf neu) — clientseitig umgangen: einmal berechnet und behalten.
parent_sourceist nur einesource, kein Paar — damit schließen sich „ein Monitor je Instanz" und instanzweise Alarmunterdrückung gegenseitig aus.
Stand: 2026-08-06. Geprüft: J:\Softwareprojekte\Deploymentcenter (Client, Server,
Schema, beide Integrationsleitfäden) gegen den
HW-ID-v2-Vorschlag und die
Linux-Analyse.
Ergebnis vorweg: Die Lizenz blockiert den Linux-Umzug nicht mehr. Alles, was an Hardware-ID v2 plattformrelevant war, ist da und richtig. Was hier steht, sind Punkte aus derselben Durchsicht — drei davon würden beim Ausrollen wehtun.
1. Was erledigt ist
| Punkt aus dem Vorschlag | Umsetzung |
|---|---|
Format 2:<plattform>:<hex> |
HardwareId.cs:125 |
Kein MachineName im Hash |
ComputeV2Hash, :138 — der wichtigste Punkt, sauber umgesetzt |
| Quellenkette Windows/Linux | :42–107, inklusive dmi-uuid |
IsPlausibleMachineId (Länge, uninitialized, nur Nullen) |
:161 |
| MAC-Filter über locally-administered-Bit | :224 |
/sys/class/net/<name>/device-Prüfung |
:228 |
| Erweiterte Stoppwortliste | :23 — inkl. br-, virbr, cni, cali |
machine.key mit 0600 |
:290, SetUnixPermissions mit #if NET8_0_OR_GREATER |
| Vorgabe per Umgebungsvariable | LicenseConfig.HardwareIdOverride, beide Namen |
| XDG-Auflösungskette, nie leerer Pfad | LicenseConfig.cs:28, mit ValidateNonEmpty |
Mehrfachziel netstandard2.0;net8.0 |
csproj, BouncyCastle nur im netstandard-Zweig |
LLS2-Hülle, AES-GCM, HKDF |
StateStore.cs:169 — Schlüssel aus HW-ID abgeleitet, bindet den Cache also echt an die Maschine |
ILicensePrompt + Konsolenfassung |
vorhanden — genau das, was der kopflose Host braucht |
| Servermigration v1→v2 | LicenseService.php:108, mit Prüfprotokolleintrag hwid_migrated |
Schema hwid_version/hwid_source/platform |
sql/migrations/v2_hardware_id.sql, rückwärtskompatibel |
| Verwaltungsansicht zeigt Quelle/Plattform | public/index.php:1227 |
OperatingSystemHelpers nutzt jetzt RuntimeInformation. Der Client hat auf dem
Linux-Pfad keine Windows-Laufzeitabhängigkeit — ProtectedData wird nur unter
IsWindows() aufgerufen.
Für die Portierung heißt das: Punkt 4 aus der Entscheidungsliste der Linux-Analyse („Erlaubt LicenseLabrador den Wechsel der Hardware-ID?") ist beantwortet. Der Aufwandsblock „Lizenz" schrumpft von 3–5 PT auf 2–3 PT — das ist jetzt reine Anschlussarbeit in ClawdDotNet, keine Konzeptarbeit mehr.
2. Drei Befunde, die vor dem Ausrollen geklärt sein sollten
2.1 Es wird nichts signiert — die Lizenzprüfung ist eine Vertrauensfrage an DNS
string status = root.TryGetProperty("status", out var sProp) ? sProp.GetString() ?? "unknown" : "unknown";
if (status.Equals("valid", StringComparison.OrdinalIgnoreCase))
{
// → gültig
}
Das ist die vollständige Prüfung. Es gibt im neuen Client kein Signature.cs,
keinen hinterlegten öffentlichen Schlüssel, keine Hüllenprüfung — die Dateien
Signature.cs, LicenseResult.cs und LicenseState.cs aus dem alten
LicenseLabrador-SDK sind beim Umzug nicht mitgekommen.
Folge: Wer die HTTP-Anfrage umlenken kann, hat eine gültige Lizenz. Ein Eintrag
in /etc/hosts, ein Proxy, ein eigener DNS — die Antwort {"status":"valid"}
genügt. Auf einem Linux-Server, den der Betreiber ohnehin vollständig
kontrolliert, ist das kein Kunststück.
Serverseitig sieht es passend dazu aus. public/index.php:45:
'signature' => 'ED25519_SIG_' . base64_encode(hash('sha256', $lic['license_key'] . 'DC_OFFLINE_SECRET', true))
Das ist ein SHA-256 über den Lizenzschlüssel plus eine fest verdrahtete
Zeichenkette — keine Signatur, sondern ein Wert, der jeder erzeugen kann, der den
Quelltext kennt. Und public/index.php:1993 im JavaScript:
"ED25519_SIG_" + btoa(key + hwId).substring(0, 32)
Base64 der Eingabe, abgeschnitten. Auch kein Hash.
Das ist erkennbar ein Platzhalter — nur trägt er einen Namen, der nach fertigem Verfahren klingt, und darauf verlässt sich LicenseGate mit seiner harten Startsperre. Es ist keine Portierungsfrage (unter Windows gilt heute dasselbe) und auch kein Grund, den Linux-Umzug aufzuhalten — aber es sollte eine bewusste Entscheidung sein und nicht in dem Glauben untergehen, die Signaturprüfung sei bereits da.
Wenn das Verfahren zurückkommen soll: Ed25519 über die kanonisch serialisierte
Antwort, öffentlicher Schlüssel im Client einkompiliert, nonce aus der Anfrage
in der signierten Nutzlast gegenprüfen (gegen Wiedereinspielung). Der alte
Signer.php und Signature.cs sind im LicenseLabrador-Repo noch vorhanden und
lassen sich als Vorlage nehmen.
2.2 Der v1-Ersatzhash trifft die alten Aktivierungen nicht
Der Migrationsweg ist auf beiden Seiten korrekt gebaut — er wird nur nie auslösen, weil der Client eine andere v1-ID berechnet als die, die in der Datenbank steht.
Alt (LicenseLabrador/HardwareId.cs:20):
rawBuilder.Append(machineId); // MachineGuid, sonst MAC
rawBuilder.Append(Environment.MachineName); // direkt angehängt, kein Trenner
→ sha256(machineGuid + machineName)
Neu (HardwareId.cs:149):
string raw = $"{Environment.MachineName}:{firstMac}";
→ sha256(machineName + ":" + mac)
Andere Reihenfolge, anderer Trenner, und MAC statt MachineGuid. Auf jedem
Windows-Rechner, auf dem MachineGuid lesbar war — also praktisch allen —
stimmen die Hashes nicht überein. Der Server sucht die Altaktivierung, findet
nichts und legt eine neue an: genau der Platzverbrauch, den die Migration
verhindern sollte. Bei max_activations = 2 ist danach ein Platz für den
Linux-Server weniger da.
Auch GetFirstPhysicalMacLegacy (:254) weicht ab: keine Stoppwortfilterung,
keine Sortierung, erste Schnittstelle in Aufzählungsreihenfolge. Die alte
Fassung nahm die alphabetisch erste gefilterte MAC.
Zu tun: GetLegacyHardwareId() muss den v1-Algorithmus zeichengenau
nachbilden — inklusive der alten Stichwortliste (virtual, veth, docker,
hyper-v, wsl, mullvad, wireguard, tap, tun, vpn, bluetooth,
vmware, box, pseudo, loopback, npcap, pcap), OrderBy(…, Ordinal)
und FirstOrDefault(). Der Code steht im LicenseLabrador-Repo noch da und kann
weitgehend übernommen werden.
Am besten mit einem Test absichern, der einen bekannten Eingabewert gegen den erwarteten v1-Hash prüft — sonst fällt eine Abweichung erst auf, wenn die Aktivierungsplätze schon verbraucht sind.
2.3 Der Klartext-Rückfall ist noch da, nur woanders
StateStore.cs:79 — „Legacy Migration Check":
string legacyJson = Encoding.UTF8.GetString(payloadBytes);
var legacyData = JsonSerializer.Deserialize<LocalCacheData>(legacyJson);
if (legacyData != null)
{
legacyData.SchemaVersion = 2;
Save(productSlug, hardwareId, legacyData);
return legacyData;
}
Der LLS2-Zweig darüber ist genau richtig — Entschlüsselung fehlgeschlagen heißt
Cache-Fehltreffer, kein Klartext. Der Zweig darunter hebt das wieder auf: Jede
Datei ohne LLS2-Kennung wird als JSON gelesen und, wenn sie sich deserialisieren
lässt, übernommen und anschließend verschlüsselt neu geschrieben.
Durchgespielt: Eine von Hand angelegte state.dat mit
{"SchemaVersion":2,"Status":"valid","ExpiresAt":99999999999,"MaxSeenTime":0}
wird angenommen. In ValidateAsync greift bei fehlender Verbindung der
Cache-Zweig (:110): Status == "valid" ✓, now < MaxSeenTime ✗, now > ExpiresAt ✗ → IsValid = true. Die Bindung an die Hardware, die
DeriveKey(hardwareId, …) sonst herstellt, ist auf diesem Weg umgangen; die
Datei ist zwischen Maschinen übertragbar.
Der Zweig hilft dabei nicht einmal beim eigentlichen Zweck. Die alte
LocalCacheData hieß last_envelope, max_seen_time, endpoints,
last_license_key; die neue SchemaVersion, Status, ExpiresAt, … Kein
gemeinsames Feld, und JsonSerializer ist ohne
PropertyNameCaseInsensitive/JsonPropertyName bei den Namen streng. Eine echte
v1-Datei ergibt also ein Objekt mit lauter Vorgabewerten (Status = "invalid")
und ist als Cache wertlos.
Empfehlung: den Zweig ersatzlos streichen. Er kostet Sicherheit und leistet nichts. Alte Cachedateien sollen verworfen werden — eine einmalige Online-Prüfung ist der ganze Preis.
Nebenbei: Checksum = hwInfo.HardwareId (LicenseClient.cs:79)
ist keine Prüfsumme, sondern eine Kopie der HW-ID. Das Feld ist damit ohne
Funktion — entweder mit einem HMAC über die übrigen Felder füllen oder entfernen,
damit niemand später Schutz vermutet, wo keiner ist.
3. Kleinere Punkte
| Fundstelle | Sache |
|---|---|
| LicenseClient.cs:26 | Eigener HttpClient je Instanz, nie freigegeben, ohne Zeitgrenze (Vorgabe 100 s). Der alte LicenseConfig.HttpTimeout war 6 s. In LicenseGate.RunStartupCheck bedeutet das bis zu 100 s Standbild beim Start, wenn der Server nicht antwortet. |
:107 |
catch (Exception ex) um den gesamten Block: Auch ein Fehler beim Auswerten einer erfolgreichen Antwort landet im Offline-Zweig. Ein defekter Server gilt dann als „offline". |
:47 |
app_version = "1.0.0" fest verdrahtet. ClawdDotNet hat BuildInfo.Build — sollte Parameter sein, sonst steht in der Verwaltungsansicht bei jeder Instanz dasselbe. |
:32, :172 |
HardwareId.GetHardwareId() bei jedem Aufruf neu: liest unter Linux Dateien und zählt Netzwerkschnittstellen auf. Einmal berechnen und halten. |
HardwareId.cs:205 |
MAC-Auswahl überspringt Schnittstellen, die nicht Up oder Unknown sind. Ein Kabel, das beim Start nicht steckt, ändert damit die Hardware-ID. Für die Ausweichlösung sollte der Betriebszustand keine Rolle spielen — sonst ist sie genau in dem Moment instabil, in dem sie gebraucht wird. |
HardwareId.cs:231 |
/sys/class/net/<name>/device ist ein Symlink. Directory.Exists/File.Exists folgen ihm — funktioniert, ist aber Zufall und sollte kommentiert sein. |
4. Watchdog: die Anbindung passt noch nicht
Kein Linux-Thema, fällt aber in dieselbe Umbauarbeit.
WatchdogClient.cs sendet an:
| ClawdDotNet | Deploymentcenter |
|---|---|
POST /api/heartbeat |
POST /api/watchdog/v1/ping (nimmt auch /heartbeat) |
POST /api/event |
POST /api/watchdog/v1/event |
POST /api/register |
existiert nicht |
Die Pfade sind also alle um /watchdog/v1 zu ergänzen. Der Kopfzeilenname passt:
public/api/watchdog/v1/index.php:30 akzeptiert X-Watchdog-Key,
Authorization und X-Agent-Token.
Der Selbstregistrierungsweg aus Program.cs:334 — mit dem
Master-Token einen eigenen Agent-Token holen und in der Instanzkonfiguration
zwischenspeichern — hat serverseitig kein Gegenstück mehr. Zu klären: Tokens
künftig von Hand in der Verwaltung anlegen und in die Instanzkonfiguration
eintragen, oder /register im Deploymentcenter nachziehen. Für den ersten Weg
spricht, dass er den Master-Token gar nicht erst auf die Instanzen verteilt.
Die Feldnamen des Ping-Rumpfs (source, instance, type, status, message,
interval, group, os) sind gegen
InstanceHealthProvider
abzugleichen.
5. Was in ClawdDotNet zu tun ist
| Datei | Was |
|---|---|
| ClawdDotNet.csproj | Projektverweis von ..\LicenseLabrador\client-dotnet\… auf ..\Deploymentcenter\client-dotnet\Deploymentcenter.Client\… umhängen. Langfristig als Submodul unter external/ — der Kommentar dazu steht schon im csproj. |
| Services/LicenseGate.cs | Neu gegen LicenseValidationResult schreiben. LicenseState gibt es nicht mehr, Status ist jetzt eine Zeichenkette — DescribeProblem (:125) muss auf revoked/expired/activation_limit/not_found umgestellt werden. MessageBox durch ILicensePrompt ersetzen; die Konsolenfassung bringt der Client mit. |
| Services/LicenseInfo.cs | PublicKeyBase64 hat ohne Signaturprüfung keine Funktion mehr — entweder mit 2.1 zurückholen oder streichen, damit nichts Totes stehenbleibt. |
| Program.cs:112 | Lizenzprüfung so verlagern, dass sie ohne Fenster auskommt (kopfloser Host). |
| Host (neu) | --license-status, --license-set-key, --license-deactivate — der Client bringt alles Nötige mit. |
| WatchdogClient.cs | Pfade auf /api/watchdog/v1/…; Registrierungsweg klären (Abschnitt 4). |
docs/Integrationsplan-WatchDog-LicenseLabrador.md |
Abgelöst durch Deploymentcenter-Integration. |
6. Antwort auf die Ausgangsfrage
Ja — Avalonia und Linux sind damit machbar. Die einzige Frage, die ich als
möglicher Blocker außerhalb unserer Hand markiert hatte, ist geklärt: Der Client
läuft auf beiden Plattformen, zielt auf net8.0 (von net10.0 problemlos
verwendbar), löst seinen Ablageort auch ohne HOME auf, und die HW-ID ist
container- und umbenennungsfest.
Der Lizenzblock in der Aufwandsschätzung fällt von 3–5 PT auf 2–3 PT. Die Gesamtspanne bleibt bei 50–80 PT, weil die Lizenz nie der große Posten war — das sind PropertyGrid und Chat-Ansicht.
Zwei Dinge sollten aber vor dem Ausrollen erledigt sein, unabhängig von Linux:
- 2.2 (v1-Ersatzhash) — klein, aber wenn es beim Ausrollen falsch ist, sind Aktivierungsplätze verbraucht und man bekommt sie nur einzeln über die Verwaltung zurück. Das ist der Punkt mit dem schlechtesten Verhältnis von Aufwand zu Schaden.
- 2.3 (Klartext-Rückfall) — eine Zeile weniger Code, dafür wieder das
Verhalten, das der
LLS2-Umbau eigentlich herstellen sollte.
2.1 (keine Signaturprüfung) ist eine eigene Entscheidung mit eigenem Umfang
und hält den Umzug nicht auf. Sie sollte nur getroffen und nicht übersehen
werden — der Name ED25519_SIG_ im Serverquelltext legt sonst nahe, dass die
Sache erledigt sei.