UpdateService: Zugangsschutz, Plattform und sauberer Agent-Uebergang

Seit Deploymentcenter 2.4 liegt die Release-Ablage hinter HTTP-Basic-Auth mit
dem Lizenzschluessel als Zugangsdatum. Ohne ihn antwortet jeder Paketabruf mit
401. Predictalytics hat noch kein Release veroeffentlicht; nach UPGRADE.md
16.1 entsteht der Schutz fuer ein Produkt mit dem ersten Upload, der erste
ausgelieferte Build muss die Zugangsdaten also bereits mitbringen.

- DcUpdateService reicht licenseKey und platform (PlatformId.Current) an
  CheckForUpdateAsync und LaunchUpdateAgent weiter. Fehlt der Schluessel am
  Aufruf, greift LicenseClient.TryGetCachedKey - damit ist der Headless-Pfad
  mit abgedeckt.
- LaunchAgent startet ohne Lizenzschluessel gar nicht erst. Sonst schliesst
  sich die Anwendung, der Agent laeuft in einen 401, und zurueck bleibt eine
  geschlossene App ohne Update.
- restartPath und waitForCurrentProcess werden gesetzt: der Agent wartet auf
  das Ende dieses Prozesses, statt auf noch gesperrte Assemblies zu schreiben,
  und startet die Anwendung danach wieder. Ein null-restartPath ist dafuer
  nicht brauchbar - das SDK ersetzt ihn durch denselben ProcessPath -, beim
  Start ueber "dotnet App.dll" wird deshalb der Apphost aufgeloest.
- result.Unauthorized wird vor dem "ist aktuell"-Zweig behandelt. Bei 401
  setzt das SDK weder Error noch UpdateAvailable; die Pruefung meldete bisher
  "aktuell", waehrend in Wirklichkeit keine Updates mehr ankamen.
- Version zentral in Directory.Build.props, damit Hosting und Shell nicht
  auseinanderlaufen.
- run.sh setzt unter Linux das beim Entpacken verlorene Ausfuehrungsbit.
  .gitattributes haelt Shell-Skripte auf LF: mit core.autocrlf=true traegt die
  Arbeitskopie sonst CRLF, und CopyToPublishDirectory nimmt genau die ins
  Linux-Paket - dort scheitert der Start an "bad interpreter".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-15 09:43:44 +02:00
co-authored by Claude Opus 5
parent 6e8ccba258
commit 5ccd4f5f4e
7 changed files with 122 additions and 16 deletions
@@ -191,7 +191,24 @@ public sealed partial class MainWindowViewModel : ObservableObject
{
try
{
var result = await DcUpdateService.CheckAsync(Options.DcUpdateChannel);
var result = await DcUpdateService.CheckAsync(Options.DcUpdateChannel, License?.LicenseKey);
// 401 is a licensing statement, not a network hiccup — and it must be caught
// before the "up to date" branch: the server answers no version at all, so
// silently reporting "aktuell" would hide that updates stopped arriving.
if (result.Unauthorized)
{
Log.Error("⛔ Die Release-Ablage weist die Lizenz zurück — es kommen keine Updates mehr an: {Message}",
result.Message);
BuildVersionText = $"v{DcConfig.AppVersion} — Update-Zugang abgelehnt";
if (!silent && ShowError != null)
await ShowError("Deployment Center",
"Die Release-Ablage hat den Lizenzschlüssel abgelehnt (401).\n\n" +
$"{result.Message}\n\n" +
"Das ist kein Netzwerkproblem: Die Lizenz ist abgelaufen oder widerrufen. " +
"Solange das so bleibt, erreichen diese Installation keine Updates.");
return;
}
if (result.Error is not null)
{
@@ -237,7 +254,11 @@ public sealed partial class MainWindowViewModel : ObservableObject
// The agent replaces the running installation, so announce the shutdown first —
// otherwise the monitor reports a crash a few minutes later.
_heartbeat?.NotifyStopping();
DcUpdateService.LaunchAgent(Options.DcUpdateChannel);
if (!DcUpdateService.LaunchAgent(Options.DcUpdateChannel, License?.LicenseKey) && ShowError != null)
{
await ShowError("Deployment Center",
"Der Update-Agent konnte nicht gestartet werden. Details stehen im Log.");
}
}
catch (Exception ex)
{