feat: UpdateAgent Selbstaktualisierung & kopflose Installation, .NET 10 Migration, Backend- & SDK-Fixes

- .NET 10 Migration: Alle .NET-Projekte auf net10.0 umgestellt (#459)
- Client-SDK: ResolveAgentPath bereinigt und DLL-Pruefung ergaenzt (#458)
- Packager: Commit-Ermittlung aus publishDir / Assembly InformationalVersion (#456)
- BuildInfo.targets: ExitCode-Abfang und Regex-Validierung gegen Git-Fehlertexte (#363)
- UpdateService: Re-Publish erhaelt bestehende Manifeste und Werte (#457)
- Bugtracker API: on_roadmap-Filter in manage?action=list durchgereicht (#349)
- Watchdog API: project_slug-Filter in GET /status ergaenzt (#420)
- UpdateService API: no_release_for_platform mit verfuegbaren Plattformen (#421)
- Bugtracker Core: Fehler-Normalisierung gegen zersplitterte error_hashes (#419)
- UpdateAgent: Kopfloser Installationsmodus via --answers (#89)
- Installer & Agent: Automatische Selbstaktualisierung beim Start
- Tooling: AGENTS.md, CLAUDE.md und Standalone-CLI scripts/dc.py hinzugefuegt
This commit is contained in:
Deploymentcenter Bot
2026-09-02 23:43:37 +02:00
parent 8447b2d10a
commit 087f4392a9
26 changed files with 1582 additions and 67 deletions
+20 -2
View File
@@ -2382,7 +2382,8 @@ final class BugRepo
/**
* Entfernt Rauschen, das denselben Fehler sonst als neu erscheinen liesse:
* Zeilennummern, Speicheradressen, Zeitstempel, GUIDs, Mehrfach-Leerzeichen.
* Zeilennummern, Speicheradressen, Zeitstempel, GUIDs, Dauern/Laufzeiten,
* Groessenangaben, IDs, IP-Adressen und Mehrfach-Leerzeichen (#419).
*/
private static function normalizeForHash(string $value): string
{
@@ -2392,6 +2393,14 @@ final class BugRepo
'/0x[0-9a-f]+/' => '0xADDR',
'/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/' => 'GUID',
'/\d{4}-\d{2}-\d{2}[t ]\d{2}:\d{2}:\d{2}/' => 'TIMESTAMP',
'/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?\b/' => 'IP:PORT',
// Dauern und Messwerte (z. B. ("2,196"ms), 45ms, 123.45ms, 30.5s, 5000 ms)
// Verhindert das Zersplittern von EF-Core- und DB-Meldungen (#419)
'/\(?["\']?\d+([.,]\d+)?["\']?\s*(ms|s|sec|seconds|sekunden|min|minutes|minuten|µs|us|ns)\b\)?/' => 'DURATION',
// Datenmengen (z. B. 4,096 bytes, 500 kb, 10 mb)
'/\d+([.,]\d+)?\s*(b|bytes|kb|kib|mb|mib|gb|gib)\b/' => 'SIZE',
// Werte in Anfuehrungszeichen, die mindestens eine Ziffer
// enthalten, sind praktisch immer konkrete Datensatzwerte und
@@ -2404,7 +2413,16 @@ final class BugRepo
'/"[^"]*\d[^"]*"/' => '"VALUE"',
'/:line \d+/' => ':line N',
'/\b\d{3,}\b/' => 'N',
// Formatierte Zahlen mit Tausender- oder Dezimaltrennern (z. B. 2,196 oder 12.34)
'/\b\d+([.,]\d+)+\b/' => 'N',
// Zahlen nach Zuweisungen (=, :, #)
'/(?<=[=:#])\s*\d+\b/' => ' N',
// Eigenstaendige mehrstellige Zahlen
'/\b\d{2,}\b/' => 'N',
'/\s+/' => ' ',
];
+56 -7
View File
@@ -145,6 +145,30 @@ final class UpdateManager
return Version::highest($this->releasesFor($productSlug, $channel, $platform));
}
/**
* Liefert alle Plattformen, fuer die Releases fuer dieses Produkt existieren.
*
* @return list<string>
*/
public function getAvailablePlatforms(string $productSlug, string $channel = 'prod'): array
{
$stmt = $this->db->prepare('
SELECT DISTINCT platform FROM updateservice_releases
WHERE product_slug = :slug AND channel = :channel
ORDER BY platform ASC
');
$stmt->execute([':slug' => $productSlug, ':channel' => $channel]);
$rows = $stmt->fetchAll() ?: [];
$platforms = [];
foreach ($rows as $r) {
$p = (string)($r['platform'] ?? '');
if ($p !== '') {
$platforms[] = $p;
}
}
return $platforms;
}
/**
* Legt ein Release an oder aktualisiert es.
*
@@ -169,6 +193,31 @@ final class UpdateManager
$existing = $this->findRelease($productSlug, $version, $channel, $platform);
// Bestehende Werte nicht mit null oder leer ueberschreiben, wenn sie im Aufruf weggelassen wurden (#457)
if ($existing !== null) {
if ($manifestJson === null && !empty($existing['manifest_json'])) {
$manifestJson = (string)$existing['manifest_json'];
}
if (($manifestSignature === null || $manifestSignature === '') && !empty($existing['manifest_signature'])) {
$manifestSignature = (string)$existing['manifest_signature'];
}
if ($releaseNotes === null && !empty($existing['release_notes'])) {
$releaseNotes = (string)$existing['release_notes'];
}
if (($sha256Hash === null || $sha256Hash === '') && !empty($existing['sha256_hash'])) {
$sha256Hash = (string)$existing['sha256_hash'];
}
if (($gitCommit === null || $gitCommit === '') && !empty($existing['git_commit'])) {
$gitCommit = (string)$existing['git_commit'];
}
if ($sizeBytes <= 0 && !empty($existing['size_bytes'])) {
$sizeBytes = (int)$existing['size_bytes'];
}
if (($downloadUrl === '') && !empty($existing['download_url'])) {
$downloadUrl = (string)$existing['download_url'];
}
}
$stmt = $this->db->prepare('
INSERT INTO updateservice_releases (
product_slug, version, channel, platform, release_notes, download_url,
@@ -179,13 +228,13 @@ final class UpdateManager
:hash, :git, :size, :manifest, :signature,
:critical
) ON DUPLICATE KEY UPDATE
release_notes = VALUES(release_notes),
download_url = VALUES(download_url),
sha256_hash = VALUES(sha256_hash),
git_commit = VALUES(git_commit),
size_bytes = VALUES(size_bytes),
manifest_json = VALUES(manifest_json),
manifest_signature = VALUES(manifest_signature),
release_notes = COALESCE(VALUES(release_notes), release_notes),
download_url = CASE WHEN VALUES(download_url) != "" THEN VALUES(download_url) ELSE download_url END,
sha256_hash = COALESCE(VALUES(sha256_hash), sha256_hash),
git_commit = COALESCE(VALUES(git_commit), git_commit),
size_bytes = CASE WHEN VALUES(size_bytes) > 0 THEN VALUES(size_bytes) ELSE size_bytes END,
manifest_json = COALESCE(VALUES(manifest_json), manifest_json),
manifest_signature = COALESCE(VALUES(manifest_signature), manifest_signature),
is_critical = VALUES(is_critical)
');
+11 -1
View File
@@ -52,8 +52,18 @@ final class MonitorRepo
}
/** @return list<array<string,mixed>> */
public function getAllMonitors(): array
public function getAllMonitors(?string $projectSlug = null): array
{
if ($projectSlug !== null && $projectSlug !== '' && $projectSlug !== 'all') {
$stmt = $this->db->prepare('
SELECT * FROM watchdog_monitors
WHERE project_slug = :project
ORDER BY COALESCE(group_key, "zzz") ASC, source ASC
');
$stmt->execute([':project' => $projectSlug]);
return $stmt->fetchAll() ?: [];
}
$stmt = $this->db->query('
SELECT * FROM watchdog_monitors
ORDER BY COALESCE(group_key, "zzz") ASC, source ASC