Bisher musste ein Agent, der eine Anbindung aktualisiert, die gesamte Historie lesen - oder er las gar nichts und uebersah eine brechende Aenderung. Beides schlecht. - public/docs/changelog.json ist die einzige Quelle. Je Fassung eine Zusammenfassung, je Aenderung Bereich, ein "breaking"-Kennzeichen und vor allem ein Feld "action" mit dem, was konkret zu tun ist. Steht dort null, ist nichts zu tun - das ist die haeufigste und nuetzlichste Antwort. - GET /api/updateservice/v1/changelog?since=2.2.0 liefert nur die neueren Fassungen, dazu die Anzahl der Punkte mit Handlungsbedarf und der brechenden Aenderungen. count:0 heisst "du bist auf Stand" - dann muss gar nichts gelesen werden. Optional nach Bereich filterbar (?area=packager). - /docs/changelog.php rendert dieselbe Datei fuer Menschen, mit Eingabefeld fuer die eigene Fassung. Bewusst dieselbe Quelle: zwei Fassungen zu pflegen hiesse, sie auseinanderlaufen zu lassen. - DeploymentcenterSdk.Version im SDK ist der Bezugspunkt. Damit muss die Fassung nicht abgetippt werden. - AGENT_PROMPT_TEMPLATE.md verpflichtet dazu, sie in der AGENTS.md des Projekts festzuhalten und vor jeder Aenderung an der Anbindung den Unterschied abzufragen. Auch in der Kurzfassung fuer knappe Prompt-Budgets. Die Historie ist rueckwirkend bis 2.0.0 gefuellt: 6 Fassungen, 25 Punkte mit Handlungsbedarf, 13 brechende Aenderungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
254 lines
16 KiB
JSON
254 lines
16 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.5.0",
|
|
|
|
"versions": [
|
|
{
|
|
"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."
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|