Files
ClawdDotNet/docs/Linux-Portierung-Analyse.md
T
RichardandClaude Opus 5 2853541629 Fruehjahrsputz: WinForms-Altlast entfernt, Dokumentation nachgezogen
Die Avalonia-Portierung ist abgeschlossen, damit ist die in ClawdDotNet.slnx
angekuendigte Aufgabe "WinForms-Oberflaeche entfernen" faellig. Der Stand
davor liegt unter dem Tag vor-fruehjahrsputz-2026-08.

Entfernt (56 Dateien, seit dem Herausloesen der Anwendungsschicht nicht mehr
Teil des Builds): ClawdDotNet.csproj, Program.cs, sieben frm_*-Formulare,
UI/, Models/, EmbeddedUI/, Properties/, Resources/, Services/, das alte
Anwendungssymbol und Deploy-Build.ps1 (ersetzt durch deploy/publish.py).
Dazu configs/*.json - Beispielkonfigurationen aus der Zeit vor dem
Instanzverzeichnis, auf die nur noch die alten Prompts verwiesen.

Die vier Entwicklungs-Prompts der Anfangszeit ziehen nach docs/archiv/ um,
mit README, das ihren Stand einordnet. Eine Regel darin gilt weiter - die
Pflichtfelder fetchedAt/dataAsOf/source der Internet-Tools -, deshalb
Archiv statt Loeschen; der WebSearch-Plan verweist auf den neuen Pfad.

Toter Code
- PlaceholderPageViewModel samt Ansicht: Es gibt keinen Platzhalter-Bereich
  mehr, seit alle neun Seiten portiert sind.
- Snappier als direkter Paketverweis: MongoDB.Driver loest es ohnehin auf
  dieselbe Fassung auf, der Verweis hob nichts an.

Zwei Fehler, die dabei sichtbar wurden
- Die taegliche Sicherung lief ins Leere. Die Oberflaeche bot sie an und
  schrieb Uhrzeit, Zielordner und Anzahl in die Einstellungen, aber der
  BackupScheduler wurde nirgends erzeugt. Jetzt am AppHost verdrahtet und
  in den geordneten Abbau aufgenommen.
- SettingsPageViewModel hielt die vier Sicherungs-Einstellungen doppelt.
  Aus der Ansicht waren sie laengst verschwunden, gelesen und beim
  Speichern zurueckgeschrieben wurden sie weiter: Wer die Uhrzeit auf der
  Sicherungs-Seite aenderte und danach die Einstellungen speicherte, bekam
  den alten Wert zurueck.

Pakete: keine bekannten Sicherheitsluecken mehr
- SQLitePCLRaw.bundle_e_sqlite3 auf 2.1.13 angehoben. Microsoft.Data.Sqlite
  bringt 2.1.11 mit, darin steckt GHSA-2m69-gcr7-jv3q (NU1903, hoch).
- SharpCompress bleibt als direkter Verweis stehen. Beim Aufraeumen erst
  als ungenutzt entfernt - dabei kam die von MongoDB.Driver gezogene
  Fassung 0.30.1 mit GHSA-6c8g-7p36-r338 zurueck. Der Verweis ist eine
  Anhebung, kein Ballast; das steht jetzt als Kommentar dabei.

Dokumentation
- Roadmap mit Statusblock: A1 und A3 erledigt, A2 nur zur Haelfte - Gate,
  Policy und Dienst greifen, aber keine Ansicht ruft ApproveAsync auf, ein
  gestagter Aufruf liegt unbeantwortet. Das ist jetzt Punkt 1 der Reihung.
  Rocket.Chat steht und kollidiert mit A5 (Matrix) - Entscheidung faellig.
- Avalonia-Portierungsleitfaden -> Oberflaechen-Leitfaden: kein Auftrag mehr,
  sondern Beschreibung des Stands.
- Bestandsaufnahme und Linux-Analyse als datierte Befunde gekennzeichnet;
  der teure Teil der Linux-Analyse (8.900 Zeilen WinForms) ist hinfaellig.
- Verweise auf frm_*, WebView2 und ClawdDotNet.csproj in den lebenden
  Dokumenten richtiggestellt.

Build fehlerfrei, 585 Tests gruen (6 uebersprungen).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:26:14 +02:00

29 KiB
Raw Blame History

Linux-Portierung — Analyse

Stand: 2026-08-06. Reine Bestandsaufnahme und Aufwandsschätzung, kein Umbau.

Nachtrag 2026-08-23: Der teure Teil dieser Analyse hat sich erledigt. Die rund 8.900 Zeilen Windows-Forms-Oberfläche samt WebView2 und den vier PropertyGrid- Instanzen gibt es nicht mehr — sie sind durch src/ClawdDotNet.Desktop (Avalonia, net10.0) ersetzt und beim Frühjahrsputz entfernt worden. Damit fällt der zweite der beiden unten vorgeschlagenen Schnitte weg: Es gibt keine Windows-gebundene Oberfläche mehr, die noch umzuziehen wäre. Offen bleiben die drei Kernstellen (DPAPI, Pfadvergleiche, Zeitzonen-IDs) und ein linux-x64-Release (Roadmap DC3).

Frage: Was ist nötig, damit ClawdDotNet unter Linux läuft, und was kostet das?


0. Kurzfassung

Die gute Nachricht zuerst: Der Kern ist bereits portabel. Alle 16 Bibliotheks- und beide Testprojekte zielen auf net10.0 (nicht net10.0-windows), es gibt im gesamten Repository kein einziges DllImport, keinen Registry-Zugriff und keine System.Drawing-Nutzung in src/. Windows steckt an genau drei Stellen im Kern: DPAPI-Verschlüsselung, Groß-/Kleinschreibung bei Pfadvergleichen und die Zeitzonen-IDs.

Die schlechte Nachricht: Die gesamte Bedienoberfläche — rund 8.900 Zeilen in frm_*.cs, UI/, Models/ und Services/ — hängt an Windows Forms, an WebView2 und, am unangenehmsten, an vier PropertyGrid-Instanzen, die praktisch die komplette Einstellungsverwaltung ausmachen. Dafür gibt es in Avalonia keine Eins-zu-eins-Entsprechung.

Empfehlung: den Umzug in zwei Schnitte teilen. Ein kopfloser Host (ohne GUI) auf Linux ist in etwa 1218 Personentagen erreichbar und liefert den eigentlichen Nutzen — Agenten laufen auf einem Server, nicht auf einem Windows-Desktop. Die Avalonia-Oberfläche ist ein davon unabhängiges Vorhaben von 3252 Personentagen, das man danach in Ruhe angehen kann.

Gesamt für „alles auf Linux, mit GUI": 5080 Personentage.


1. Bestandsaufnahme

1.1 Was bereits portabel ist

Bereich Zeilen Zielframework Windows-Abhängigkeit
src/ClawdDotNet.Core 8.959 net10.0 nur DPAPI (1 Datei)
15 Tool-Projekte 6.415 net10.0 nur .exe-Pfade im SocialMediaManager
tests/ (348 Tests, 39 Dateien) 6.888 net10.0 3 Testfälle mit C:\-Pfaden

Alle NuGet-Pakete laufen unter Linux: Microsoft.Data.Sqlite (bringt e_sqlite3 nativ für linux-x64/arm64 mit), MySqlConnector, Npgsql, Microsoft.Data.SqlClient, MongoDB.Driver, MailKit, FluentFTP, Telegram.Bot, WTelegramClient, SharpCompress, Snappier, Microsoft.Extensions.Logging. Kein Paket muss ersetzt werden — mit zwei Ausnahmen (siehe 2.1 und 2.3).

Auch die Dinge, bei denen man Ärger erwarten würde, sind sauber gelöst:

  • AtomicFile.cs:167Commit prüft File.Exists und weicht auf File.Move aus. File.Replace verlangt unter Unix ebenfalls eine vorhandene Zieldatei; der Fall ist also schon abgedeckt. Die Wiederholschleife ist unter Linux überflüssig, aber harmlos.
  • TaskFrontmatter.cs:27 — normalisiert \r\n und \r vor dem Zerlegen. Task-Dateien von einem Windows-Rechner werden unter Linux korrekt gelesen.
  • Textdateien werden durchgängig als UTF-8 ohne BOM geschrieben (AtomicFile, FileLogWriter, AgentEditorTool). Kein Encoding.Default, keine Codepage-Fallen.
  • Zeitstempel gehen als DateTime.UtcNow in die Datenbank und werden mit DateTimeStyles.RoundtripKind gelesen.

1.2 Was am Windows-Teil hängt

Bereich Zeilen davon Designer
frm_*.cs (6 Formulare + Dialoge) 4.946 1.865
UI/ (BackupPanel, WebViewBridge, EmbeddedUiManager) 1.087 428
Models/ (PropertyGrid-ViewModels) 1.257
Services/ (4 Dienste, an WinForms-Timer gekoppelt) 1.501
Properties/ 123
Summe 8.914 2.293

Dazu drei .resx-Dateien à ~272 KB (eingebettete Symbole/Bilder) und eine frm_main.en.resx für die englische Lokalisierung über den WinForms-Resx-Mechanismus.

Steuerelement-Inventar aus den Designer-Dateien: 24 Label, 19 ToolStripButton, 12 TabPage, 12 Button, 9 TextBox, 6 DataGridView, 6 ToolStrip, 5 ComboBox, 4 PropertyGrid, 4 TableLayoutPanel, 4 FlowLayoutPanel, 3 TabControl, 3 SplitContainer, 1 RichTextBox, 1 ListView, 1 NotifyIcon, 1 DateTimePicker, 1 NumericUpDown.

Tabs in frm_main: Chat, Logs, Settings (mit Unter-Tabs App-Settings, Instance-Settings), Agent Settings, Jobs/Services (mit Unter-Tabs Jobs, Services, Job History), Info, Backup.


2. Die harten Brocken

2.1 WebView2 → kein Linux (Chat- und Übersichts-Ansicht)

Microsoft.Web.WebView2 ist die einzige Windows-only-Paketabhängigkeit des Hauptprojekts und trägt die zwei sichtbarsten Ansichten: frm_main.cs:235 und frm_chat.cs:44 laden overview.html bzw. chat.html aus EmbeddedUI/ über SetVirtualHostNameToFolderMapping unter https://ui.clwd.internal/. Die Kommunikation läuft über WebViewBridge.csWebMessageReceived in die eine, ExecuteScriptAsync in die andere Richtung.

Drei Wege, jeder mit einem eigenen Preis:

Variante Was passiert Aufwand Risiko
A — Avalonia.WebView HTML/JS bleiben. Unter Linux rendert WebKitGTK, unter Windows weiterhin WebView2. Die Bridge wird auf die Abstraktion der Bibliothek umgeschrieben. 46 PT Bibliothek ist deutlich weniger reif als WebView2; WebKitGTK-Abhängigkeit muss auf dem Zielserver vorhanden sein; Verhalten unterscheidet sich je Plattform.
B — nativ neu in Avalonia Chat als echte Avalonia-Ansicht mit ItemsControl und einem Markdown-Renderer. EmbeddedUI/ entfällt. 812 PT Kein Fremdrisiko, aber Neuentwicklung. Am Ende deutlich wartbarer als HTML-in-Container.
C — lokaler HTTP-Server + Systembrowser Die App liefert EmbeddedUI/ über http://localhost:port aus, der Nutzer öffnet den Browser. 34 PT Bricht die Ein-Fenster-Anmutung. Passt aber ausgezeichnet zum kopflosen Betrieb — dort ist der Browser die Oberfläche.

Empfehlung: C für den kopflosen Host (fällt dort ohnehin an), B für die Desktop-Oberfläche. Variante A koppelt uns an eine Bibliothek, die weniger stabil ist als alles andere im Projekt.

2.2 PropertyGrid → es gibt keinen Ersatz von der Stange

Vier PropertyGrid-Instanzen in frm_main.Designer.cs bilden App-Settings, Instance-Settings, Agent-Settings und Tool-Settings ab. Sie werden vollständig durch Attribute gesteuert — 246 [Category], [DisplayName], [Description]-Angaben verteilt auf vier Dateien:

Dazu kommen [TypeConverter(typeof(ExpandableObjectConverter))] für verschachtelte Objekte, [PasswordPropertyText(true)] für Geheimnisse und ein eigener ModelTypeConverter, der das Modell-Auswahlfeld dynamisch aus der OpenRouter-Modellliste füllt.

Avalonia hat kein PropertyGrid. Zwei Möglichkeiten:

  1. Avalonia.PropertyGrid (Community, MIT). Versteht Category, DisplayName, Description, Browsable, ReadOnly und ExpandableObjectConverter. Die ViewModels und ihre Attribute könnten weitgehend unverändert bleiben — das spart am meisten. Zu prüfen ist, ob der dynamische ModelTypeConverter mit GetStandardValues unterstützt wird; das ist der Punkt, an dem so etwas erfahrungsgemäß hakt. Aufwand 68 PT, plus dauerhafte Abhängigkeit an ein Ein-Personen-Projekt.
  2. Von Hand gebaute Einstellungsformulare. Mehr Arbeit, aber wir bekommen eine Oberfläche, die man Nutzern zumuten kann — das PropertyGrid ist ehrlicherweise eine Entwickleransicht. Passwörter, Verzeichnisauswahl, Validierung und die Modell-Auswahl werden dabei richtig statt behelfsmäßig. Aufwand 1014 PT.

Das ist der größte Einzelposten der GUI-Portierung. Die Entscheidung kann und sollte man verschieben, bis das Grundgerüst steht.

2.3 DPAPI → Geheimnisse liegen unter Linux im Klartext

SecretProtector.cs:42:

if (!OperatingSystem.IsWindows())
    return plainText;

Unter Linux verschlüsselt Protect stillschweigend nicht. OpenRouter-Key, Datenbank-Verbindungszeichenfolgen mit Passwort, Mail-Zugangsdaten und das Telegram-2FA-Passwort lägen im Klartext in InstanceConfig.json und AgentSettings.json — genau der Zustand, den S7 behoben hat. Auf einem Server, der per SSH erreichbar ist und gesichert wird, ist das schlechter als auf einem Einzelplatz-Windows.

Dasselbe gilt für den Lizenz-Zustandsspeicher: LicenseLabrador/client-dotnet/.../StateStore.cs:92 schützt seine Datei ebenfalls nur unter Windows per DPAPI.

Zu klären ist also ein plattformübergreifendes Verfahren. Realistisch:

  • AES-GCM mit Schlüssel aus einer Datei mit 0600 neben der Konfiguration (Linux) bzw. weiterhin DPAPI (Windows). Einfach, wirkt gegen versehentliche Weitergabe und Backups, nicht gegen einen Angreifer mit demselben Benutzer — dieselbe Schutzstufe wie DPAPI heute.
  • Optional zusätzlich libsecret/Schlüsselbund, wenn eine Desktop-Sitzung da ist. Auf einem Server gibt es die nicht, also braucht es den Dateiweg ohnehin.

Nebenwirkung, die man einplanen muss: Konfigurationen sind nicht mehr zwischen Betriebssystemen austauschbar. Ein enc:v1:-Wert von Windows ist unter Linux nicht lesbar und umgekehrt. Unprotect wirft dann korrekterweise eine SecretProtectionException (SecretProtector.cs:81) — für den Umzug einer Instanz braucht es einen Migrationsweg (Präfix enc:v2:, Werte neu eintragen oder ein Export/Import-Kommando).

Aufwand 35 PT inklusive Tests und Migration.

2.4 Zeitzonen → das ist die stillste Fehlerquelle

TaskSchedule.cs:161:

try { return TimeZoneInfo.FindSystemTimeZoneById(id); }
catch { return TimeZoneInfo.Utc; }

Und SchedulerTaskMigration.cs:32 schreibt TimeZoneInfo.Local.Id in die Task-Frontmatter. Auf dem Entwicklungsrechner ergibt das "W. Europe Standard Time", unter Linux "Europe/Berlin".

Task-Dateien sind Markdown im SharedWorkspace und wandern zwischen Rechnern. Trifft eine Windows-ID auf ein System ohne die Umsetzungsdaten, greift das catch — und der Task läuft ab sofort nach UTC statt Ortszeit, also im Sommer zwei Stunden zu früh. Ohne Fehlermeldung, ohne Logeintrag. Ein Task, der um 08:00 die Marktübersicht holen soll, läuft um 06:00.

.NET 6+ kann Windows-IDs unter Linux über ICU auflösen, aber nur wenn ICU vorhanden ist. In einem schlanken Container (Alpine ohne icu-libs, distroless) oder bei InvariantGlobalization=true ist es das nicht — dann schlägt jede Auflösung fehl und alles fällt auf UTC.

Was zu tun ist:

  • Beim Schreiben auf IANA normalisieren (TimeZoneInfo.TryConvertWindowsIdToIanaId), beim Lesen beide Formen akzeptieren.
  • Das catch nicht mehr still schlucken — eine unbekannte Zeitzone muss protokolliert werden, besser noch den Task als fehlerhaft markieren.
  • Das Zielsystem muss tzdata haben. Für Container explizit installieren.

Verwandt: 82 Vorkommen von DateTime.Now/UtcNow. Die meisten sind unkritisch, zwei fallen auf: TaskboardService.cs:80 schreibt DateTime.Now-Zeitstempel in Task-Dateien, und LiveLogViewerService.cs:98 sucht die Logdatei des Tages über DateTime.Now. Server laufen üblicherweise mit TZ=UTC — dort wechselt die Logdatei dann um 02:00 Ortszeit statt um Mitternacht, und Task-Zeitstempel bekommen eine andere Bedeutung als bisher. Kein Fehler, aber eine Verhaltensänderung, die man kennen sollte.

Aufwand 23 PT.

2.5 Groß-/Kleinschreibung bei Pfaden → sicherheitsrelevant

Linux-Dateisysteme unterscheiden Groß- und Kleinschreibung, Windows nicht. An vier Stellen wird das Gegenteil angenommen — und drei davon bewachen eine Sandbox-Grenze:

Unter Linux sind /home/x/Workspace und /home/x/workspace zwei verschiedene Verzeichnisse. Der Vergleich mit OrdinalIgnoreCase würde einen Pfad im zweiten als „innerhalb" des ersten durchwinken. Genauso liefe die Sperrliste in FileRWTool ins Leere, sobald jemand die Schreibweise ändert.

Nötig ist ein Vergleichsverfahren, das die Plattform berücksichtigt — ein PathComparer, der unter Windows OrdinalIgnoreCase und unter Unix Ordinal verwendet, konsequent an allen vier Stellen.

Ebenfalls betroffen, aber harmlos: AtomicFile.cs:35 schlüsselt seine Sperren mit fullPath.ToLowerInvariant(). Unter Linux teilen sich damit zwei verschiedene Dateien eine Sperre — das serialisiert zu viel, gefährdet aber nichts.

Aufwand 23 PT, davon der größere Teil Tests.

2.6 Prozessaufrufe und .exe-Annahmen

  • Process.Start("explorer.exe", …) — 4 Stellen (frm_main.cs:1552, frm_main.cs:1557, frm_main.cs:1562, BackupPanel.cs:338). Ersatz: Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }) bzw. xdg-open. Die Variante explorer.exe /select,"…" hat unter Linux kein Gegenstück — dort öffnet man nur den Ordner.
  • Microsoft.VisualBasic.Interaction.InputBox — 3 Stellen (Program.cs:280, Program.cs:291, frm_main.cs:658), zwei davon für den interaktiven Telegram-Login (Code und 2FA-Passwort). Braucht einen eigenen Dialog. Für den kopflosen Betrieb ohnehin problematisch: ein Login, der ein Eingabefenster öffnet, blockiert einen Dienst. Dort muss der Telegram-Login anders gelöst werden (vorab per CLI, oder über die Weboberfläche).
  • yt-dlp.exe / ffmpeg.exeSocialMediaManagerTool.cs:782 und :825. Die PATH-Suche davor funktioniert unter Linux bereits; nur die Ausweichpfade sind fest auf .exe verdrahtet und laufen dort ins Leere. Kleine Änderung, aber sie fällt sonst erst zur Laufzeit auf.

Aufwand zusammen 12 PT.

2.7 WinForms-Timer in der Dienstschicht

Services/ ist logisch kein UI-Code, hängt aber an System.Windows.Forms.Timer:

Der Backup-Zeitplan und die Lizenzprüfung gehören in den kopflosen Host und müssen dafür auf System.Threading.PeriodicTimer umgestellt werden. Der Log-Betrachter ist echte Oberfläche und wird ohnehin neu gebaut.

Aufwand 23 PT.

2.8 Lizenzierung — erledigt (Stand 2026-08-06)

Nachtrag. LicenseLabrador und WatchDog sind im Deploymentcenter zusammengefasst, Hardware-ID v2 ist dort umgesetzt. Der Client (Deploymentcenter.Client, netstandard2.0;net8.0) läuft auf beiden Plattformen, die HW-ID ist container- und umbenennungsfest, der Ablageort löst sich auch ohne HOME auf, und der Zustandsspeicher ist mit AES-GCM plattformübergreifend verschlüsselt.

Damit ist die einzige potenziell blockierende Frage dieser Analyse beantwortet. Details und offene Punkte der Anbindung: Deploymentcenter-Anbindung-Review.md.

Es bleibt reine Anschlussarbeit in ClawdDotNet: Projektverweis umhängen, LicenseGate gegen die neue Ergebnisklasse schreiben (LicenseState ist entfallen, Status ist jetzt eine Zeichenkette), MessageBox durch das mitgelieferte ILicensePrompt ersetzen und die Lizenzprüfung aus Program.cs:112 fensterfrei machen.

Aufwand 23 PT (vorher 35).


3. Kleinere Punkte, die trotzdem beißen

3.1 Kultur- und Zahlenformatierung

Nur 16 Stellen im gesamten Projekt nennen eine Kultur explizit. Das heißt umgekehrt: fast alles formatiert mit CurrentCulture. Auf dem Entwicklungsrechner ist das de-DE, auf einem Server mit unbesetztem LANG ist es InvariantCulture. Aus 1,25 wird 1.25.

Wo das folgenlos bleibt:

  • JSONSystem.Text.Json schreibt Zahlen immer invariant. Alle Konfigurationen, Zustandsdateien und API-Aufrufe sind sicher.
  • SQLite — Werte gehen typisiert über Parameter, nicht als Text.

Wo hinzuschauen ist:

  • Zeichenkettenverkettung in Logeinträgen und Prompts ($"{cost:F4}"). Wenn eine Zahl mit deutschem Dezimalkomma in einen Prompt gerät, muss das Modell raten.
  • Anzeigewerte in der Oberfläche — dort ist Ortsformat gewünscht, aber es sollte bewusst gesetzt sein, nicht zufällig.

Empfehlung: einmal alle Formatierungen durchgehen und trennen — invariant für alles Maschinenlesbare, CurrentCulture nur für die Anzeige. Am besten mit einem Analyzer (CA1305, CA1304, CA1310) als Warnung im Build, damit es so bleibt.

Aufwand 23 PT.

3.2 Globalisierungsmodus festlegen

InvariantGlobalization=true macht das Publikat kleiner und ICU überflüssig — kostet aber TimeZoneInfo.FindSystemTimeZoneById (siehe 2.4), kulturabhängige Vergleiche und korrektes ToLower() für Umlaute. Für dieses Projekt mit zeitzonenabhängiger Planung ist das keine Option; die Entscheidung sollte im Projekt dokumentiert und ICU/tzdata als Voraussetzung festgehalten werden.

Nebenbemerkung: COLLATE NOCASE in SqliteMemoryRepository.cs:143 und SqliteTaskRepository.cs:105 ist ASCII-beschränkt — Ä und ä gelten SQLite als verschieden. Das ist heute schon so und ändert sich beim Umzug nicht, ist also kein Portierungsthema, sondern eine bestehende Eigenheit.

3.3 Zeilenenden

1.230 Stellen verwenden Environment.NewLine oder \r\n. Für Logdateien ist das egal. Bei Task-Dateien und Agenten-erzeugten Dateien im geteilten Arbeitsverzeichnis führt es zu Rauschen: Eine Datei, die unter Windows geschrieben und unter Linux angefasst wird, ändert komplett ihre Zeilenenden. Wenn der Arbeitsbereich unter Git liegt oder synchronisiert wird, sieht jede Änderung wie eine Vollumschreibung aus. Der Parser kommt damit klar (siehe 1.1) — es ist eine Frage der Ordnung, kein Fehler. Empfehlung: für Task- und Konfigurationsdateien fest \n schreiben.

3.4 Dateinamen

Path.GetInvalidFileNameChars() liefert unter Windows 41 Zeichen, unter Linux genau zwei (\0 und /). FileLogWriter.cs:95 säubert Modulnamen damit — unter Linux entstehen also Dateinamen, die auf Windows nicht mehr lesbar sind. Betrifft Sicherungen, die zwischen Systemen wandern. Ebenso die Windows-Sonderfälle CON, PRN, AUX und Namen mit abschließendem Punkt: unter Linux erlaubt, beim Rückspielen auf Windows nicht. Für den Sicherungs-/Wiederherstellungsweg über Systemgrenzen hinweg relevant.

3.5 Ablageorte

SettingsManager.cs:24 legt AppSettings.json neben die Programmdatei (AppDomain.CurrentDomain.BaseDirectory). Unter Windows in einem Benutzerverzeichnis geht das; unter Linux liegt die Anwendung typisch in /opt/… oder /usr/lib/… und ist für den Dienstbenutzer nicht schreibbar. Dasselbe gilt für die Zielordner tools/, Logs/ und Instances/, die die Build-Ziele in ClawdDotNet.csproj neben der Programmdatei anlegen.

Nötig ist eine Trennung von Programm und Daten nach XDG-Konvention: $XDG_CONFIG_HOME bzw. /etc/clawddotnet für die Konfiguration, $XDG_DATA_HOME bzw. /var/lib/clawddotnet für Instanzen und Datenbanken, /var/log/clawddotnet für Logs. Dazu Dateirechte: Instanzverzeichnisse mit Geheimnissen gehören auf 0700, Konfigurationsdateien auf 0600 — unter Windows regelt das die ACL des Benutzerprofils, unter Linux muss man es setzen.

Aufwand 23 PT.

3.6 Tests

Von 348 Tests sind fast alle portabel. Auffällig ist WorkspacePathTests.cs:49:

[InlineData(@"C:\Windows\System32\config\SAM")]
[InlineData(@"\\server\share\evil.txt")]
[InlineData(@"C:\temp\datei.txt")]

Unter Linux liefert Path.IsPathRooted(@"C:\temp\datei.txt") false — das ist ein gewöhnlicher relativer Dateiname mit Doppelpunkt und Backslashes darin. Der Test prüft dort also etwas anderes als beabsichtigt. Und da er die Sandbox-Grenze absichert, ist das keine Kleinigkeit: Er muss betriebssystemabhängig aufgeteilt werden, mit einer eigenen Linux-Fassung (/etc/passwd, ../../etc/passwd, Symlinks). Symlinks sind überhaupt ein Prüfpunkt, den es unter Windows so nicht gab — Path.GetFullPath löst sie nicht auf, File.ResolveLinkTarget schon. Ein Agent könnte im Arbeitsverzeichnis einen Symlink nach /etc anlegen und die Prüfung ginge durch.

Ebenso in YouTubeUrlTests.cs:118 (harmlos, nur Beispieldaten).

Aufwand 24 PT, inklusive Symlink-Absicherung in WorkspacePath selbst.

3.7 Bau und Auslieferung

Deploy-Build.ps1 setzt PowerShell 5.1 voraus, verwendet Backslash-Pfade und den festen Ausgabepfad bin\Release\net10.0-windows. Für Linux braucht es entweder eine pwsh-taugliche Fassung oder — besser — einen schlichten dotnet publish -r linux-x64 --self-contained mit einer systemd-Unit-Datei. Dazu:

  • systemd-Unit mit eigenem Dienstbenutzer, Restart=on-failure
  • Prüfen, ob der bestehende Watchdog-Heartbeat (Program.cs:367) mit systemd-notify zusammenspielen soll
  • optional .deb oder AppImage für den Desktop-Fall

Aufwand 35 PT.


4. Der empfohlene Schnitt

Der entscheidende Befund dieser Analyse: Die Oberfläche ist nicht der Grund, warum wir Linux wollen. Der Grund ist, dass Agenten auf einem Server laufen sollen. Program.cs baut bereits alles — Speicher, Engine, Taskboard-Scanner, Watchdog, Lizenzprüfung — vollständig auf, bevor frm_main überhaupt entsteht (Zeilen 36385 gegen 388401). Diese Trennung existiert faktisch schon; sie muss nur formalisiert werden.

Stufe 1 — Kern Linux-fest und kopfloser Host (1218 PT)

Schritt PT
Geheimnisse plattformübergreifend (2.3) 35
Zeitzonen normalisieren, Fehler nicht mehr schlucken (2.4) 23
Pfadvergleiche plattformabhängig + Symlink-Prüfung (2.5, 3.6) 35
Prozessaufrufe, .exe-Pfade (2.6) 12
Ablageorte und Dateirechte nach XDG (3.5) 23
ClawdDotNet.Host — Startlogik aus Program.cs herauslösen, PeriodicTimer statt WinForms-Timer, Telegram-Login ohne Dialog 46
Tests auf Linux grün, CI-Lauf für linux-x64 23

Ergebnis: Die Anwendung läuft als systemd-Dienst auf einem Linux-Server. Die Windows-GUI bleibt unverändert bestehen und wird weiter benutzt. Das ist der Punkt, an dem der Nutzen anfällt.

Stufe 2 — Avalonia-Oberfläche (3252 PT)

Schritt PT
Grundgerüst: Avalonia-Projekt, DI, Dispatcher, Shell mit Tabs, MVVM-Schicht 57
Logs-Tab (RichTextBoxSelectingItemsControl mit Filterung) 23
Agent-Settings: Liste, Werkzeugauswahl, Aktionsschaltflächen 58
Einstellungs-Tabs — PropertyGrid-Ersatz (2.2) 610
Jobs / Services / Job History (4 DataGridView) 46
Backup-Panel 34
Instance-Manager und die fünf Dialoge 46
Chat-Ansicht (Variante B, siehe 2.1) 812
Info, Statusleiste, Werkzeugleisten, Menü, Lokalisierung de/en 34

Die Spanne ist breit, weil zwei Entscheidungen noch offen sind (PropertyGrid-Ersatz und Chat-Variante). Sind die getroffen, lässt sich das auf etwa ±15 % genau angeben.

Stufe 3 — Auslieferung und Härtung (610 PT)

Publish-Pipeline, systemd-Unit, Paketierung, Abnahme auf echter Hardware, Dokumentation, Umzugsweg für bestehende Instanzen.

Gesamt

PT bei Vollzeit
Stufe 1 1218 2,53,5 Wochen
Stufe 2 3252 6,510,5 Wochen
Stufe 3 610 1,52 Wochen
Summe 5080 1016 Wochen

5. LiveCharts2

Zur Einordnung: Das Projekt enthält heute keine einzige Diagrammdarstellung. Die Suche nach Chart, Series oder Plot findet nur JSON-Feldnamen der Yahoo-Finance-Abfrage in DirectAPITool.cs:126.

LiveCharts2 ist damit keine Portierung, sondern neue Funktionalität — sie gehört zum Trading-Teil, nicht zum Linux-Umzug, und ist in den 5080 PT oben nicht enthalten. Wenn die Kursansichten kommen, sind dafür grob 510 PT zusätzlich zu rechnen. Das passt zu dem, was in docs/Roadmap.md und der Notiz „Basis vor Trading härten" festgehalten ist: Erst die Basis, dann die Handelsansichten.

Ein Punkt, der jetzt schon zählt: LiveCharts2 setzt auf SkiaSharp, genau wie Avalonia. Das spricht zusätzlich dafür, die Diagramme erst nach der Avalonia-Portierung zu bauen — sonst entstehen sie zweimal.


6. Was vor dem ersten Handgriff zu entscheiden ist

  1. Ist das Ziel Server oder Desktop? Bei „Server" reicht Stufe 1, und Stufe 2 kann entfallen oder durch eine Weboberfläche ersetzt werden. Das ändert die Schätzung um den Faktor drei.
  2. Chat-Ansicht: HTML behalten oder nativ neu bauen? (2.1)
  3. PropertyGrid: Fremdbibliothek oder eigene Formulare? (2.2)
  4. Erlaubt LicenseLabrador den Wechsel der Hardware-ID?geklärt, siehe 2.8 und Deploymentcenter-Anbindung-Review.md.
  5. Bleibt Windows als Zielplattform bestehen? Wenn ja, muss alles doppelt getestet werden, und die Geheimnis-Verschlüsselung braucht beide Wege plus Umzugspfad. Wenn nein, wird 2.3 deutlich einfacher.

Frage 1 und 5 beantworten sich vermutlich schnell; 2 und 3 kann man bis zum Beginn von Stufe 2 offenlassen, ohne Stufe 1 zu blockieren. Damit liegt nichts mehr außerhalb unserer Hand — Stufe 1 kann beginnen.


Anhang — Vollständige Fundstellenliste

Thema Datei:Zeile
DPAPI SecretProtector.cs:42, :66, :90, :94
DPAPI (Lizenz) LicenseLabrador/client-dotnet/.../StateStore.cs:43, :92
Zeitzone TaskSchedule.cs:161, SchedulerTaskMigration.cs:32
Pfad-Groß-/Kleinschreibung WorkspacePath.cs:72, FileRWTool.cs:174, FtpTool.cs:155, BackupService.cs:384, AtomicFile.cs:35
explorer.exe frm_main.cs:1552, :1557, :1562, BackupPanel.cs:338
VisualBasic.InputBox Program.cs:280, :291, frm_main.cs:658
.exe-Werkzeugpfade SocialMediaManagerTool.cs:782, :825
WinForms-Timer BackupScheduler.cs:41, LiveLogViewerService.cs:38, OpenRouterStatusService.cs:43, frm_main.License.cs:41
WebView2 frm_main.cs:235, frm_chat.cs:44, WebViewBridge.cs, ClawdDotNet.csproj
PropertyGrid frm_main.Designer.cs (4×), Models/ (246 Attribute)
Datenablage SettingsManager.cs:24, ClawdDotNet.csproj (Build-Ziele)
Testdaten mit Windows-Pfaden WorkspacePathTests.cs:49
Build-Skript Deploy-Build.ps1