Neues Projekt Predictalytics.Hosting nimmt auf, was bisher im windows-gebundenen WinFormsHost feststeckte, aber portabel ist: - PredictalyticsHost (aus EmbeddedWebServer): Kestrel- und Worker-Lifecycle, Wartungsaktionen, DB-Groesse. Meldet Zustandswechsel ueber StateChanged. - PredictalyticsOptions (aus AppSettings): ohne WinForms-Bezug. Die System.ComponentModel-Attribute sind plattformneutral und bleiben, damit das PropertyGrid Gruppen und Beschreibungen behaelt. - LoggingSetup (aus Program.cs): Serilog-Aufbau, Terminal-Sink als optionale Action statt fester RichTextBox. - LicenseGuard: GUI-frei. Periodische Revalidierung ueber PeriodicTimer statt WinForms-Timer, Abbruch ueber Callback statt Application.Exit. Der interaktive Dialogaufruf bleibt als LicenseGate im WinForms-Host. - WatchdogHeartbeatService unveraendert verschoben. Infrastructure: RichTextBoxSink -> DelegateSink umbenannt (war nie WinForms-abhaengig, nur missverstaendlich benannt). Einstellungen liegen jetzt unter %APPDATA%/Predictalytics bzw. ~/.config/Predictalytics statt neben der Programmdatei, mit einmaliger Uebernahme aus dem alten Ort. Das Installationsverzeichnis ist unter Linux ueblicherweise nicht beschreibbar. wwwroot wird ueber einen Content-Eintrag neben die Programmdatei kopiert; die frueheren Pfad-Heuristiken entfallen. Hosting und WinFormsHost nutzen Microsoft.NET.Sdk statt Sdk.Web: der Web-SDK globbt wwwroot automatisch als Static Web Asset und kollidiert mit dem Content-Eintrag. WebApplication kommt ueber FrameworkReference. Neu konfigurierbar (verhaltensgleiche Defaults): WebserverHost fuer die Kestrel-Bind-Adresse, DbSslMode fuer die MySQL-Verschluesselung. explorer.exe-Aufrufe durch ProcessStartInfo mit UseShellExecute ersetzt — funktioniert unter Windows und Linux. Build: 0 Fehler. Tests: 100 bestanden, 0 Fehler, 1 uebersprungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
27 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 (4–6 PT)
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.
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.