From b0ca082b525043417f3dfc1810ef8e57a327ae2b Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Tue, 7 Jul 2026 13:42:39 +0200 Subject: [PATCH] =?UTF-8?q?sync=5Fone=20:=20hash=20uniquement=20l'entr?= =?UTF-8?q?=C3=A9e=20cliqu=C3=A9e,=20pas=20toutes=20celles=20du=20m=C3=AAm?= =?UTF-8?q?e=20num=C3=A9ro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug : sur un manifest avec deux entrées partageant le même numéro de version (channels multiples — ex proserve-firefighter-1.5.4.32.zip ET proserve-full- 1.5.4.32.zip, deux builds distincts pour firefighter vs full), cliquer le bouton « 🔁 Hash » d'une ligne déclenchait le hash des DEUX ZIPs dans la même requête HTTP. Résultat 2 × 14 Go = 28 Go dans une seule requête, ce qui dépasse le timeout front d'OVH mutualisé (hard-limit invisible côté PHP même avec set_time_limit(0)) → 500 Internal Server Error. C'est pour ça que « des hashs de la même taille passaient sans problème avant » : c'est le cas 28 Go des DEUX ZIPs en une requête qui est nouveau (depuis l'introduction des channels), pas la taille du single 14 Go qui passait déjà. Fix : nouveau paramètre $onlyEntryId sur SignManifest::run(). Prend le pas sur $onlyVersion. L'admin action sync_one passe l'entryId (unique par ligne, généré par generate_entry_id()) au lieu du numéro de version. Une ligne cliquée = un ZIP hashé, point. Le bouton « 🔁 Sync (all versions) » (action sync_versions) continue de tout hasher — c'est ce que l'opérateur demande explicitement. Rétro-compat : si $onlyEntryId est null (CLI, cron, anciens callers), $onlyVersion filtre comme avant. Co-Authored-By: Claude Opus 4.7 (1M context) --- server/admin/versions.php | 10 +++++----- server/tools/SignManifest.php | 32 +++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/server/admin/versions.php b/server/admin/versions.php index 33c24a1..44fef6c 100644 --- a/server/admin/versions.php +++ b/server/admin/versions.php @@ -671,11 +671,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { require_once "$root/tools/SignManifest.php"; $signer = new \PSLauncher\Tools\SignManifest($root); $force = !empty($_POST['force']); - // SignManifest::run filtre par numéro de version ; en cas d'entries - // de même version sur des channels différents, le hash sera recalculé - // pour toutes celles qui matchent (chacune pointe sur son propre ZIP, - // donc le résultat est correct, juste un peu plus de boulot). - $result = $signer->run('versions', $force, $version); + // Filtre par entryId (pas par numéro de version). Sinon, quand deux + // entrées partagent le même numéro (channels firefighter vs full sur + // 1.5.4.32 p.ex.), les DEUX ZIPs sont hashés dans la même requête HTTP + // → 2 × 14 Go = risque de dépasser le timeout front OVH sur mutualisé. + $result = $signer->run('versions', $force, null, $entryId); $forceLabel = $force ? ' [FORCE]' : ''; $message = "Hash de v{$version}{$forceLabel} :\n" . implode("\n", $result['log']); if (!$result['ok']) $messageType = 'error'; diff --git a/server/tools/SignManifest.php b/server/tools/SignManifest.php index 2c70af4..87eff39 100644 --- a/server/tools/SignManifest.php +++ b/server/tools/SignManifest.php @@ -139,10 +139,19 @@ final class SignManifest * @param bool $force Si true, ignore le cache et recalcule tous les hashs * @param ?string $onlyVersion Si non null, ne touche QUE cette version dans la section * versions[] (les autres restent inchangées). Le scope 'launcher' - * est ignoré dans ce cas. + * est ignoré dans ce cas. ATTENTION : si plusieurs entrées + * partagent le même numéro de version (channels firefighter + * vs full sur 1.5.4.32 p.ex.), TOUTES sont hashées — d'où + * $onlyEntryId ci-dessous pour cibler une seule ligne. + * @param ?string $onlyEntryId Si non null, ne touche QUE l'entrée avec cet id (généré par + * generate_entry_id() au backoffice). Prend le pas sur + * $onlyVersion. Utilisé par le bouton « 🔁 Hash » d'une + * ligne isolée pour éviter de re-hasher les autres channels + * qui partagent le même numéro de version (2 × 14 Go dans une + * requête HTTP → risque de timeout front OVH). * @return array{ok:bool, log:string[]} */ - public function run(string $scope = 'all', bool $force = false, ?string $onlyVersion = null): array + public function run(string $scope = 'all', bool $force = false, ?string $onlyVersion = null, ?string $onlyEntryId = null): array { // Sur OVH mutualisé, un SHA-256 d'un ZIP de 14 Go peut prendre 5-10 min via // le SAN partagé. Le max_execution_time par défaut (30-60s) tue le process @@ -166,7 +175,8 @@ final class SignManifest $ts = date('Y-m-d H:i:s'); @file_put_contents($errorLogPath, "[{$ts}] --- SignManifest::run(scope={$scope}, force=" . ($force?'1':'0') - . ", onlyVersion=" . ($onlyVersion ?? 'null') . ") ---\n", + . ", onlyVersion=" . ($onlyVersion ?? 'null') + . ", onlyEntryId=" . ($onlyEntryId ?? 'null') . ") ---\n", FILE_APPEND); // Fatal errors → capturés par un shutdown handler. Sinon Apache renvoie // juste 500 sans qu'on sache ce qui a claqué. @@ -193,9 +203,9 @@ final class SignManifest $doVersions = ($scope === 'all' || $scope === 'versions'); $doLauncher = ($scope === 'all' || $scope === 'launcher'); - if ($onlyVersion !== null) { - // Mode "hash une seule version" : on ne touche pas au launcher, - // et on ne hash que la version demandée dans la section versions[]. + if ($onlyVersion !== null || $onlyEntryId !== null) { + // Mode "hash une seule entrée / version" : on ne touche pas au launcher, + // et on ne hash que ce qui correspond au filtre demandé dans versions[]. $doLauncher = false; $doVersions = true; } @@ -207,7 +217,15 @@ final class SignManifest $hashedVersions = []; if ($doVersions) foreach ($manifest['versions'] as &$v) { $version = $v['version'] ?? '?'; - if ($onlyVersion !== null && $version !== $onlyVersion) { + $entryId = (string)($v['id'] ?? ''); + // Filtre par id d'entrée prioritaire sur filtre par version (cas + // channels multiples partageant un numéro de version identique — + // ex : proserve-firefighter-1.5.4.32 vs proserve-full-1.5.4.32). + // Sinon, filtre par numéro de version (rétro-compat). + if ($onlyEntryId !== null && $entryId !== $onlyEntryId) { + continue; + } + if ($onlyEntryId === null && $onlyVersion !== null && $version !== $onlyVersion) { continue; // skip silently les autres versions } $url = $v['download']['url'] ?? '';