Predictalytics hing noch am abgeloesten LicenseLabrador (license.mhdf.de), waehrend die Lizenzen inzwischen im Deploymentcenter (dc.mhdf.de) gepflegt werden. Vor dem Umbau mit einem Wegwerf-Probelauf gegen den echten Client abgesichert: Slug predictalytics, HWID v2 aus machine-guid, Server meldet valid. Hosting/LicenseGuard.cs auf Deploymentcenter.Client umgeschrieben. Die Aufteilung bleibt: plattformneutrale Pruefung im Hosting, interaktive Aktivierung in den Hosts. Gewinn fuer die Linux-Portierung: - Hardware-ID v2 mit Quellen-Kaskade /etc/machine-id, dbus, DMI-UUID, MAC, Schluesseldatei; Container ueber DEPLOYMENTCENTER_HWID - Der Rechnername steckt nicht mehr im Hash, Umbenennen verbraucht keinen Aktivierungsplatz - Cache mit AES-256-GCM statt DPAPI, ohne stillen Klartext-Rueckfall unter Linux. Damit erledigt sich Punkt 3.2 Nr. 2 der Portierungsanalyse. - XDG_CONFIG_HOME wird beruecksichtigt - Keine Basic-Auth-Zugangsdaten mehr im Quelltext Neue CLI-Schalter aus dem Integrationsleitfaden, ausgewertet vor jeder Avalonia-Initialisierung: --license-status, --license-set-key, --license-deactivate. Schluessel werden in der Ausgabe maskiert. Die Lizenzfenster beider Hosts zeigen jetzt die Hardware-ID an, an die der Server die Aktivierung bindet. Build: 0 Fehler. Tests: 100 bestanden. Verifiziert: --license-status meldet gueltig; die GUI startet vollstaendig durch bis MainWindowViewModel.Start(), belegt durch die Logdateien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
34 KiB
Implementierungsplan: Predictalytics auf Windows + Linux
Stand: 2026-08-06 · Basis: ANALYSE-Linux-Portierung.md Ziel: Ein Code-Stand, der unter Windows und Linux identisch läuft — Bedienhülle in Avalonia, gesamte Solution auf .NET 10.
0. Drei Entscheidungen vorab — alle getroffen (2026-08-06)
| # | Entscheidung | Ergebnis |
|---|---|---|
| 0.1 | EF Core | Weg A — TFM net10.0, EF Core 9.0.18 + Pomelo 9.0.0 |
| 0.2 | UI-Framework | Avalonia 12.1.1, kein LiveCharts2 |
| 0.3 | Linux-Ziel | Beides — Avalonia-GUI und Headless-Modus (AP 3.4 verbindlich) |
0.1 ✅ EF Core: Version 9 behalten, nicht auf 10 gehen — entschieden: Weg A
Das ist der einzige Punkt, an dem „alles auf .NET 10" nicht sauber aufgeht.
Befund (verifiziert über nuget.org am 2026-08-06):
Pomelo.EntityFrameworkCore.MySql → neueste stabile Version: 9.0.0 (2025-08-17)
nuspec: targetFramework net8.0
Microsoft.EntityFrameworkCore.Relational [9.0.0, 9.0.999] ← harte Obergrenze
Es gibt keinen Pomelo-Provider für EF Core 10. Die Versionsspanne ist nach oben geschlossen; ein Upgrade auf EF Core 10 ist mit Pomelo technisch unmöglich.
| Weg | Konsequenz |
|---|---|
A — empfohlen: TFM net10.0, EF Core 9.0.18, Pomelo 9.0.0 |
Alle Projekte laufen auf .NET 10. EF-Core-9-Pakete (net8.0) laufen problemlos auf der .NET-10-Runtime. Kein Provider-Wechsel, kein DB-Risiko. Nur +1 EF-Major statt +2. |
B — EF Core 10.0.10 + MySql.EntityFrameworkCore 10.0.9 (Oracle) |
Erzwingt den Wechsel des Datenbankproviders. Andere SQL-Generierung, andere Migrations-Historie, abweichendes Verhalten bei Typ-Mapping und Bulk-Operationen — gegen die Produktiv-DB. Realistisch +3–5 PT plus Regressionsrisiko in 94 Infrastructure-Dateien und 30+ Migrationen. |
→ Gewählt: Weg A. „Auf .NET 10 gleichbleiben" ist das eigentliche Ziel und wird
vollständig erreicht — das Zielframework aller Projekte ist net10.0. Dass die
EF-Core-Bibliothek bei 9.0.18 bleibt, ist ein Implementierungsdetail ohne Auswirkung auf
Runtime, Sprachfeatures oder Toolchain. Sobald Pomelo einen EF-Core-10-Provider
veröffentlicht, ist der Nachzug ein Einzeiler.
Weg B entfällt; ein Providerwechsel gegen die Produktiv-DB findet nicht statt.
0.2 Avalonia 12.1.1 — entschieden ✅
Avalonia 12.1.1 (2026-07-29), lib-TFMs: net8.0, net10.0 ← natives .NET 10
Avalonia.Desktop 12.1.1 · Avalonia.Themes.Fluent 12.1.1 · Avalonia.Controls.DataGrid 12.1.2
Eine Einschränkung: Avalonia.Diagnostics liegt noch bei 11.3.18, es gibt keine
12.x-Version. Das ist das DevTools-Inspektorfenster (F12) — reines Entwicklerwerkzeug, für
die Laufzeit irrelevant. Falls beim Layouten vermisst: temporär eine 11.3.18-Referenz nur
in der Debug-Konfiguration, oder ohne auskommen.
LiveCharts2 wird nicht benötigt. Die Bedienhülle enthält kein einziges Diagramm; alle Charts liegen in der Web-UI (Chart.js). Keine Chart-Bibliothek im Shell-Projekt.
0.3 ✅ Wo läuft die Linux-Instanz? — entschieden: beides
Eine Avalonia-GUI braucht unter Linux eine Desktop-Umgebung: X11 (bzw. Wayland über XWayland),
dazu libx11, libice, libsm, libfontconfig1 und installierte Fonts. Auf einem
headless Server ohne Desktop startet sie nicht.
→ Gewählt: beides in einem Binary. Die Avalonia-Shell für Desktop-Betrieb (Windows und
Linux), zusätzlich ein Schalter --headless für Server ohne Desktop-Session.
Damit ist AP 3.4 verbindlicher Bestandteil, nicht optional. Nach der Hosting-Extraktion (Phase 2) kostet er ca. 0,5 PT, weil der gesamte Kern dann bereits GUI-frei ist. Konsequenzen, die dadurch fix werden:
- Der Lizenzschlüssel muss auch ohne Dialog beziehbar sein (Konfiguration / Umgebungsvariable) — koppelt an die laufende HardwareID-Arbeit.
- Das Deployment liefert zwei Betriebsarten: Desktop-Start und systemd/Container.
- Phase 6 verifiziert beide Modi.
1. Zielarchitektur
Der entscheidende Umbau ist nicht die GUI, sondern das Herausziehen eines plattformneutralen Hosting-Kerns. Heute liegen ~870 portable Zeilen (EmbeddedWebServer, Watchdog, Serilog-Setup, Settings) im windows-gebundenen Projekt fest. Danach ist die GUI nur noch eine dünne Schale darüber — und der Headless-Modus fällt fast von selbst ab.
src/
Predictalytics.Domain net10.0 unverändert
Predictalytics.Application net10.0 unverändert
Predictalytics.Infrastructure net10.0 unverändert
Predictalytics.Api net10.0 unverändert
Predictalytics.Worker net10.0 unverändert
Predictalytics.Hosting NEU net10.0 ← plattformneutraler Kern
Predictalytics.Shell NEU net10.0 ← Avalonia-Bedienhülle (Win + Linux)
Predictalytics.Application.Tests net10.0 net10.0 statt bisher net10.0 (bleibt)
Predictalytics.WinFormsHost net10.0-windows ← Übergang, s. 6.2
Predictalytics.Hosting (neu, GUI-frei) nimmt auf:
| Herkunft | Ziel |
|---|---|
WinFormsHost/Services/EmbeddedWebServer.cs (365 Z.) |
Hosting/PredictalyticsHost.cs — unverändert bis auf Binding + wwwroot |
WinFormsHost/AppSettings.cs (137 Z.) |
Hosting/PredictalyticsOptions.cs — ohne [Category]/[Editor]-Attribute |
WinFormsHost/Services/WatchdogHeartbeatService.cs (141 Z.) |
1:1, ist bereits portabel |
WinFormsHost/Program.cs:33-148 (Serilog-Setup) |
Hosting/LoggingSetup.cs — 1:1, ist bereits portabel |
WinFormsHost/Services/LicenseGuard.cs (102 Z.) |
Hosting/LicenseGuard.cs — ohne WinForms-Timer/Dialog |
Infrastructure/Logging/RichTextBoxSink.cs |
umbenennen → DelegateSink.cs (ist schon portabel) |
Predictalytics.Shell (neu, Avalonia) enthält nur noch View + ViewModel.
2. Phase 0 — Vorarbeiten ✅ erledigt (2026-08-06)
| # | Aufgabe | Ergebnis |
|---|---|---|
| 0.1 | InvariantCulture ergänzen |
✅ LimitlessProvider.cs:270 (Volume) |
| 0.1b | Nachtrag: 13 × lokalzeit-verzerrendes DateTime.TryParse |
✅ neuer Helfer Infrastructure/Helpers/DateParsing.cs, alle Fundstellen umgestellt |
| 0.2 | Eingecheckte Logdatei entfernen | ✅ nicht nötig — Datei ist nicht getrackt, .gitignore:14 logs/ greift bereits |
| 0.3 | j:\-Absolutpfad-Fallback entfernen |
✅ EmbeddedWebServer.cs |
| 0.4 | Branch anlegen | ✅ feature/linux-net10 |
Abnahme erreicht: dotnet build → 0 Fehler, 10 Warnungen (alle vorbestehend).
dotnet test → 100 bestanden, 0 Fehler, 1 übersprungen.
Nachtrag 0.1b — Umfang und offener Punkt
DateTime.TryParse("2026-08-06T12:00:00Z") ohne DateTimeStyles liefert Kind=Local und
rechnet in Lokalzeit um. Betroffen waren in beiden Providern StartDate, EndDate,
CreatedAt, ClosedAt, ExecutedAt, ExpirationDate — während der Fallback derselben
Zuweisungen (DateTime.UtcNow) korrektes UTC schrieb. Auf der Berliner Maschine also
1–2 h Versatz, auf einem Linux-Server mit TZ=UTC hingegen nicht: derselbe Code hätte
plattformabhängig unterschiedliche Werte erzeugt.
✅ Altdaten-Backfill: entfällt (Entscheidung 2026-08-06). Der Fix wirkt nur für neue Schreibvorgänge; bereits gespeicherte Zeilen tragen den 1–2-h-Versatz weiter. Bewusst akzeptiert — die Daten verlieren mit steigendem Alter ohnehin an Relevanz, und die betroffenen Felder werden bei jedem Markt-Sync überschrieben.
Konsequenz für Phase 6: Beim Vergleich Linux-Lauf ↔ Windows-Referenz (Prüfung 8.3) dürfen historische Zeilen nicht als Referenz dienen — nur frisch geschriebene Datensätze sind aussagekräftig.
3. Phase 1 — .NET 10 ✅ erledigt (2026-08-06)
Abnahme erreicht: dotnet build → 0 Fehler, 8 Warnungen (alle vorbestehend, 2 weniger
als vorher). dotnet test → 100 bestanden, 0 Fehler, 1 übersprungen, Testhost
net10.0. Alle Projekte laufen auf net10.0, WinFormsHost auf net10.0-windows.
Ergebnis gegenüber der Planung:
| Erwartung | Tatsächlich |
|---|---|
Swashbuckle 6.9 → 10.x „wahrscheinlichster Reibungspunkt", ggf. Wechsel auf AddOpenApi() |
Keine Bruchstelle. Der genutzte API-Ausschnitt (AddSwaggerGen/SwaggerDoc/UseSwagger/UseSwaggerUI) ist stabil geblieben. Kein Umbau nötig. |
| EF Core 8 → 9 „überschaubar" | Bestätigt, keine Anpassung nötig |
| SQLite-Sicherheitswarnung „löst sich mit dem EF-Bump von selbst" | Falsch — EF Core 9.0.18 zieht SQLitePCLRaw 2.1.10, weiterhin von GHSA-2m69-gcr7-jv3q betroffen. Behoben über einen expliziten transitiven Pin auf 2.1.12. |
Bewusst vor dem Avalonia-Umbau erledigt: eine Baustelle nach der anderen, und das Shell-Projekt entsteht direkt auf dem Zielframework.
AP 1.1 — Zentrale Frameworkverwaltung ✅
Directory.Build.props setzt TargetFramework=net10.0 zentral; <TargetFramework> aus
allen .csproj entfernt (Ausnahme: WinFormsHost → net10.0-windows). Zusätzlich zentral:
SatelliteResourceLanguages=en, das bisher in vier Projekten einzeln stand.
Directory.Packages.props mit ManagePackageVersionsCentrally eingeführt — 21 Pakete,
Versionen aus allen .csproj entfernt. Zusätzlich
CentralPackageTransitivePinningEnabled, um Sicherheits-Pins auf transitive
Abhängigkeiten setzen zu können.
Nicht angefasst: Spike/Spike.csproj — leeres Restprojekt ohne Quelldateien, nicht in
der Solution, baut auch im Ist-Zustand nicht (CS5001: keine Main-Methode). Kandidat zum
Löschen, aber nicht Teil dieser Portierung.
AP 1.2 — Paketmatrix
| Paket | heute | Ziel | Anmerkung |
|---|---|---|---|
Microsoft.EntityFrameworkCore(.Relational/.Design) |
8.0.11 | 9.0.18 | Obergrenze durch Pomelo |
Pomelo.EntityFrameworkCore.MySql |
8.0.2 | 9.0.0 | zieht MySqlConnector 2.4.0 |
Microsoft.EntityFrameworkCore.InMemory/.Sqlite (Tests) |
8.0.11 | 9.0.18 | muss zu EF Core passen |
Swashbuckle.AspNetCore |
6.9.0 | 10.2.3 | ⚠️ 6.x ist nicht ASP.NET-Core-10-tauglich |
Serilog |
4.2.0 | 4.4.0 | |
Serilog.AspNetCore |
8.0.3 | 10.0.0 | |
Serilog.Sinks.File |
6.0.0 | 7.0.0 | |
Serilog.Extensions.Logging / .Sinks.Console / .Formatting.Compact |
8.0.0 / 6.0.0 / 3.0.0 | jeweils aktuell | |
Microsoft.Extensions.Http / .Hosting.Abstractions / .Logging.Abstractions |
8.0.x | 10.0.x | |
Microsoft.NET.Test.Sdk / xunit / Moq / coverlet |
aktuell | prüfen |
Tatsächlich aufgelöste Versionen (verifiziert über dotnet list package): EF Core 9.0.18,
Pomelo 9.0.0, Serilog 4.4.0 / Extensions.Logging 10.0.0 / Sinks.File 7.0.0 /
Sinks.Console 6.1.1, Swashbuckle 10.2.3, Test-SDK 18.8.1, coverlet 10.0.1.
Sicherheits-Pin: SQLitePCLRaw 2.1.12 (statt transitiv 2.1.10).
AP 1.3 — Erwartete Bruchstellen (Rückblick: keine eingetreten)
- Swashbuckle 6.9 → 10.x ist der wahrscheinlichste Reibungspunkt: geänderte API-Oberfläche,
und ASP.NET Core 10 bringt mit
Microsoft.AspNetCore.OpenApieine eigene OpenAPI-Generierung mit. BetrifftApi/ApiConfiguration.cs:19-20undWinFormsHost/Services/EmbeddedWebServer.cs:143-144,154-155. Alternative: auf das eingebauteAddOpenApi()/MapOpenApi()umsteigen und Swashbuckle ganz ablösen — sauberer, aber die Swagger-UI müsste ersetzt werden (z. B. Scalar). - EF Core 8 → 9: überschaubar. Zu prüfen sind Migrationsverhalten und die
ExecuteSqlRawAsync-Aufrufe inEmbeddedWebServer.cs:56-100. Predictalytics.Application.Testssteht bereits aufnet10.0— hier wird die Inkonsistenz gerade aufgelöst statt vergrößert.
Abnahme: dotnet build + dotnet test grün; WinFormsHost startet, Worker laufen,
Web-UI erreichbar; Migrationen gegen die Dev-DB (bergisnu_db0) verifiziert, nicht gegen Prod.
4. Phase 2 — Hosting-Kern extrahieren ✅ erledigt (2026-08-06)
Abnahme erreicht: dotnet build → 0 Fehler. dotnet test → 100 bestanden, 0 Fehler,
1 übersprungen. Predictalytics.Hosting baut ohne Windows-Bezug; WinFormsHost enthält
nur noch UI-Code (MainForm, LicenseDialog, TerminalHelper, Program) und referenziert
Api/Worker/Infrastructure/LicenseLabrador nur noch transitiv über Hosting.
⚠️ Betrieblicher Hinweis: settings.json ging verloren
Beim Aufräumen der Build-Ausgabe in Phase 1 wurde bin/ gelöscht — und dort lag die zur
Laufzeit erzeugte settings.json mit DB-Zugangsdaten und Watchdog-API-Key. Die Datei war
nie versioniert (.gitignore:11) und ist nicht wiederherstellbar.
Beim nächsten Start neu einzutragen: DB Server/Database/User/Password, Watchdog API Key, ggf. Egress-Kanäle.
Der Fehler kann sich nicht wiederholen: durch AP 2.3 liegen die Einstellungen jetzt unter
%APPDATA%\Predictalytics\settings.json bzw. ~/.config/Predictalytics/settings.json —
außerhalb des Build-Verzeichnisses. dotnet clean oder ein Löschen von bin/ sind damit
folgenlos.
Abweichungen von der Planung
| Geplant | Tatsächlich |
|---|---|
Hosting als Microsoft.NET.Sdk.Web mit OutputType=Library (analog Api) |
Microsoft.NET.Sdk + <FrameworkReference Include="Microsoft.AspNetCore.App" />. Der Web-SDK globbt wwwroot/** automatisch als Static Web Asset und kollidierte mit dem Content-Eintrag aus AP 2.5 (DiscoverPrecompressedAssets: doppelter Schlüssel). Gilt auch für den Host: WinFormsHost ist jetzt ebenfalls normaler SDK. Für die Avalonia-Shell gleich so anlegen. |
Process.Start("explorer.exe", …) erst in AP 3.3 ersetzen |
Vorgezogen — MainForm wurde ohnehin umgeschrieben. Jetzt ProcessStartInfo { UseShellExecute = true }, funktioniert unter Windows und Linux. |
LicenseGuard komplett nach Hosting |
Aufgeteilt: der plattformneutrale Teil liegt in Hosting/LicenseGuard.cs, der interaktive Dialogaufruf in WinFormsHost/Services/LicenseGate.cs. Genau die Naht, an der Phase 3 den Avalonia-Dialog bzw. den Headless-Pfad einhängt. |
[Editor]-Attribut für den mehrzeiligen Egress-Editor erhalten |
Entfallen. Es verwies auf die .NET-Framework-Assembly System.Design, die es in .NET (Core) nicht gibt — der Editor dürfte schon vorher nicht gegriffen haben. Die Avalonia-Settings-View bekommt ein echtes mehrzeiliges Textfeld. Alle übrigen System.ComponentModel-Attribute sind plattformneutral und bleiben, das PropertyGrid behält Gruppen und Beschreibungen. |
Zusätzlich mitgenommen
PredictalyticsOptions.WebserverHost— Bind-Adresse konfigurierbar (Vorbereitung AP 4.1), Defaultlocalhost, also verhaltensgleich.PredictalyticsOptions.DbSslMode— Default bewusstNone, damit sich das Verbindungs- verhalten in einem reinen Refactoring nicht ändert. Umstellung aufPreferredist jetzt eine reine Konfigurationsfrage.PredictalyticsHost.GetDatabaseSizeMbAsync()— die DB-Abfrage lag vorher direkt imMainForm.RichTextBoxSink→DelegateSinkumbenannt (war nie WinForms-abhängig).
Reiner Verschiebe- und Entkopplungsvorgang, keine neue Fachlogik.
AP 2.1 — Projekt Predictalytics.Hosting anlegen
net10.0, Referenzen auf Api, Worker, Infrastructure, LicenseLabrador.Client.
AP 2.2 — Verschieben (Tabelle in Abschnitt 1)
EmbeddedWebServer → PredictalyticsHost, Watchdog, Serilog-Setup, LicenseGuard.
RichTextBoxSink → DelegateSink umbenennen (der Sink ist ein reiner
Action<string, LogEventLevel>-Delegat und war nie WinForms-abhängig).
AP 2.3 — Konfiguration entkoppeln
AppSettings → PredictalyticsOptions ohne System.ComponentModel-Attribute.
Ladereihenfolge: appsettings.json → appsettings.{Environment}.json →
Umgebungsvariablen (PREDICTALYTICS_) → settings.json im Benutzerprofil (Kompatibilität).
Ablageort plattformabhängig über Environment.SpecialFolder.ApplicationData
(Windows: %APPDATA%, Linux: ~/.config) statt neben der Executable — das
Installationsverzeichnis ist unter Linux üblicherweise nicht beschreibbar.
⚠️ Beim Umbau ist SslMode = MySqlSslMode.None (AppSettings.cs:100) fest verdrahtet.
Da die Zugangsdaten künftig auch über Netz zu einer entfernten DB gehen, sollte das
konfigurierbar und standardmäßig aktiv werden.
AP 2.4 — Lifecycle GUI-frei machen
System.Windows.Forms.Timer→PeriodicTimerbzw.IHostedService(MainForm.cs:71DB-Größe,LicenseGuard.cs:70Revalidierung)Application.Exit()→IHostApplicationLifetime.StopApplication()PredictalyticsHostbekommt Events/Observables (StatusChanged,DbSizeChanged), an die sich später sowohl das Avalonia-ViewModel als auch der Headless-Modus hängen.
AP 2.5 — wwwroot sauber deployen
FindWwwrootPath() (EmbeddedWebServer.cs:308-335) mit der Verzeichnis-Heuristik ersatzlos
streichen. Stattdessen in den Host-.csproj:
<Content Include="..\Predictalytics.Api\wwwroot\**"
Link="wwwroot\%(RecursiveDir)%(Filename)%(Extension)"
CopyToOutputDirectory="PreserveNewest" />
und im Code nur noch Path.Combine(AppContext.BaseDirectory, "wwwroot").
Abnahme: WinFormsHost benutzt nur noch Predictalytics.Hosting und enthält
ausschließlich UI-Code. Predictalytics.Hosting baut ohne Windows-Referenzen.
5. Phase 3 — Avalonia-Bedienhülle ✅ erledigt (2026-08-07)
Abnahme: dotnet build → 0 Fehler, 0 Warnungen. dotnet test → 100 bestanden.
Beide Betriebsarten laufen an:
| Prüfung | Ergebnis |
|---|---|
Predictalytics.Shell.exe --headless |
Logging läuft, Lizenzprüfung greift, sauberer Abbruch mit Exit-Code 2 und handlungsfähiger Meldung |
Predictalytics.Shell.exe (GUI) |
startet, Lizenzfenster erscheint, kein Absturz |
| Konsolen-Kodierung | UTF-8 explizit gesetzt — ohne das zeigt die Windows-Konsole nur Fragezeichen statt Rahmen und Emoji |
Nachtrag 2026-08-07: Hauptfenster verifiziert ✅
Zunächst blockierte eine abgelaufene Lizenz die Prüfung. Nach der Umstellung auf das Deploymentcenter (Abschnitt 5a) startet die GUI vollständig durch — belegt durch die Logdateien des Laufs:
[INF] Shell initialisiert. Bereit.
[INF] 'Start Webserver' startet die WebUI auf http://localhost:5000
[INF] Einstellungen: C:\Users\bergm\AppData\Roaming\Predictalytics\settings.json
Damit sind Lizenzschranke, Serilog-Aufbau mit Terminal-Sink, MainWindowViewModel.Start()
und der neue Einstellungspfad nachgewiesen. Noch nicht durchgeklickt: die einzelnen
Wartungsaktionen und die Einstellungsansicht — dafür fehlen die in Phase 1 verlorenen
DB-Zugangsdaten.
Umsetzung
| Datei | Zweck |
|---|---|
Program.cs |
Einstiegspunkt. Argumentauswertung vor jeder Avalonia-Initialisierung — sonst stirbt der Prozess auf einem Server ohne X11, bevor --headless greift. |
App.axaml(.cs) |
Lizenzschranke, dann Hauptfenster. ShutdownMode.OnExplicitShutdown während der Aktivierung, danach OnMainWindowClose. |
ViewModels/MainWindowViewModel.cs |
Bindungsziel, CommunityToolkit.Mvvm-Quellgeneratoren, kapselt PredictalyticsHost |
ViewModels/LogLine.cs |
Terminalzeile mit Einfärbung nach Loglevel (Farbschema aus WinForms übernommen) |
Views/MainWindow.axaml |
Menü, Werkzeugleiste, Terminal-Tab, Einstellungen-Tab, Statusleiste |
Views/LicenseWindow.axaml |
Aktivierungsfenster, ersetzt LicenseDialog |
Views/Dialogs.cs |
Ersatz für MessageBox.Show — im Code aufgebaut, kein Drittanbieterpaket |
Services/HeadlessRunner.cs |
AP 3.4: Betrieb ohne Oberfläche |
Services/ConsoleAttach.cs |
Windows-Konsole anhängen + UTF-8 |
Abweichungen und Entscheidungen
PropertyGrid-Ersatz handgeschrieben, wie empfohlen. Vier Gruppen alsHeaderedContentControl, Passwortfelder mitPasswordChar, echtes mehrzeiliges Textfeld für die Egress-Kanäle (besser als das in Phase 2 entfallene[Editor]-Attribut). SSL-Modus alsComboBox; bewusst nurNone/Preferred/Required—VerifyCA/VerifyFullbräuchten hinterlegte Zertifikate und würden hier nur zu Fehlkonfiguration einladen.- Speichern ist jetzt explizit („Speichern & übernehmen") statt wie beim
PropertyGridbei jeder Einzeländerung. Klarer, und erspartINotifyPropertyChangedaufPredictalyticsOptions. - Feste Fenstergröße aufgelöst (vorher
1886×1088fix): jetztMinWidth/MinHeightmit skalierendemDockPanel/Grid. - Schrift-Fallbackkette
Cascadia Code, DejaVu Sans Mono, Liberation Mono, Consolas, monospacefürs Terminal;Avalonia.Fonts.Interals mitgelieferte UI-Schrift, weil Linux-Distributionen sehr unterschiedliche Standardfonts haben. - Ringpuffer statt Leeren: die WinForms-Fassung verwarf bei 500 Zeilen das gesamte
Terminal (
rtb.Clear()), jetzt fällt jeweils nur die älteste Zeile heraus. Avalonia.Diagnosticsnicht referenziert — gibt es nur bis 11.3.18, nicht für 12.x.global::Avalonia.Application: innerhalb vonPredictalytics.*löst der kurze NameApplicationauf den eigenen NamespacePredictalytics.Applicationauf. Betrifft jede künftige Avalonia-Klasse in diesem Projekt.Microsoft.NET.SdkstattSdk.Web, wie in Phase 2 gelernt (Static-Web-Assets-Kollision mit demwwwroot-Content-Eintrag).
Headless-Betrieb
Lizenz über PREDICTALYTICS_LICENSE_KEY; ohne nutzbare Lizenz Abbruch mit Exit-Code 2
statt eines Dialogs, den niemand sieht. SIGTERM und SIGINT werden über
PosixSignalRegistration abgefangen, damit systemd sauber stoppen kann.
Exit-Codes: 0 planmäßig beendet · 2 keine nutzbare Lizenz · 3 Abbruch mit Fehler.
AP 3.1 — Projekt Predictalytics.Shell
<PackageReference Include="Avalonia" Version="12.1.1" />
<PackageReference Include="Avalonia.Desktop" Version="12.1.1" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.1" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
MVVM mit CommunityToolkit.Mvvm (Source-Generatoren) — bei ~15 Settings-Properties plus
Status-Bindings spart das spürbar Boilerplate gegenüber handgeschriebenem INotifyPropertyChanged.
AP 3.2 — Control-Mapping
| WinForms (heute) | Avalonia | Aufwand |
|---|---|---|
MenuStrip + 3 Menüs |
Menu / NativeMenuBar |
klein |
ToolStrip + 2 Buttons |
StackPanel + Button |
klein |
TabControl / TabPage |
TabControl / TabItem |
klein |
StatusStrip + 3 Labels |
DockPanel unten + TextBlock |
klein |
MessageBox.Show (7 Aufrufe) |
eigener Dialog oder MsBox.Avalonia |
klein |
RichTextBox (farbiges Log) |
ListBox/ItemsControl über ObservableCollection<LogLine>, DataTemplate mit Foreground-Binding |
mittel |
PropertyGrid |
kein Äquivalent in Avalonia Core | mittel–groß |
Zum Log-View: die bisherige Ringpuffer-Logik (TerminalHelper.cs:26-31 — bei 500 Zeilen
rtb.Clear()) wird zur ObservableCollection mit RemoveAt(0) — angenehmer, weil die
Historie nicht mehr komplett verworfen wird. Marshalling über Dispatcher.UIThread.Post
statt Control.BeginInvoke. Farbschema aus TerminalHelper.cs:33-39 direkt übernehmbar.
Zum PropertyGrid — das ist der einzige echte UI-Entwurfsaufwand. Drei Optionen:
| Option | Bewertung |
|---|---|
| Handgeschriebene Settings-View (empfohlen) | 4 Gruppen / 15 Properties (Webserver 2, Egress 1, Watchdog 6, Database 4 + abgeleiteter ConnectionString). Als Expander je Gruppe mit TextBox/CheckBox/NumericUpDown. Volle Kontrolle über Passwortfelder (PasswordChar) und Mehrzeilen-Editor für Egress. ~1–1,5 PT. |
Drittanbieter Avalonia.PropertyGrid |
Spart Zeit, bringt aber eine Abhängigkeit fragwürdiger Pflegelage und passt selten zum Fluent-Theme. |
| Reflection-basierter Generator | Elegant, aber Overengineering für 15 Properties. |
AP 3.3 — Plattformdetails
- Fixe Fenstergröße auflösen:
MinimumSize/MaximumSize = 1886×1088(MainForm.Designer.cs:233-234) ist bei abweichender DPI/Auflösung unter Linux untauglich. → skalierendes Grid-Layout, sinnvollesMinWidth/MinHeight. - Schriftart:
"Cascadia Code"(MainForm.cs:25) existiert unter Linux meist nicht. → Fallback-KetteCascadia Code, DejaVu Sans Mono, monospace. Process.Start("explorer.exe", …)(3×,MainForm.cs:192,207,209) →funktioniert auf Windows und Linux (dort überProcess.Start(new ProcessStartInfo(target) { UseShellExecute = true });xdg-open).- Lizenzdialog (
LicenseDialog.cs, 118 Z.) in Avalonia nachbauen: Label,TextBox(Großschreibung —CharacterCasing.Upperhat kein direktes Pendant, perTextInput-Handler oder Converter lösen), Status-Label, zwei Buttons. Der Aktivierungsablauf (ValidateAsync+VerifyChecksum) bleibt unverändert. Hinweis: greift auf die HardwareID-Arbeit zu, die separat läuft.
AP 3.4 — Headless-Modus (verbindlich, 0,5 PT — Entscheidung 0.3)
Schalter --headless in Program.cs der Shell: statt BuildAvaloniaApp().StartWithClassicDesktopLifetime()
direkt PredictalyticsHost starten und auf IHostApplicationLifetime warten, Logs nur auf
Console. Nach Phase 2 ist das reine Verdrahtung, weil der Kern bereits GUI-frei ist.
Zwingend mitzudenken:
- Lizenz ohne Dialog: Schlüssel aus
PREDICTALYTICS_LICENSE_KEYbzw. Konfiguration; bei fehlender/ungültiger Lizenz sauberer Abbruch mit Exit-Code ≠ 0 und klarer Logzeile statt eines Dialogs, den niemand sieht. - Keine Avalonia-Initialisierung vor der Argumentauswertung — sonst versucht der Prozess auf einem Server ohne X11 zu starten und stirbt, bevor der Schalter greift.
- SIGTERM sauber behandeln (
IHostApplicationLifetime), damit systemd korrekt stoppt.
Abnahme: Shell startet unter Windows und Linux-Desktop mit GUI; derselbe Build
startet mit --headless auf einem Server ohne X11. In beiden Modi: Worker laufen, Web-UI
erreichbar, Settings persistieren; im GUI-Modus zusätzlich farbiger Log-View und
Lizenzdialog.
5a. Zusatz — Umstellung auf das Deploymentcenter-Lizenzsystem ✅ (2026-08-07)
Nicht ursprünglich geplant. Beim Aktivieren der neuen Lizenz stellte sich heraus, dass
Predictalytics noch am alten Lizenzsystem hing (LicenseLabrador, license.mhdf.de),
während die Lizenzen inzwischen im Deploymentcenter (dc.mhdf.de) gepflegt werden.
Vor dem Umbau mit einem Wegwerf-Probelauf gegen den echten Client abgesichert:
Slug predictalytics, HWID v2 aus machine-guid, Server meldet valid.
Was sich geändert hat
| vorher (LicenseLabrador) | jetzt (Deploymentcenter) | |
|---|---|---|
| Endpunkt | license.mhdf.de/public/api/v1 |
dc.mhdf.de/api/license/v1 |
| Client | LicenseLabrador.Client (netstandard2.0) |
Deploymentcenter.Client (netstandard2.0;net8.0) |
| Authentifizierung | Ed25519-Signaturprüfung + Basic Auth im Quelltext | serverseitig, kein Geheimnis im Client |
| Hardware-ID | SHA-256 aus MachineGuid + Rechnername | v2: 2:<plattform>:<hex>, ohne Rechnername |
| Linux-HWID | /etc/machine-id, sonst MAC |
/etc/machine-id → dbus → DMI-UUID → MAC → Schlüsseldatei |
| Container | nicht vorgesehen | DEPLOYMENTCENTER_HWID überschreibt |
| Cache | DPAPI, stiller Klartext-Rückfall unter Linux | AES-256-GCM, HKDF aus HWID+Slug, kein Klartext-Rückfall, 0600 unter Linux |
| Ablageort | %APPDATA% |
zusätzlich XDG_CONFIG_HOME unter Linux |
Damit erledigt sich Analyse-Punkt 3.2 Nr. 2 von selbst: der stille Klartext-Rückfall des Lizenz-Caches unter Linux existiert im neuen Client nicht mehr. Der Rechnername steckt außerdem nicht mehr im Hash — Umbenennen verbraucht keinen Aktivierungsplatz.
Neue CLI-Schalter (Services/LicenseCli.cs)
Aus dem Deploymentcenter-Integrationsleitfaden übernommen, für Server ohne Oberfläche:
Predictalytics.Shell --license-status # HWID, Cache-Ablage, Serverprüfung
Predictalytics.Shell --license-set-key <SCHLÜSSEL> # einmalig aktivieren
Predictalytics.Shell --license-deactivate # Aktivierungsplatz freigeben
Die Schalter werden vor jeder Avalonia-Initialisierung ausgewertet, wie --headless.
Der Schlüssel wird in der Ausgabe maskiert (LLAB3-*****-*****-*****-AABB2).
⚠️ Offen: Watchdog läuft noch auf dem alten System
Das Deploymentcenter bündelt License, Watchdog, UpdateService und Bugtracker.
WatchdogHeartbeatService zeigt weiterhin auf watchdog.mhdf.de mit dem Header
X-Watchdog-Key. Das funktioniert, ist aber dieselbe Altlast wie eben beim Lizenzsystem.
Migration nach dc.mhdf.de/api/watchdog/v1 als eigener Schritt einplanen —
Leitfaden liegt unter Deploymentcenter/docs/WATCHDOG_INTEGRATION_GUIDE.md.
6. Phase 4 — Aufräumen und Absichern (1–1,5 PT)
AP 4.1 — Kestrel-Binding und Absicherung ⚠️
UseUrls($"http://localhost:{port}") (EmbeddedWebServer.cs:122) konfigurierbar machen
(ASPNETCORE_URLS bzw. Options), CORS-AllowedOrigins (heute fest http://localhost:5000)
mitziehen.
Sobald nicht mehr nur auf localhost gelauscht wird, entfällt die bisher implizite
Absicherung. MapPredictalyticsControlEndpoints() (schreibende Operationen) und Swagger
wären dann offen erreichbar. Das vorhandene CanControl/AuthRequired-Konzept
(Api/ApiConfiguration.cs:66) muss davor scharf geschaltet werden.
AP 4.2 — Globalisierung
Im Host-Bootstrap CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture
setzen, damit Zahlenformatierung unabhängig von der Systemlocale ist.
InvariantGlobalization=true (spart libicu im Container) erst später erwägen — es ändert
String-Vergleichssemantik und sollte nicht im selben Sprung passieren.
AP 4.3 — Log-Verzeichnis
Path.Combine(AppContext.BaseDirectory, "logs") (Program.cs:33) konfigurierbar machen.
Default: Windows %APPDATA%, Linux ~/.local/share bzw. /var/log/predictalytics.
AP 4.4 — Solution und Repo-Kopplung
Predictalytics.slnx: WinFormsHost mitCondition="$([MSBuild]::IsOSPlatform('Windows'))"versehen, damitdotnet buildder Gesamtsolution unter Linux durchläuft.- Die
..\..\..\..\LicenseLabrador\…-Projektreferenz (4 Ebenen aufwärts) ist für CI und Container fragil → Git-Submodul oder NuGet-Paket.
AP 4.5 — Egress-Kanäle prüfen ⚠️
EgressPoolService.cs:70-88 bindet Sockets an eine Quell-IP. Unter Linux muss die IP
tatsächlich auf einem Interface konfiguriert sein (sonst EADDRNOTAVAIL); Windows ist
toleranter. Für nicht-lokale IPs bräuchte es IP_FREEBIND/CAP_NET_ADMIN.
Nur relevant, falls Egress-Kanäle produktiv genutzt werden. Proxy-Kanäle sind unkritisch.
7. Phase 5 — Deployment und CI (1–2 PT)
dotnet publish -r linux-x64(undwin-x64); bei self-contained an--self-contained+ Trimming-Verhalten von Avalonia denken.- Systemabhängigkeiten Linux (GUI):
libx11-6,libice6,libsm6,libfontconfig1,libharfbuzz0b, Monospace-Fonts (z. B.fonts-dejavu). (GUI-Variante braucht eine Desktop-Session — siehe 0.3.) - systemd-Unit bzw. Dockerfile (für den Headless-Modus); es existiert bereits ein
J:\Softwareprojekte\dockerCompose— vermutlich anknüpfbar. - Reverse Proxy (nginx/Caddy) mit TLS vor Kestrel.
- Secrets: DB-Passwort und Watchdog-API-Key liegen heute im Klartext in
settings.json. Unter Linux → EnvironmentFile mit0600oder Secret-Store. Randnotiz: InLicenseGuard.cs:16-17stehen Basic-Auth-Zugangsdaten im Quelltext — unabhängig von dieser Portierung überdenkenswert, erst recht mit Blick auf Monetarisierung. - CI: Build + Test auf Linux und Windows.
docs/BETRIEB-Watchdog-Lizenz.mdum den Linux-Betrieb ergänzen.
8. Phase 6 — Verifikation (2 PT)
| # | Prüfung |
|---|---|
| 8.1 | Worker-Dauerlauf ≥ 24 h unter Linux gegen die Dev-DB, Logvergleich mit einem Windows-Lauf |
| 8.2 | EF-Migrationen von Null gegen Dev-DB (Weg A: nur EF-9-Regression) |
| 8.3 | Zahlenformatierung: Lauf unter LANG=de_DE.UTF-8 — Volumina/Preise/PnL gegen Windows-Referenz |
| 8.4 | Web-UI vollständig durchklicken (case-sensitive Assets, alle Endpunkte) |
| 8.5 | Lizenz: Erstaktivierung, Neustart aus Cache, Offline-Gnadenfrist, Revalidierung |
| 8.6 | Watchdog-Heartbeats erscheinen unter watchdog.mhdf.de |
| 8.7 | Egress-Kanäle (falls genutzt) |
| 8.8 | Shell unter Windows und Linux-Desktop: alle Buttons, Menüs, Settings-Persistenz |
| 8.9 | Derselbe Build mit --headless auf einem Server ohne X11: Start, Lizenz aus Konfiguration, SIGTERM-Stopp |
9. Zeitplan
| Phase | Inhalt | PT |
|---|---|---|
| 0 | Vorarbeiten | 0,5 |
| 1 | .NET 10 + Paketmatrix | 1,5–2,5 |
| 2 | Hosting-Kern extrahieren | 1,5–2 |
| 3 | Avalonia-Shell inkl. Headless-Modus (AP 3.4) | 4,5–6,5 |
| 4 | Aufräumen, Binding, Absicherung | 1–1,5 |
| 5 | Deployment + CI (Desktop und systemd/Container) | 1,5–2,5 |
| 6 | Verifikation (beide Betriebsarten) | 2 |
| Summe | 12,5–17,5 PT |
≈ 2,5–3,5 Wochen fokussiert.
Phasen 0–2 sind risikoarm und liefern bereits Wert (aufgeräumte Architektur, aktuelles Framework). Phase 3 ist der Hauptaufwand. Die Phasen sind strikt sequenziell — jede baut auf der vorigen auf; einzig AP 5 (Deployment) kann parallel zu Phase 3 vorbereitet werden.
10. Risiken
| Risiko | Eintritt | Wirkung | Gegenmaßnahme |
|---|---|---|---|
| Pomelo hat keinen EF-Core-10-Provider | sicher | blockiert EF Core 10 | ✅ gelöst durch Weg A: EF Core 9 auf net10.0 |
| Swashbuckle 6.9 → 10.x mit Brüchen | wahrscheinlich | halber bis ganzer Tag | ggf. auf eingebautes AddOpenApi() wechseln |
| PropertyGrid-Ersatz wird aufwendiger als geschätzt | mittel | +1 PT | handgeschriebene View, 15 Properties sind überschaubar |
| Avalonia-GUI läuft auf dem Linux-Ziel nicht (kein Desktop) | sicher auf Servern | Shell unbrauchbar | ✅ abgedeckt durch AP 3.4 (verbindlich) |
Avalonia.Diagnostics fehlt für 12.x |
sicher | nur Komfort | ohne DevTools arbeiten oder 11.3.18 in Debug |
| Lizenzaktivierung headless | sicher (AP 3.4) | mittel | koppelt an laufende HardwareID-Arbeit — früh abstimmen |
Egress SourceIp unter Linux |
niedrig | mittel | AP 4.5, früh testen |
| Regression in der Datenschicht | niedrig (Weg A) | hoch | ausschließlich Dev-DB, nie Prod |
11. Nächster Schritt
Alle Vorentscheidungen sind getroffen (Abschnitt 0). Umsetzung kann beginnen:
- Phase 0 — 0,5 PT, sofort, unabhängig vom Rest. Ergebnis ist ein aufgeräumter
Ausgangspunkt auf
feature/linux-net10. - Phase 1 — .NET 10 + Paketmatrix. Hier zeigt sich früh, wie groß der Swashbuckle-Umbau tatsächlich ist.
- Parallel dazu: Lizenzaktivierung ohne Dialog mit der laufenden HardwareID-Arbeit abstimmen — das ist der einzige Punkt im Plan, der von außen abhängt und dadurch spät teuer werden kann.