Vorarbeiten fuer die Linux-/NET-10-Portierung (siehe docs/PLAN-Linux-Portierung.md). DateTime.TryParse ohne DateTimeStyles liefert bei Z-Zeitstempeln Kind=Local und rechnet in Lokalzeit um. An 13 Stellen in PolymarketProvider und LimitlessProvider wurden so ISO-Zeitstempel der Plattform-APIs eingelesen, waehrend der Fallback derselben Zuweisungen (DateTime.UtcNow) korrektes UTC schrieb - die Spalten enthielten also gemischt verschobene und korrekte Werte. Auf einem Server mit TZ=UTC haette derselbe Code andere Werte erzeugt als unter Windows. Neuer Helfer Infrastructure/Helpers/DateParsing.cs kapselt das Parsen mit InvariantCulture und AdjustToUniversal|AssumeUniversal; alle Fundstellen umgestellt. Ausserdem: - LimitlessProvider: Volume-Parsing auf InvariantCulture umgestellt - EmbeddedWebServer: hartkodierten j:\-Absolutpfad als wwwroot-Fallback entfernt Ein Backfill der Altdaten erfolgt bewusst nicht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
21 KiB
Analyse: Linux-Fähigkeit von Predictalytics
Stand: 2026-08-06 · Branch main @ aa19a89 · reine Ist-Analyse, keine Codeänderungen
0. Kernbefund vorab
Avalonia UI und LiveCharts2 werden nicht benötigt.
Die Annahme im Auftrag war, dass eine WinForms-Oberfläche auf Avalonia + LiveCharts2 portiert werden muss. Das trifft auf dieses Projekt nicht zu:
- Die eigentliche Analyse-Oberfläche ist bereits eine Browser-Anwendung —
src/Predictalytics.Api/wwwroot/(3.745 Zeilen HTML/CSS/JS), Charts über Chart.js 4.4.4 (index.html:9, CDN). Sie ist damit heute schon plattformunabhängig. Predictalytics.WinFormsHostist kein UI-Projekt im fachlichen Sinne, sondern eine Bedien-Hülle für den Betrieb: Start/Stop der Worker, Start/Stop des Kestrel-Webservers, einPropertyGridfür Einstellungen, eineRichTextBoxals Log-Terminal und vier manuelle Wartungsaktionen. 1.698 LOC inkl. Designer-Datei. Kein einziges Diagramm.
Die Aufgabe lautet damit nicht „UI-Framework migrieren", sondern „Bedien-Hülle durch einen headless Host ersetzen" — eine Größenordnung weniger Aufwand.
1. Portierungsstatus je Projekt
| Projekt | TFM | Status | Bemerkung |
|---|---|---|---|
Predictalytics.Domain |
net8.0 |
✅ portabel | 42 Dateien, reine Fachlogik |
Predictalytics.Application |
net8.0 |
✅ portabel | 39 Dateien |
Predictalytics.Infrastructure |
net8.0 |
✅ portabel | 94 Dateien, EF Core + Pomelo MySQL |
Predictalytics.Api |
net8.0 |
✅ portabel | Minimal-APIs + wwwroot |
Predictalytics.Worker |
net8.0 |
✅ portabel | 13 Worker-Services |
Predictalytics.Application.Tests |
net10.0 |
⚠️ Inkonsistenz | s. Punkt 4.8 |
Predictalytics.WinFormsHost |
net8.0-windows |
❌ muss ersetzt werden | einziger Blocker |
LicenseLabrador.Client (Schwester-Repo) |
netstandard2.0 |
⚠️ teilportabel | s. Punkt 3.2 |
54.056 Zeilen C# gesamt. Davon sind 1.698 (3,1 %) windows-gebunden — und davon ist
etwa die Hälfte (EmbeddedWebServer.cs, Serilog-Setup) inhaltlich bereits portabel und
nur im falschen Projekt beheimatet.
2. Was bereits sauber ist (und keinen Aufwand erzeugt)
Diese Punkte wurden explizit geprüft, weil sie erfahrungsgemäß die teuren Überraschungen einer Linux-Portierung sind. Der Code ist hier durchweg in gutem Zustand:
2.1 Zeitzonen — überwiegend sauber, mit einer wichtigen Ausnahme ⚠️
Korrektur (2026-08-06, nach Phase 0): Die erste Fassung dieser Analyse stufte den Punkt als „vollständig sauber, Aufwand 0" ein. Das war falsch — siehe unten. Die explizite Zeitzonen-API-Nutzung ist sauber, die implizite über
DateTime.TryParsewar es nicht.
166 × DateTime.UtcNow / DateTimeOffset.UtcNow
0 × DateTime.Now · DateTime.Today · DateTimeOffset.Now
0 × ToLocalTime() · DateTimeKind.Local
0 × TimeZoneInfo / FindSystemTimeZoneById
13 × DateTime.TryParse ohne Culture/Styles ← Problem
Der klassische Linux-Killer TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time")
(Windows-IDs existieren unter Linux nicht, dort gilt IANA Europe/Berlin) kommt nicht
vor. Insoweit ist die Codebasis sauber.
Übersehen hatte ich die implizite Variante: DateTime.TryParse("...Z") ohne
DateTimeStyles liefert Kind=Local und rechnet den Wert in Lokalzeit um. In beiden
Providern wurden so ISO-Zeitstempel der Plattform-APIs eingelesen:
PolymarketProvider.cs—StartDate,EndDate,CreatedAt,ClosedAt(10 Stellen)LimitlessProvider.cs—ExecutedAt,ExpirationDate(2 Stellen)
Dass UTC gemeint war, belegen die Fallbacks: jede dieser Zuweisungen fällt bei
Parse-Fehler auf DateTime.UtcNow zurück, und direkt daneben steht DbCreatedAt = DateTime.UtcNow. Auf der Berliner Entwicklungsmaschine landeten die geparsten Werte
dadurch um 1–2 Stunden verschoben in der Datenbank, die Fallback-Werte dagegen korrekt.
Für die Portierung ist das doppelt relevant: derselbe Code hätte auf einem Linux-Server
mit TZ=UTC andere Werte produziert als auf dem Windows-Rechner.
Behoben in Phase 0 über Infrastructure/Helpers/DateParsing.cs
(InvariantCulture + AdjustToUniversal | AssumeUniversal).
⚠️ Offen: Altdaten. Bereits gespeicherte Zeilen tragen die Verschiebung weiter. Ob ein
Backfill nötig ist, hängt daran, wie stark StartDate/EndDate/ClosedAt in die
Auswertung eingehen — siehe „Nächste Schritte" im Portierungsplan.
2.2 Kulturabhängige Zahlenformatierung — bis auf eine Stelle sauber ✅
Alle Parser für Fremd-APIs (Polymarket, Limitless) verwenden explizit InvariantCulture:
LimitlessProvider.cs:59,60,110,113,114,115PolymarketModels.cs:17·PolymarketProvider.cs:386TradeHistoryWorker.cs:466,467
Eine Ausnahme (echter Latent-Bug, plattformunabhängig):
// Predictalytics.Infrastructure/Providers/Limitless/LimitlessProvider.cs:268
Volume = decimal.TryParse(raw.VolumeFormatted?.Replace(" USDC", ""), out var vol) ? vol : 0,
Ohne InvariantCulture. Auf einem System mit deutscher Locale (de_DE.UTF-8, unter Linux
sehr üblich) wird "1234.56" als 123456 interpretiert — Faktor 100.000 zu hoch.
Der Fehler existiert schon heute auf einem deutschsprachigen Windows; Linux macht ihn nur
wahrscheinlicher, weil die Locale dort oft explizit gesetzt wird.
Aufwand: 5 Minuten. Sollte unabhängig von der Portierung gefixt werden.
Zusätzliche Absicherung empfohlen: InvariantGlobalization bzw. ein explizites
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture im neuen Host.
2.3 Pfade und Dateisystem ✅
- Keine hartkodierten Laufwerksbuchstaben im Code — eine Ausnahme, siehe 4.5.
Path.Combine()wird konsequent verwendet, keine manuell zusammengesetzten\-Pfade.- Kein einziger Datei-/Verzeichniszugriff in
Infrastructure,Worker,Api,Application,Domain. Sämtliches File-IO liegt inWinFormsHost(Settings, Logs) und im Lizenz-Client.
2.4 Groß-/Kleinschreibung (case-sensitive Dateisystem) ✅
Linux-Dateisysteme sind case-sensitiv, Windows nicht — ein häufiger Stolperstein bei statischen Web-Assets. Geprüft:
| Referenz | Datei | Passt |
|---|---|---|
href="css/style.css?v=20260715" |
wwwroot/css/style.css |
✅ |
src="js/app.js?v=20260715" |
wwwroot/js/app.js |
✅ |
href="./docs.html" |
wwwroot/docs.html |
✅ |
href="./index.html" |
wwwroot/index.html |
✅ |
Alles durchgängig kleingeschrieben und deckungsgleich. Aufwand: 0.
Hinweis: MySQL-Tabellennamen sind unter Linux abhängig von lower_case_table_names
ebenfalls case-sensitiv. Da Prod-DB und Dev-DB bereits auf Linux-Servern laufen
(lqf7.your-database.de) und der Code über EF Core mit konsistenter Schreibweise
zugreift, besteht hier kein zusätzliches Risiko durch die Client-Portierung.
2.5 Keine Windows-spezifischen APIs außerhalb des Hosts ✅
Geprüft auf Registry, Microsoft.Win32, ManagementObject/WMI, ProtectedData,
DllImport, System.Drawing, Environment.OSVersion: keine Treffer außerhalb von
Predictalytics.WinFormsHost. Auch Encoding.Default (unter Linux immer UTF-8, unter
Windows Codepage) kommt nirgends vor.
2.6 NuGet-Abhängigkeiten ✅
Alle verwendeten Pakete sind plattformneutral: EF Core 8 + Pomelo MySQL, Serilog (+ File/
Console/Compact), Swashbuckle, Microsoft.Extensions.*, xunit, Moq. Keine
windows-exklusiven Pakete außerhalb des WinForms-Hosts.
2.7 Ein bereits vorhandener Vorteil: der Composition Root existiert schon ✅
Predictalytics.Api/ApiConfiguration.cs (ConfigureApi) ist eine vollständige,
eigenständige Host-Konfiguration — DI, CORS, Swagger, Static Files, Endpoint-Mapping.
Ihr fehlt nur eine Main-Methode. Der neue headless Host kann darauf direkt aufsetzen,
statt die Verdrahtung neu zu schreiben.
Ebenso ist Infrastructure/Logging/RichTextBoxSink.cs trotz des Namens vollständig
portabel — es ist ein reiner Delegate-Sink (Action<string, LogEventLevel>) ohne
WinForms-Referenz. Lediglich umbenennen (z. B. DelegateSink) wäre sinnvoll.
3. Die tatsächlichen Blocker
3.1 Predictalytics.WinFormsHost — Ersatz durch headless Host ⛔ Hauptaufwand
Das Projekt ist der einzige Einstiegspunkt der Anwendung (static void Main existiert nur
hier). Es leistet fünf Dinge, die im neuen Host anders gelöst werden müssen:
| Funktion heute | Ersatz unter Linux | Aufwand |
|---|---|---|
Serilog-Setup (Program.cs:35-148) — Datei-Sinks nach Level und Plattform |
1:1 übernehmbar, ist bereits portabel. Nur RichTextBoxSink entfällt bzw. wird Console-Sink. |
trivial |
EmbeddedWebServer.cs (365 Z.) — Kestrel- und Worker-Lifecycle |
Inhaltlich portabel. Wird zum normalen IHost mit WebApplication + AddWorkerServices(). Vereinfacht sich sogar deutlich, weil Start/Stop-Buttons entfallen. |
klein |
AppSettings.cs + PropertyGrid — Laufzeit-Konfiguration in settings.json |
appsettings.json + Umgebungsvariablen + IOptions<T>. Die [Category]/[DisplayName]-Attribute entfallen ersatzlos. |
klein–mittel |
Manuelle Aktionen — „Sync Markets", „DB Update", „Recalculate All Traders" (Bestätigungsdialoge + MessageBox) |
Entweder CLI-Verben (--sync-markets, --recalc-all) oder geschützte Admin-Endpunkte in der bestehenden Web-UI. Die Rückfrage-Logik („nicht während laufender Worker") muss erhalten bleiben. |
mittel |
Log-Terminal (RichTextBox) + DB-Größen-Anzeige + Build-Version |
Entfällt / Console + journalctl. Die DB-Größenabfrage (MainForm.cs:317-343) kann als Health-Endpunkt weiterleben. |
klein |
WatchdogHeartbeatService.cs (141 Z.) ist reines HttpClient-Verhalten und vollständig
portabel — sollte nach Infrastructure oder in den neuen Host wandern.
Empfehlung: WinFormsHost nicht löschen, sondern beide Hosts parallel führen. Er ist die etablierte Betriebsoberfläche unter Windows; beide teilen sich denselben Composition Root. Der Wartungsaufwand für die dünne Hülle ist gering.
3.2 Lizenzierung — der unterschätzte Blocker ⛔
Der LicenseLabrador.Client ist erfreulich gut vorbereitet, aber die Aktivierung ist es nicht.
Was bereits funktioniert:
HardwareId.cs:52-84behandelt Linux explizit: liest/etc/machine-idbzw./var/lib/dbus/machine-id, mit MAC-Adressen-Fallback. Sauber gelöst.- Zielframework
netstandard2.0,Microsoft.Win32.Registrywird perIsWindows()-Guard nur unter Windows angefasst.
Was nicht funktioniert:
-
LicenseDialogist ein blockierender WinForms-Modaldialog.Program.cs:16→LicenseGuard.EnsureLicensed()→dialog.ShowDialog(). Auf einem headless Server gibt es niemanden, der einen Key eintippt. Es braucht einen nicht-interaktiven Aktivierungspfad: Lizenzschlüssel aus Umgebungsvariable oder Konfigurationsdatei, Aktivierung beim ersten Start, sauberer Abbruch mit Exit-Code und klarer Logmeldung, wenn keine gültige Lizenz vorliegt. -
StateStore.cs:43,92— DPAPI-Verschlüsselung fällt unter Linux still auf Klartext zurück.try { dataToSave = ProtectedData.Protect(plainBytes, null, DataProtectionScope.CurrentUser); } catch { dataToSave = plainBytes; } // ← Linux landet immer hierDer Lizenz-Cache (inkl. Lizenzschlüssel) liegt dann unverschlüsselt auf der Platte. Funktional unkritisch, aber eine bewusste Entscheidung — mindestens sollten die Dateirechte (
0600) und das Verzeichnis abgesichert werden. -
LicenseConfig.StorageDirectorynutztEnvironment.SpecialFolder.ApplicationData→ unter Linux~/.config. Unter systemd mitDynamicUser=yesoder ohne$HOMEexistiert das nicht. Muss explizit gesetzt werden (z. B./var/lib/predictalytics). -
LicenseGuard.StartPeriodicRevalidationverwendetSystem.Windows.Forms.Timer→PeriodicTimeroderIHostedService.Application.Exit()→IHostApplicationLifetime.StopApplication().
Das ist der Punkt mit der größten Restunsicherheit, weil er das Schwester-Repo LicenseLabrador berührt und serverseitig geprüft werden muss (Aktivierung ohne UI).
3.3 Kestrel bindet nur auf localhost ⚠️
// EmbeddedWebServer.cs:122
builder.WebHost.UseUrls($"http://localhost:{port}");
Das ist für einen Desktop-Host korrekt, für einen Linux-Server aber falsch: von außen
nicht erreichbar. Muss konfigurierbar werden (0.0.0.0 bzw. ASPNETCORE_URLS), inkl.
Anpassung der CORS-AllowedOrigins (heute fest http://localhost:5000).
Sicherheitshinweis: Sobald der Dienst nicht mehr nur auf localhost lauscht, ist die
bisher implizite Absicherung weg. MapPredictalyticsControlEndpoints() (schreibende
Operationen) und Swagger sind dann exponiert. Es gibt bereits ein CanControl/AuthRequired-
Konzept (ApiConfiguration.cs:66) — das muss vor dem Linux-Deployment scharf geschaltet
werden, plus Reverse Proxy (nginx/Caddy) mit TLS davor.
4. Weitere Fundstellen (klein, aber notwendig)
4.1 Process.Start("explorer.exe", …) — 3 Stellen
MainForm.cs:192,207,209 (Browser öffnen, Log-Ordner öffnen). Betrifft nur den
WinForms-Host; im headless Host entfallen die Aktionen ersatzlos.
4.2 System.Windows.Forms.Timer — 2 Stellen
MainForm.cs:71 (DB-Größe), LicenseGuard.cs:70 (Revalidierung).
→ PeriodicTimer / IHostedService.
4.3 Schriftart Cascadia Code
MainForm.cs:25. Nur WinForms-relevant, entfällt.
4.4 Log-Verzeichnis
Program.cs:33: Path.Combine(AppContext.BaseDirectory, "logs").
Technisch portabel, aber unter Linux ist das Installationsverzeichnis üblicherweise nicht
beschreibbar. → konfigurierbar machen, Default /var/log/predictalytics bzw.
journalctl über den Console-Sink.
Nebenbefund: src/Predictalytics.WinFormsHost/logs/Predictalytics-20260428.json ist
offenbar versehentlich eingecheckt und enthält Stacktraces mit lokalen Pfaden. Sollte
aus der Versionierung entfernt und .gitignore ergänzt werden.
4.5 FindWwwrootPath() — hartkodierter Absolutpfad ⚠️
// EmbeddedWebServer.cs:314-321
var candidates = new[] {
Path.Combine(baseDir, "wwwroot"),
Path.GetFullPath(Path.Combine(baseDir, "..","..","..","..","Predictalytics.Api","wwwroot")),
Path.GetFullPath(Path.Combine(baseDir, "..","..","..","..","..","src","Predictalytics.Api","wwwroot")),
@"j:\Softwareprojekte\Predictalytics\Predictalytics\src\Predictalytics.Api\wwwroot" // ←
};
Die Suchheuristik über Build-Output-Verzeichnisse funktioniert im Deployment nicht
zuverlässig, der j:\-Fallback gar nicht. Sauber: wwwroot als Content mit
CopyToOutputDirectory in die Host-.csproj aufnehmen (in der Api-.csproj bereits
vorhanden) und nur noch Path.Combine(AppContext.BaseDirectory, "wwwroot") verwenden.
4.6 MySQL-Verbindung: SslMode = None fest verdrahtet ⚠️
AppSettings.cs:100. Bei einem lokal laufenden Windows-Client zu einer entfernten DB war
das schon fragwürdig; bei einem Serverbetrieb sollte SSL konfigurierbar (und
standardmäßig aktiv) sein. Kein Linux-spezifisches Problem, fällt aber bei der
Konfigurationsmigration ohnehin an.
4.7 Egress-Kanäle (SourceIp) — verhält sich unter Linux anders ⚠️
EgressPoolService.cs:70-88 bindet einen Socket per socket.Bind(new IPEndPoint(ip, 0))
an eine Quell-IP. Das funktioniert unter Linux grundsätzlich, aber:
- Die IP muss auf einem Interface tatsächlich konfiguriert sein (sonst
EADDRNOTAVAIL); Windows ist hier toleranter. - Für nicht-lokale IPs bräuchte es
IP_FREEBIND/CAP_NET_ADMIN.
Falls Egress-Kanäle produktiv genutzt werden, ist das ein expliziter Testpunkt.
Proxy-Kanäle (WebProxy) sind unproblematisch.
4.8 Zielframework-Inkonsistenz ⚠️
Predictalytics.Application.Tests steht auf net10.0, alle anderen auf net8.0.
Das funktioniert (Test-Assembly darf höher liegen), erzwingt aber ein .NET-10-SDK auf dem
Build-Server. Bei einer CI-Einrichtung für Linux vereinheitlichen — entweder alles auf
net8.0 oder ein bewusster Sprung der gesamten Solution auf net10.0.
4.9 Predictalytics.slnx und Build
Die Solution referenziert den WinFormsHost. Auf Linux schlägt ein dotnet build der
Gesamtsolution fehl (net8.0-windows + UseWindowsForms). Optionen:
- Den WinFormsHost mit
<TargetFrameworks>bzw. einer Condition auf$([MSBuild]::IsOSPlatform('Windows'))versehen, oder - eine zweite Solution-Datei für Linux-Builds pflegen.
Zusätzlich: Die .csproj des Hosts referenziert das Schwester-Repo über einen
relativen Pfad mit vier Ebenen nach oben (..\..\..\..\LicenseLabrador\…). Das setzt
eine feste Verzeichnisstruktur voraus und ist für CI/Container fragil — besser über ein
NuGet-Paket oder ein Git-Submodul lösen.
5. Was zusätzlich neu entsteht (Deployment)
Kein Portierungsproblem im engeren Sinn, aber Teil der Aufgabe „Linux-fähig":
- systemd-Unit (
Restart=always,User=,WorkingDirectory=,EnvironmentFile=) oder alternativ Dockerfile +docker-compose(es existiert bereits einJ:\Softwareprojekte\dockerCompose-Verzeichnis — vermutlich anknüpfbar). - Reverse Proxy (nginx/Caddy) mit TLS vor Kestrel.
- Secrets-Handling: DB-Passwort und Watchdog-API-Key liegen heute im Klartext in
settings.jsonneben der Exe. Unter Linux → Environment-File mit0600oder Secret-Store. Randnotiz: InLicenseGuard.cs:16-17stehen Basic-Auth-Zugangsdaten im Quelltext. Bei einem Server-Deployment (und erst recht bei der angedachten Monetarisierung) sollte das überdacht werden — unabhängig von dieser Portierung. - EF-Migrationen:
EnsureDatabaseAsync()existiert bereits und wird beim Start aufgerufen — für den Serverbetrieb ist zu entscheiden, ob Migrationen automatisch laufen sollen oder als separater Schritt. - CI: Build/Test auf Linux, Publish als
linux-x64(ggf. self-contained). docs/BETRIEB-Watchdog-Lizenz.mdmuss um den Linux-Betrieb ergänzt werden.
6. Aufwandsschätzung
| Arbeitspaket | Aufwand (PT) | Risiko |
|---|---|---|
A Neues Host-Projekt Predictalytics.Host (net8.0): Serilog, Kestrel, Worker, Lifecycle |
2–3 | niedrig — Composition Root existiert bereits |
B Konfigurationsmigration AppSettings/PropertyGrid → appsettings.json + Env + IOptions |
1 | niedrig |
| C Manuelle Wartungsaktionen → CLI-Verben bzw. Admin-Endpunkte | 1–1,5 | niedrig |
| D Lizenzierung headless: nicht-interaktive Aktivierung, StorageDirectory, Timer, Cache-Absicherung | 1,5–3 | mittel–hoch — berührt LicenseLabrador, serverseitig zu verifizieren |
| E Kestrel-Binding + CORS + Absicherung der Control-Endpunkte | 0,5–1 | mittel (Sicherheitsentscheidung nötig) |
F Kleinkram: wwwroot-Deployment, Log-Pfad, Culture-Bug, TFM-Vereinheitlichung, Solution-Split, eingecheckte Logs |
0,5–1 | niedrig |
| G Deployment: systemd/Docker, Reverse Proxy, Secrets, CI | 1–2 | niedrig–mittel |
| H Verifikation auf einer echten Linux-Instanz (Worker-Dauerlauf, DB, Egress, Watchdog, Lizenz) | 2 | mittel |
| Summe | ~9,5–14,5 PT |
Realistisch: 2–3 Wochen bei Teilzeit, ca. 2 Wochen fokussiert.
Zum Vergleich: Der ursprünglich angenommene Weg über Avalonia + LiveCharts2 hätte grob 4–8 Wochen gekostet — für eine Oberfläche, die es fachlich bereits als Web-UI gibt. Diese Arbeit wäre vollständig verloren gewesen.
7. Empfohlenes Vorgehen
- F zuerst (halber Tag): Culture-Bug, TFM, eingecheckte Logs,
wwwroot-Deployment. Wirkt sofort, unabhängig von der Portierung, reduziert Rauschen. - A + B parallel — das neue Host-Projekt entsteht neben dem WinFormsHost, nicht
an seiner Stelle. Beide teilen sich
ApiConfigurationundAddWorkerServices(). Ab hier läuft der Stack unter Linux (noch ohne Lizenz-Gate). - E — Binding und Absicherung, bevor irgendetwas exponiert wird.
- D — Lizenzierung. Am besten früh anfangen zu klären, weil es das Schwester-Repo und ggf. Serveränderungen betrifft; die Umsetzung kann dann parallel laufen.
- C, G, H zum Abschluss.
Zwei Entscheidungen sind vorab zu treffen:
- Bleibt der WinFormsHost erhalten? Empfehlung: ja, als Windows-Betriebsoberfläche. Er ist dünn, funktioniert und beide Hosts teilen sich denselben Kern. Der Web-UI-Zugang ist ohnehin identisch.
- Wie wird headless lizenziert? Lizenzschlüssel per Umgebungsvariable/Config und Aktivierung beim ersten Start ist der pragmatische Weg — muss aber mit LicenseLabrador abgestimmt werden.
8. Zusammenfassung in drei Sätzen
Predictalytics ist in deutlich besserem Zustand für eine Linux-Portierung als erwartet: 96,9 % des Codes sind bereits plattformneutral, es gibt keine Windows-Zeitzonen-IDs und keine Windows-APIs außerhalb des Hosts, und die Analyse-Oberfläche ist längst eine Browser-Anwendung mit Chart.js — LiveCharts2 wird nicht gebraucht.
Zwei Defekte an der Grenze zu den Plattform-APIs (13 × lokalzeit-verzerrendes Datums-Parsing, 1 × kulturabhängiges Zahlen-Parsing) waren allerdings vorhanden und sind in Phase 0 behoben.
Der eigentliche Aufwand liegt im Ersetzen der 1.698 Zeilen umfassenden WinForms- Bedienhülle durch einen headless Host und im Umbau der Lizenzaktivierung, die heute zwingend einen Modaldialog erfordert.
Geschätzt 9,5–14,5 Personentage, mit dem einzigen nennenswerten Risiko im Lizenzierungspfad.