Arbeitsstand gesichert: Deploymentcenter-Integration (D-0 bis D-5) und Einfenster-Shell

Sicherungscommit vor dem Aufraeumen des Repos, damit nachvollziehbar bleibt,
welcher Stand vor der Bereinigung galt. Build gruen, 476 Tests gruen.

Zwei Straenge, die sich ueber .csproj, Program.cs und appsettings.json
ueberschneiden und darum gemeinsam abgelegt werden:

Deploymentcenter-Integration (P3c, Plan D-0 bis D-5 code-seitig fertig):
- Deploymentcenter.Client 2.5.0 als lokales Paket, Source-Mapping erweitert
- DeploymentcenterOptions, DeploymentcenterErrorReporter, LicenseGate/LicenseCli
- WatchdogHeartbeatService auf die Deploymentcenter-API umgestellt
  (version, os, checks, metrics, status stopped)
- Security: MasterKeyResolver, SecretRedactor, FilePermissions
- Directory.Build.props mit zentraler Version 0.1.0 (Packager-Versionsdisziplin)
- deploy/: Packager-Vorlage und systemd-Unit; echte Zugangsdaten bleiben
  ueber .gitignore aussen vor
- setup.json fuer die Erstinstallation
- UMSETZUNGSPLAN-Deploymentcenter-Integration.md; ANALYSE-Linux-Portierung.md
  verweist auf den neuen Plan

Einfenster-Shell (UI-Redesign):
- ShellWindow + ShellNavModel als Seitenleisten-Shell
- WindowMenuBar und WindowMenuModel entfallen
- Modul- und Kernfenster auf die Shell-Einbettung angepasst

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-22 10:42:22 +02:00
co-authored by Claude Opus 5
parent ac5e16f0a6
commit dd8da3fd3f
58 changed files with 3932 additions and 650 deletions
@@ -0,0 +1,64 @@
using System;
using System.IO;
using System.Security.Cryptography;
namespace PolyTrader.Core.Security
{
/// <summary>
/// Löst den AES-Master-Key auf, mit dem <see cref="SecretProtection"/> Secrets at-rest
/// verschlüsselt (D-5/D-10 aus dem Deploymentcenter-Umsetzungsplan).
///
/// Reihenfolge: Umgebungsvariable, dann eine vorhandene Schlüsseldatei, sonst wird ein neuer
/// erzeugt und geschrieben — das ist der Regelfall direkt nach <c>update-agent --action install</c>
/// auf einem frischen System, wo niemand von Hand einen Schlüssel eintippt.
///
/// **Die eine Regel, die nie verletzt werden darf:** Erzeugt wird AUSSCHLIESSLICH, wenn weder
/// Variable noch Datei existieren. Ein versehentliches Überschreiben würde jedes bereits
/// verschlüsselte Secret in der Datenbank unwiederbringlich unlesbar machen — der einzige
/// wirklich unumkehrbare Fehler in diesem gesamten Bereich. Ein zweiter Aufruf (jeder weitere
/// Programmstart) liest deshalb ausnahmslos die vorhandene Datei, statt neu zu erzeugen.
/// </summary>
public static class MasterKeyResolver
{
/// <param name="keyFilePath">Pfad der Schlüsseldatei (z.B. neben der Anwendung).</param>
/// <param name="readEnvironmentVariable">Liest z.B. POLYTRADER_MASTER_KEY. Injizierbar für Tests.</param>
/// <param name="onGenerated">Aufgerufen, wenn ein neuer Schlüssel erzeugt wurde (Pfad als Parameter).</param>
/// <param name="onWarning">Aufgerufen bei einem nicht fatalen Problem (z.B. Dateirechte, Erzeugung fehlgeschlagen).</param>
public static string? Resolve(
string keyFilePath,
Func<string?> readEnvironmentVariable,
Action<string>? onGenerated = null,
Action<string>? onWarning = null)
{
string? masterKey = readEnvironmentVariable();
if (!string.IsNullOrWhiteSpace(masterKey))
{
return masterKey;
}
if (File.Exists(keyFilePath))
{
return File.ReadAllText(keyFilePath).Trim();
}
try
{
byte[] keyBytes = RandomNumberGenerator.GetBytes(32); // 256-Bit-Schlüssel
string generated = Convert.ToBase64String(keyBytes);
File.WriteAllText(keyFilePath, generated);
FilePermissions.RestrictToOwner(keyFilePath, onWarning);
onGenerated?.Invoke(keyFilePath);
return generated;
}
catch (Exception ex)
{
// Erzeugung darf den Start nie verhindern - ohne Master-Key laufen Secrets eben
// unverschlüsselt weiter (wie bisher ohne POLYTRADER_MASTER_KEY), statt abzustürzen.
onWarning?.Invoke($"Master-Key konnte nicht automatisch erzeugt werden: {ex.Message}.");
return null;
}
}
}
}