Die offenen Punkte lagen ueber neun Dokumente verstreut, teils widersprechend, teils mit Punkten, die laengst umgesetzt waren. ROADMAP.md fuehrt sie zusammen: sechs Stufen, jeder Punkt gegen den Code geprueft. Markierung ueber eine Legende, damit Konzepte nicht mit Aufgaben verwechselt werden: dringend, eingeplant, Backlog, Konzept (durchdacht, aber bewusst nicht eingeplant), liegt beim Nutzer, verworfen. Stufen: 0 Sofort (OpenRouter-Key) - 1 Aufraeumen abschliessen (Merge nach main, Nullable-Warnungen, Startup-Backfill) - 2 Portierung abschliessen (Linux-Erstlauf und Verifikation, dann Deployment/CI) - 3 Analytik schaerfen (Strategie- Klassifikation, Backtest-Harness, Track-Record im Score, Ranking) - 4 Daten- haushalt (SQL des Nutzers) - 5 Ingest skalieren - 6 Monetarisierung vorbereiten. Ein Anhang haelt fest, was geprueft und bewusst NICHT auf die Roadmap kam, damit es nicht versehentlich wieder als Aufgabe auftaucht (Azuro/Limitless, die Marketing-Seiten, Kaltarchiv, EF Core 10). Archiv: FIXPLAN-DONE, FIXPLAN-G-Speicher, FIXPLAN-TODO, FIXPLAN-UI-Ranglisten, UMSETZUNGSPLAN sowie ANALYSE-Linux-Portierung, PLAN-Linux-Portierung, PLAN-Architektur-WebUI-Backend und PLAN-DatenIngest-Skalierung liegen jetzt unter docs/archiv/ mit einer README, die jedes Dokument einordnet. Sie bleiben als Begruendungs- und Detailquelle - die Roadmap nennt jeden Punkt knapp, die Herleitung steht dort. Dabei aufgefallen: die beiden nie gepflegten Plaene (Architektur, DatenIngest) zeigten 40 offene Punkte, von denen die Haelfte umgesetzt war - Read/Control- Split, /api/capabilities, CORS-Whitelist, Egress-Kanaele mit Cooldown und Per-Kanal-Limiter, Ingest-Tiering. Nachgeprueft, abgehakt und mit Statusblock eingeordnet, sonst waere das Archiv selbst eine Fehlerquelle. Was dabei nur teilweise umgesetzt war, ist als Roadmap 5.3 aufgenommen (Health-Statistik je Kanal, multi-homed pruefen, DB-Guard, Plausibilitaets- pruefung), ebenso der nie durchgefuehrte Audit auf versteckte Writes in Read-Endpunkten (6.1). Header-Rotation ist als verworfen markiert statt offen zu bleiben: Verschleierung gegenueber Polymarket riskiert genau den Zugang, auf dem das Projekt aufsetzt. STATUS.md beschreibt jetzt nur noch den Ist-Stand und verweist fuer die offenen Punkte auf die Roadmap, damit nichts doppelt gepflegt wird. CLAUDE.md nennt beide Einstiege. Build gruen, 126 Tests gruen, keine toten Links. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
408 lines
21 KiB
Markdown
408 lines
21 KiB
Markdown
# 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.WinFormsHost` ist **kein UI-Projekt im fachlichen Sinne**, sondern eine
|
||
Bedien-Hülle für den Betrieb: Start/Stop der Worker, Start/Stop des Kestrel-Webservers,
|
||
ein `PropertyGrid` für Einstellungen, eine `RichTextBox` als 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.TryParse`
|
||
> war 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,115`
|
||
* `PolymarketModels.cs:17` · `PolymarketProvider.cs:386`
|
||
* `TradeHistoryWorker.cs:466,467`
|
||
|
||
**Eine Ausnahme (echter Latent-Bug, plattformunabhängig):**
|
||
|
||
```csharp
|
||
// 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 in `WinFormsHost` (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-84` behandelt Linux explizit: liest `/etc/machine-id` bzw.
|
||
`/var/lib/dbus/machine-id`, mit MAC-Adressen-Fallback. Sauber gelöst.
|
||
* Zielframework `netstandard2.0`, `Microsoft.Win32.Registry` wird per `IsWindows()`-Guard
|
||
nur unter Windows angefasst.
|
||
|
||
**Was nicht funktioniert:**
|
||
|
||
1. **`LicenseDialog` ist 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.
|
||
|
||
2. **`StateStore.cs:43,92` — DPAPI-Verschlüsselung fällt unter Linux still auf Klartext zurück.**
|
||
```csharp
|
||
try { dataToSave = ProtectedData.Protect(plainBytes, null, DataProtectionScope.CurrentUser); }
|
||
catch { dataToSave = plainBytes; } // ← Linux landet immer hier
|
||
```
|
||
Der 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.
|
||
|
||
3. **`LicenseConfig.StorageDirectory`** nutzt `Environment.SpecialFolder.ApplicationData`
|
||
→ unter Linux `~/.config`. Unter systemd mit `DynamicUser=yes` oder ohne `$HOME`
|
||
existiert das nicht. Muss explizit gesetzt werden (z. B. `/var/lib/predictalytics`).
|
||
|
||
4. **`LicenseGuard.StartPeriodicRevalidation`** verwendet `System.Windows.Forms.Timer`
|
||
→ `PeriodicTimer` oder `IHostedService`. `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` ⚠️
|
||
|
||
```csharp
|
||
// 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 ⚠️
|
||
```csharp
|
||
// 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 ein
|
||
`J:\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.json` neben der Exe. Unter Linux → Environment-File mit `0600` oder
|
||
Secret-Store.
|
||
*Randnotiz:* In `LicenseGuard.cs:16-17` stehen 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.md` muss 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
|
||
|
||
1. **F zuerst** (halber Tag): Culture-Bug, TFM, eingecheckte Logs, `wwwroot`-Deployment.
|
||
Wirkt sofort, unabhängig von der Portierung, reduziert Rauschen.
|
||
2. **A + B parallel** — das neue Host-Projekt entsteht neben dem WinFormsHost, nicht
|
||
an seiner Stelle. Beide teilen sich `ApiConfiguration` und `AddWorkerServices()`.
|
||
Ab hier läuft der Stack unter Linux (noch ohne Lizenz-Gate).
|
||
3. **E** — Binding und Absicherung, bevor irgendetwas exponiert wird.
|
||
4. **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.
|
||
5. **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.
|