Drei Dinge, die beim ersten Lauf des Installers auf einer Linux-Maschine auffielen. 1. Die Release-Ablage wies Administratorkonten ab. ReleaseGuard nahm nur die Rolle 'installer' in die .htpasswd auf, waehrend Installskripte und Agent ausdruecklich sagten, ein Administratorkonto tue es auch: Anmeldung und Katalog gelangen, erst der Download endete mit 401 - und die Meldung sprach von abgelaufenen Lizenzen, die es bei einer Erstinstallation gar nicht geben kann. Adminkonten zaehlen jetzt zu den Installationskonten. FORMAT_VERSION auf 3, damit reconcile() die Dateien sofort neu schreibt statt erst beim naechsten turnusmaessigen Lauf; ein neu angelegtes Konto landet ausserdem unabhaengig von seiner Rolle sofort darin. Bei einem 401 mit Benutzerzugangsdaten nennt der Client jetzt Konto und zugangsberechtigte Rollen, und der Agent bricht ab, statt ueber die API weiterzusuchen und dieselbe Meldung ein paar Schritte spaeter ein zweites Mal zu zeigen. 2. Der Installer prueft die .NET-Laufzeit. Bisher endete eine gelungene Installation auf einer Maschine ohne .NET mit einer Anwendung, die sich nicht starten laesst - und die Fehlersuche begann beim Deploymentcenter, weil das der letzte bewusste Schritt war. Gelesen wird die runtimeconfig.json der Anwendung und mit "dotnet --list-runtimes" verglichen; fehlt etwas, nennt der Installer den Installationsbefehl fuer diese Plattform. Eigenstaendig veroeffentlichte Pakete werden nicht bemaengelt, rollForward wird beachtet. 3. Die Ueberwachung der Maschine entsteht im Installer. Zwei Fragen - Name im Dashboard und ob eingeplant werden soll - statt fuenf Schritten in der Oberflaeche an einem anderen Rechner. Monitor, Token mit genau watchdog:ping, Skript, Dateirechte, ein Heartbeat zur Probe und der Cron-Eintrag bzw. die geplante Aufgabe entstehen daraus. Fuer Maschinen ohne Installation: --action monitor. Die Agent-Skripte werden jetzt in src/Modules/Watchdog/AgentScript.php erzeugt - von Oberflaeche und Installer gemeinsam - und melden Last, Speicher, Plattenbelegung und Laufzeit mit, statt nur "status: ok". Beim Ausfuehren fielen zwei Fehler auf, die dort behoben sind: df -P verrutscht bei Geraetenamen mit Leerzeichen (gezaehlt wird jetzt von hinten), und ohne LC_ALL=C erzeugt awk auf einem deutschen System "12,5" und damit kaputtes JSON. Neu: POST /api/setup/v1/agent. SDK 2.6.0 mit SetupClient.RequestWatchdogAgentAsync(). Die OpenAPI-Beschreibung des neuen Endpunkts bleibt zunaechst aussen vor: public/api/openapi.php traegt gerade auch fremde, noch nicht committete Aenderungen aus einer parallel laufenden Arbeit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
370 lines
24 KiB
JSON
370 lines
24 KiB
JSON
{
|
|
"_comment": "Einzige Quelle der Wahrheit fuer den Changelog. Wird von /docs/changelog.php gerendert und von GET /api/updateservice/v1/changelog?since=X ausgeliefert. Neue Eintraege oben einfuegen.",
|
|
|
|
"schema": 1,
|
|
"current": "2.6.0",
|
|
|
|
"versions": [
|
|
{
|
|
"version": "2.6.0",
|
|
"date": "2026-08-21",
|
|
"summary": "Der Installer prueft die Laufzeit und richtet die Ueberwachung des Hosts ein.",
|
|
"actionRequired": false,
|
|
"changes": [
|
|
{
|
|
"area": "client",
|
|
"breaking": false,
|
|
"title": "Installer prueft die .NET-Laufzeit",
|
|
"text": "Bisher endete eine gelungene Installation auf einer Maschine ohne .NET mit einer Anwendung, die sich nicht starten laesst - und die Fehlersuche begann beim Deploymentcenter, weil das der letzte bewusste Schritt war. Nach dem Uebertragen der Dateien liest der Installer jetzt die runtimeconfig.json der Anwendung, vergleicht sie mit \"dotnet --list-runtimes\" und nennt bei Bedarf den Installationsbefehl fuer diese Plattform. Eigenstaendig veroeffentlichte Pakete bringen ihre Laufzeit mit und werden nicht bemaengelt; rollForward wird beachtet.",
|
|
"action": ""
|
|
},
|
|
{
|
|
"area": "client",
|
|
"breaking": false,
|
|
"title": "Ueberwachung der Maschine direkt aus dem Installer",
|
|
"text": "Am Ende der Installation genuegen zwei Fragen - Name im Dashboard und ob eingeplant werden soll. Monitor, Token, Agent-Skript, Dateirechte, ein erster Heartbeat zur Probe und der Cron-Eintrag bzw. die geplante Aufgabe entstehen daraus. Fuer Maschinen ohne Installation, etwa den Hypervisor darunter, gibt es denselben Weg als \"--action monitor\".",
|
|
"action": "Keine. Wer die Frage verneint, holt es spaeter mit --action monitor nach."
|
|
},
|
|
{
|
|
"area": "server",
|
|
"breaking": false,
|
|
"title": "Agent-Skripte melden jetzt Kennzahlen",
|
|
"text": "Das aus der Oberflaeche heruntergeladene Skript schickte nur \"status: ok\" - damit beantwortete ein Host-Monitor genau eine Frage und keine der Fragen, wegen derer man auf einen Host schaut. Die Skripte melden jetzt Last, Speicherbelegung, Plattenbelegung und Laufzeit mit und stufen sich ab 90 Prozent Plattenbelegung selbst auf \"warning\". Erzeugt werden sie an einer Stelle (Modules/Watchdog/AgentScript.php), die Oberflaeche und Installer gemeinsam nutzen.",
|
|
"action": ""
|
|
},
|
|
{
|
|
"area": "api",
|
|
"breaking": false,
|
|
"title": "POST /api/setup/v1/agent",
|
|
"text": "Legt den Monitor an, stellt ein Token mit genau watchdog:ping aus und liefert das fertige Skript fuer den Host. Ein bereits vorhandener Monitor gleichen Namens wird mit 409 abgelehnt, damit nicht eine zweite Maschine stillschweigend die Historie der ersten uebernimmt; overwrite=true erlaubt es ausdruecklich. Im SDK: SetupClient.RequestWatchdogAgentAsync(...).",
|
|
"action": ""
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"version": "2.5.3",
|
|
"date": "2026-08-21",
|
|
"summary": "Administratorkonten kommen an die Release-Ablage.",
|
|
"actionRequired": false,
|
|
"changes": [
|
|
{
|
|
"area": "server",
|
|
"breaking": false,
|
|
"title": "Administratorkonten im Release-Zugangsschutz",
|
|
"text": "Der Zugangsschutz nahm bisher nur Konten der Rolle installer auf, waehrend die Installationsskripte und der Installer ausdruecklich sagten, ein Administratorkonto tue es auch. Eine Erstinstallation mit einem Administratorkonto meldete sich erfolgreich an, zeigte den Katalog und scheiterte erst beim Herunterladen mit einer Meldung ueber abgelaufene Lizenzen. Administratoren zaehlen jetzt zu den Installationskonten; die .htpasswd-Dateien werden beim naechsten Tick neu erzeugt. Neu angelegte Konten landen ausserdem sofort darin - zuvor galt das nur fuer die Rolle installer, ein frisches Administratorkonto wartete bis zum naechsten turnusmaessigen Lauf.",
|
|
"action": ""
|
|
},
|
|
{
|
|
"area": "client",
|
|
"breaking": false,
|
|
"title": "Ehrliche Meldung bei abgelehntem Zugang",
|
|
"text": "Ein 401 der Release-Ablage nannte immer Lizenzgruende - abgelaufen, widerrufen, falsches Produkt. Bei einer Erstinstallation gibt es noch gar keine Lizenz; die Meldung schickte damit in die falsche Richtung. Wurde mit Benutzername und Passwort zugegriffen, nennt sie jetzt das Konto und die Rollen, die Zugang haben. Ausserdem bricht der Agent bei 401 sofort ab, statt ueber die API weiterzusuchen und dieselbe Meldung ein paar Schritte spaeter ein zweites Mal zu zeigen.",
|
|
"action": ""
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"version": "2.5.2",
|
|
"date": "2026-08-15",
|
|
"summary": "LaunchUpdateAgent reicht --require-signature durch.",
|
|
"actionRequired": true,
|
|
"changes": [
|
|
{
|
|
"area": "sdk",
|
|
"breaking": false,
|
|
"title": "requireSignature-Parameter auf LaunchUpdateAgent",
|
|
"text": "--require-signature gab es bisher nur als Kommandozeilenschalter. Jede Anwendung, die dem Leitfaden folgt und ueber LaunchUpdateAgent startet - der empfohlene Weg - aktualisierte damit ungeprueft, waehrend derselbe Vorgang von Hand auf der Kommandozeile geschuetzt gewesen waere. Der oeffentliche Schluessel muss dafuer nicht separat verwaltet werden, der Agent holt und cached ihn ohnehin selbst.",
|
|
"action": "LaunchUpdateAgent(..., requireSignature: true) setzen, sobald server.security.release_private_key hinterlegt ist und alle ausgelieferten Releases signiert wurden."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"version": "2.5.1",
|
|
"date": "2026-08-14",
|
|
"summary": "Zugangsschutz je Produkt abschaltbar, Dienst-Betrieb dokumentiert, inkrementelle Builds repariert.",
|
|
"actionRequired": true,
|
|
"changes": [
|
|
{
|
|
"area": "server",
|
|
"breaking": false,
|
|
"title": "Produkte lassen sich vom Zugangsschutz ausnehmen",
|
|
"text": "Der Schutz entstand beim ersten Lauf nach dem Deployment von selbst - auch fuer Produkte, deren ausgelieferte Installationen noch ohne Lizenzschluessel bauen. Die bekamen ab dem Moment 401 statt Updates; bei Predictalytics ist genau das passiert. Unter UpdateService -> Zugangsschutz laesst sich je Produkt umschalten.",
|
|
"action": "Wer eine Installationsbasis ohne SDK 2.4 hat: Produkt ausnehmen, Build mit Schluessel ausliefern, danach wieder einschalten."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": false,
|
|
"title": "BuildInfo.targets verhindert keine inkrementellen Builds mehr",
|
|
"text": "BuildDateUtc trug die volle Uhrzeit und aenderte sich bei jedem Build; WriteOnlyWhenDifferent griff nie, und jedes einbindende Projekt wurde jedes Mal neu uebersetzt. Jetzt tagesgenau - hoechstens ein Neubau pro Tag.",
|
|
"action": "Keine. BuildInfo.BuildDateUtc enthaelt nur noch das Datum, nicht die Uhrzeit."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": true,
|
|
"title": "LicenseConfig.SetStorageDirectory haengt jetzt <slug>/license an",
|
|
"text": "Zuvor wurde der gesetzte Pfad roh benutzt, waehrend der Weg ueber DEPLOYMENTCENTER_STORAGE_DIR <slug>/license anhaengte. Zwei Produkte im selben Prozess mit gesetztem Override schrieben damit in dieselbe state.dat.",
|
|
"action": "Wer SetStorageDirectory benutzt: Der Cache liegt jetzt eine Ebene tiefer. Ein vorhandener Cache wird einmalig neu aufgebaut."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": false,
|
|
"title": "Lizenz-Cache faellt auf /var/lib/<slug> zurueck",
|
|
"text": "Eine systemd-Unit mit User= und ohne eigenes Heimatverzeichnis hat weder XDG_CONFIG_HOME noch HOME. Der Rueckfall landete im Installationsverzeichnis, unter /opt fuer den Dienstbenutzer nicht beschreibbar - also kein Offline-Cache, ausgerechnet im kopflosen Betrieb.",
|
|
"action": "Wenn /var/lib nicht beschreibbar ist: DEPLOYMENTCENTER_STORAGE_DIR setzen."
|
|
},
|
|
{
|
|
"area": "docs",
|
|
"breaking": false,
|
|
"title": "Lueckenschluss in den Leitfaeden",
|
|
"text": "Woher die Anwendung den Lizenzschluessel fuer den Update-Zugang nimmt (TryGetCachedKey), wie ein laufender systemd-Dienst aktualisiert wird, was statt Environment.Exit(1) zu tun ist, wenn Beenden die gefaehrlichere Option waere, und die Drosselungsgrenzen aller Endpunkte.",
|
|
"action": null
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"version": "2.5.0",
|
|
"date": "2026-08-14",
|
|
"summary": "Geheimnisse raus aus der Kommandozeile, Packager sperrt statt zu warnen, ILicensePrompt lebt.",
|
|
"actionRequired": true,
|
|
"changes": [
|
|
{
|
|
"area": "sdk",
|
|
"breaking": false,
|
|
"title": "Lizenzschluessel wird nicht mehr als Argument uebergeben",
|
|
"text": "Der Agent nahm --license-key nur aus argv. 'ps' zeigt das jedem Benutzer der Maschine - exakt die Begruendung, mit der UPGRADE.md §5 den Crontab-Weg verwirft. Der Agent liest jetzt DC_LICENSE_KEY, DC_DOWNLOAD_USER und DC_DOWNLOAD_PASSWORD; Umgebung vor Argument. LaunchUpdateAgent setzt die Variable auf dem eigenen Prozess, das Kind erbt sie.",
|
|
"action": "Keine Aenderung noetig, wenn ihr licenseKey an LaunchUpdateAgent uebergebt. Wer den Agenten selbst aufruft, sollte von --license-key auf DC_LICENSE_KEY umstellen."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": false,
|
|
"title": "waitTimeoutSeconds an LaunchUpdateAgent",
|
|
"text": "Der Agent kannte --wait-timeout, das SDK reichte es nicht durch - es galten fest 60 Sekunden. Laeuft die Zeit ab, bricht der Agent ab, ohne etwas zu veraendern.",
|
|
"action": "Wenn euer Herunterfahren laenger als etwa 40 Sekunden dauert: waitTimeoutSeconds heraufsetzen."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": false,
|
|
"title": "Environment.Exit(0) ist dokumentiert, nicht geaendert",
|
|
"text": "exitCurrentApp:true beendet den Prozess hart - laufende finally-Bloecke, IHostApplicationLifetime und Destruktoren kommen nicht mehr zum Zug.",
|
|
"action": "Bei offenem Zustand (Positionen, Transaktionen, ungeschriebene Puffer) exitCurrentApp:false setzen und selbst geordnet herunterfahren. Der Agent wartet ohnehin auf das Prozessende."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": false,
|
|
"title": "EnsureLicensedAsync - ILicensePrompt wird endlich benutzt",
|
|
"text": "Der Konstruktor nahm ILicensePrompt entgegen, legte es ab und rief es nie auf. Neu: EnsureLicensedAsync() nimmt den zwischengespeicherten Schluessel, fragt sonst nach, prueft, und fragt bei Ablehnung erneut. allowPrompt:false lehnt ohne Cache ab, statt im Dienst auf eine Eingabe zu warten, die nie kommt.",
|
|
"action": "Optional. Wer eine eigene Abfrage gebaut hat, kann sie behalten."
|
|
},
|
|
{
|
|
"area": "packager",
|
|
"breaking": true,
|
|
"title": "Bricht bei Zugangsdaten im Paket ab",
|
|
"text": "Zuvor nur eine Warnung - und die war ausgerechnet unterdrueckt, wenn die Datei auf der preserve-Liste stand. So geriet ein echter API-Schluessel in ein oeffentlich abrufbares Paket. Geprueft werden Dateiname (appsettings.Local.json, master.key, *.pfx, *.db, server_settings.xml) und Inhalt (gefuelltes Password=, sk-, ghp_, dc_master_, AKIA, private Schluessel). Platzhalter loesen nicht aus.",
|
|
"action": "Pruefen, was im Publish-Verzeichnis landet - haeufig eine CopyToOutputDirectory-Regel in der csproj. Notausgang: --allow-secrets."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": false,
|
|
"title": "BuildInfo.targets liegt im NuGet-Paket",
|
|
"text": "Die Anleitung empfahl einen <Import> per relativem Pfad ins Nachbar-Repository - das setzt voraus, dass beide Arbeitskopien nebeneinander liegen und in derselben Fassung stehen. Das Target liegt jetzt unter build/ im Paket, NuGet importiert es selbst.",
|
|
"action": "Den <Import Project=\"..\\Deploymentcenter.Client\\Deploymentcenter.BuildInfo.targets\" /> aus der csproj entfernen und stattdessen PackageReference auf Deploymentcenter.Client 2.5.0 setzen."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": false,
|
|
"title": "Unauthorized auch im API-Zweig",
|
|
"text": "Ein 401 wurde nur auf dem statischen Weg als Lizenzproblem erkannt; ueber die API kam er als gewoehnlicher HTTP-Fehler an.",
|
|
"action": null
|
|
},
|
|
{
|
|
"area": "tooling",
|
|
"breaking": false,
|
|
"title": "pack-and-deploy ist beziehbar, Release-Vorlage vorhanden",
|
|
"text": "Das Werkzeug wurde in der Anleitung benutzt, als laege es im PATH - beziehbar war es nirgends. Es steht jetzt unter /installer/ neben dem Agenten. Dazu eine Vorlage zum Kopieren ins eigene Projekt, die je Plattform dotnet publish und pack-and-deploy verkettet und sich das Werkzeug selbst holt.",
|
|
"action": "Empfohlen: Vorlage von /docs/release-template/ holen. Anleitung unter /docs/release.md."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"version": "2.4.0",
|
|
"date": "2026-08-13",
|
|
"summary": "Release-Ablage liegt hinter Zugangsschutz. Ohne Lizenzschluessel keine Updates mehr.",
|
|
"actionRequired": true,
|
|
"changes": [
|
|
{
|
|
"area": "server",
|
|
"breaking": true,
|
|
"title": "/releases/ verlangt Zugangsdaten",
|
|
"text": "Zuvor konnte jeder im Internet die vollstaendigen Pakete herunterladen. Zugang haben jetzt gueltige Lizenzschluessel des jeweiligen Produkts sowie die Installationskonten. Je Produkt eine eigene .htpasswd - eine gemeinsame wuerde bedeuten, dass eine Lizenz fuer Produkt A auch Produkt B oeffnet.",
|
|
"action": "PFLICHT: credentials: ReleaseCredentials.FromLicenseKey(schluessel) an CheckForUpdateAsync, und licenseKey an LaunchUpdateAgent. Ohne das bekommt die Anwendung 401 und keine Updates mehr."
|
|
},
|
|
{
|
|
"area": "server",
|
|
"breaking": true,
|
|
"title": "Benutzername ist eine Ableitung, nicht der Schluessel",
|
|
"text": "Das htpasswd-Format hasht nur die Passwortspalte. Stuende der Lizenzschluessel auch als Benutzername darin, waere die Datei eine Klartext-Kundenliste. Der Benutzername ist deshalb lic_<sha256(schluessel), 16 Hexzeichen>.",
|
|
"action": "Nur relevant, wenn ihr Basic Auth selbst baut statt ReleaseCredentials zu benutzen."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": false,
|
|
"title": "UpdateCheckResult.Unauthorized",
|
|
"text": "Trennt 'Lizenz traegt nicht mehr' von einem Netzwerkfehler. Ohne diese Unterscheidung sieht ein abgelaufener Vertrag aus wie eine Stoerung, und man sucht an der falschen Stelle.",
|
|
"action": "Empfohlen: Unauthorized abfragen und dem Benutzer als Lizenzhinweis zeigen."
|
|
},
|
|
{
|
|
"area": "server",
|
|
"breaking": false,
|
|
"title": "Reihenfolge bei Neuprodukten",
|
|
"text": "Fuer ein Produkt ohne Release existiert /releases/<slug>/ nicht und wird uebersprungen. Das Verzeichnis entsteht mit dem ersten Upload, der naechste Abgleich schuetzt es. Es gibt also kein Fenster, um ein ungeschuetztes Release zu ziehen und danach das SDK nachzuruesten.",
|
|
"action": "Der erste ausgelieferte Build eines neuen Produkts muss die Zugangsdaten schon mitbringen."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"version": "2.3.0",
|
|
"date": "2026-08-13",
|
|
"summary": "Erstinstallation ueber den Update-Agent, setup.json, Installationskonto.",
|
|
"actionRequired": false,
|
|
"changes": [
|
|
{
|
|
"area": "agent",
|
|
"breaking": false,
|
|
"title": "update-agent --action install",
|
|
"text": "Fuehrt durch Anmeldung, Auswahl aus dem Katalog, Zielverzeichnis, Installation und Einrichtung. Die Dateien kommen ueber denselben Pfad wie ein Update - mit Pruefsumme, Signatur, Staging und Rollback.",
|
|
"action": "Optional: setup.json ins Publish-Verzeichnis legen, damit die Erstinstallation die noetigen Werte abfragen kann."
|
|
},
|
|
{
|
|
"area": "agent",
|
|
"breaking": false,
|
|
"title": "setup.json beschreibt die einzurichtenden Werte",
|
|
"text": "Gefragt wird nur, was uebrig bleibt: bereits gesetzt -> detect:... -> provision -> fragen. Ziele haben ein 'location' (install, config, data, home) und optional fileWindows/fileLinux/fileMacOS. Dateien mit geheimen Werten werden auf den eigenen Benutzer beschraenkt.",
|
|
"action": "Optional. Ohne setup.json laesst sich die Anwendung installieren, aber nicht einrichten."
|
|
},
|
|
{
|
|
"area": "server",
|
|
"breaking": false,
|
|
"title": "Rolle 'installer' und Benutzerverwaltung",
|
|
"text": "Konten dieser Rolle koennen sich ausschliesslich ueber /api/setup/v1/login anmelden und Anwendungen einrichten - nicht am WebUI. Die Zugangsdaten werden auf jedem Zielsystem eingetippt; mit einem Administratorkonto verteilte man den Zugang zur gesamten Verwaltung.",
|
|
"action": "Migration 012 einspielen. Fuer Erstinstallationen ein installer-Konto anlegen."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"version": "2.2.0",
|
|
"date": "2026-08-09",
|
|
"summary": "Plattform-Dimension, signierte Releases, Update mit Rollback, geschuetzte Konfigurationsdateien.",
|
|
"actionRequired": true,
|
|
"changes": [
|
|
{
|
|
"area": "server",
|
|
"breaking": true,
|
|
"title": "Releases tragen eine Plattform",
|
|
"text": "Zuvor gab es die Dimension nicht: win-x64 und linux-x64 landeten unter derselben Version im selben Kanal und ueberschrieben sich: ein Linux-System zog sich das Windows-Paket. Ein Client, der keine Plattform mitschickt, sieht ausschliesslich Releases mit platform=any.",
|
|
"action": "PFLICHT beim Veroeffentlichen: --platform win-x64 (o. ae.) an pack-and-deploy. Clientseitig passiert es von selbst - CheckForUpdateAsync schickt ohne Angabe die Kennung des laufenden Systems."
|
|
},
|
|
{
|
|
"area": "packager",
|
|
"breaking": true,
|
|
"title": "Version wird gegen die Hauptassembly geprueft",
|
|
"text": "Weicht --version von der einkompilierten ab, bricht der Vorgang ab. Wird 1.0.1 als 1.0.2 veroeffentlicht, aktualisieren alle Clients, melden danach weiter 1.0.1, halten das Release erneut fuer neu - eine Endlosschleife ueber die gesamte Installationsbasis.",
|
|
"action": "<Version> in die Directory.Build.props, nicht in einzelne csproj-Dateien. Notausgang: --ignore-version-mismatch."
|
|
},
|
|
{
|
|
"area": "packager",
|
|
"breaking": true,
|
|
"title": "preservePatterns schuetzt Konfigurationsdateien",
|
|
"text": "Zuvor ueberschrieb jedes Update die eingerichteten Werte des Zielsystems. Ausschluss und Schutz sind zwei verschiedene Dinge: excludePatterns haelt eine Datei aus dem Paket, preservePatterns liefert sie aus, laesst am Ziel aber die vorhandene Fassung in Ruhe. Die Muster sind jetzt echte Globs - 'logs/**' traf zuvor nie zu.",
|
|
"action": "preservePatterns in die packager.config.json aufnehmen. Pruefen, ob eure Konfigurationsdateien bisher ueberschrieben wurden."
|
|
},
|
|
{
|
|
"area": "agent",
|
|
"breaking": false,
|
|
"title": "Anwenden mit Backup und Rollback",
|
|
"text": "Die Stelle war als 'Atomic Replace with Backup' kommentiert und war eine Kopierschleife. Bricht sie ab, blieb eine halb aktualisierte Installation zurueck. Jetzt: Plan, Backup, Anwenden, bei Fehler vollstaendiger Rollback. Verwaiste Dateien werden entfernt - aber nur solche aus dem Manifest der Vorversion.",
|
|
"action": null
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": true,
|
|
"title": "LaunchUpdateAgent uebergibt Neustart und Prozesskennung",
|
|
"text": "--restart wurde nie uebergeben - die Anwendung schloss sich nach 'Jetzt installieren' und blieb zu. --wait-for-pid gab es nicht: der Agent kopierte bei langsamem Herunterfahren ueber gesperrte Dateien. ResolveAgentPath() liefert den plattformrichtigen Namen; ein fest verdrahtetes update-agent.exe wird unter Linux nie gefunden.",
|
|
"action": "UpdateClient.ResolveAgentPath() statt eines festen Pfads benutzen. currentVersion: BuildInfo.Version mitgeben - fuer Installationen ohne manifest.json."
|
|
},
|
|
{
|
|
"area": "server",
|
|
"breaking": false,
|
|
"title": "Releases werden signiert",
|
|
"text": "Der SHA256 stammt aus derselben Quelle wie das Paket. Wer den Webroot kontrolliert, tauscht beide gemeinsam aus. Der Server signiert jetzt mit RSA-SHA256, der Agent prueft gegen /api/updateservice/v1/pubkey. Bewusst asymmetrisch: bei HMAC braeuchte der pruefende Agent denselben geheimen Schluessel.",
|
|
"action": "Serverseitig security.release_private_key hinterlegen. Ohne Schluessel bleiben Releases unsigniert und installierbar."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": true,
|
|
"title": "CheckForUpdateAsync hat einen Parameter mehr",
|
|
"text": "platform steht vor dem CancellationToken. Wer den Token positionell uebergeben hat, bekommt einen Uebersetzungsfehler - kein stilles Fehlverhalten.",
|
|
"action": "Benannte Argumente benutzen."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"version": "2.1.0",
|
|
"date": "2026-08-08",
|
|
"summary": "Korrekturen im .NET-SDK, app_version am Heartbeat.",
|
|
"actionRequired": true,
|
|
"changes": [
|
|
{
|
|
"area": "sdk",
|
|
"breaking": true,
|
|
"title": "BuildInfo.targets erzeugt im Namensraum des Projekts",
|
|
"text": "Die vorherige Fassung erzeugte fest in Deploymentcenter.Client.Models und war damit nicht einbindbar (CS0433).",
|
|
"action": "Ein etwaiges 'using Deploymentcenter.Client.Models' fuer BuildInfo entfernen."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": false,
|
|
"title": "API-Rueckfall der Update-Pruefung liefert vollstaendige Daten",
|
|
"text": "Fehlte die latest.json, kamen ueber die API weder Download-Adresse noch Pruefsumme, Changelog oder Kritikalitaet an - nur 'version' stimmte in beiden Formaten ueberein.",
|
|
"action": "Eigene Umgehungen koennen entfallen."
|
|
},
|
|
{
|
|
"area": "sdk",
|
|
"breaking": true,
|
|
"title": "unknown_error entfaellt, cache_ttl_hours wird ausgewertet",
|
|
"text": "Der Cache-Rueckfall greift jetzt bei jedem HTTP-Fehler, nicht nur bei bestimmten. app_version ist Parameter statt fest.",
|
|
"action": "Wer unknown_error abfaengt, prueft stattdessen IsTransient. LicenseClient.DefaultAppVersion beim Start setzen."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"version": "2.0.0",
|
|
"date": "2026-08-07",
|
|
"summary": "Sicherheitsumstellung: Veroeffentlichen braucht ein Token, Versionsvergleich nach Semver.",
|
|
"actionRequired": true,
|
|
"changes": [
|
|
{
|
|
"area": "server",
|
|
"breaking": true,
|
|
"title": "Veroeffentlichen verlangt updateservice:publish",
|
|
"text": "Zuvor voellig ungeschuetzt - jeder konnte download_url und sha256_hash eines bestehenden Releases ueberschreiben und allen Clients ein beliebiges Paket unterschieben.",
|
|
"action": "Token mit dem Recht updateservice:publish erzeugen und als DC_TOKEN hinterlegen."
|
|
},
|
|
{
|
|
"area": "server",
|
|
"breaking": true,
|
|
"title": "Versionsvergleich folgt der semantischen Ordnung",
|
|
"text": "Zuvor verglich SQL lexikografisch: 1.9.0 galt als neuer als 1.10.0, und Clients bekamen ein Downgrade als Update angeboten.",
|
|
"action": null
|
|
},
|
|
{
|
|
"area": "server",
|
|
"breaking": true,
|
|
"title": "Einheitliches Antwortformat",
|
|
"text": "Alle JSON-Endpunkte antworten mit {\"status\":\"success\",...} bzw. {\"status\":\"error\",\"error\":{\"code\":...}}. Der code ist stabil und fuer Programme gedacht, die message richtet sich an Menschen.",
|
|
"action": "Auswertung der Antworten auf das neue Format umstellen."
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|