diff --git a/server/tools/SignManifest.php b/server/tools/SignManifest.php index b1237d0..65f9f4b 100644 --- a/server/tools/SignManifest.php +++ b/server/tools/SignManifest.php @@ -83,7 +83,37 @@ final class SignManifest } $start = microtime(true); - $sha = hash_file('sha256', $path); + // Streamed hash en chunks 16 Mo. Deux bénéfices vs hash_file() atomique : + // 1. On peut envoyer un heartbeat (flush()) entre chunks pour que le + // proxy Apache/OVH ne timeout pas la requête pendant les ~5-10 min + // que prend un SHA-256 sur un ZIP de 14 Go via SAN mutualisé. + // 2. On peut relever set_time_limit() à chaque chunk (fenêtre glissante) + // au lieu de faire un unique set_time_limit(0) à la caller. + // Mémoire : hash_update_stream() ne buffere pas — c'est du streaming pur. + $fp = @fopen($path, 'rb'); + if ($fp === false) { + return ['sha256' => '', 'fromCache' => false, 'durationMs' => 0]; + } + try { + $ctx = hash_init('sha256'); + // 16 Mo = compromis entre nombre d'appels PHP et pression CPU par read() + $chunkBytes = 16 * 1024 * 1024; + while (!feof($fp)) { + hash_update_stream($ctx, $fp, $chunkBytes); + // Fenêtre glissante : autorise ~5 min de plus avant que PHP ne + // timeout. Sur un fichier de 14 Go / chunks 16 Mo = ~900 itérations, + // donc si un chunk prend >5 min c'est vraiment que le disque est HS. + @set_time_limit(300); + // Heartbeat côté front — évite Apache RequestTimeout / OVH proxy + // timeout sur les grosses requêtes. Silencieux si output buffering + // est actif (pas fatal). + @ob_flush(); + @flush(); + } + $sha = hash_final($ctx); + } finally { + fclose($fp); + } $duration = (int)((microtime(true) - $start) * 1000); $cache[$key] = ['size' => $size, 'mtime' => $mtime, 'sha256' => $sha]; @@ -114,6 +144,16 @@ final class SignManifest */ public function run(string $scope = 'all', bool $force = false, ?string $onlyVersion = 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 + // → Apache retourne 500 Internal Server Error avec le boilerplate + // postmaster@… — c'est le mode d'échec principal du bouton « Hash » au + // backoffice. On désactive la limite ici (couvre AUSSI les callers CLI et + // cron, pas seulement l'admin web). ignore_user_abort évite qu'un refresh + // ou une fermeture d'onglet côté opérateur interrompe un hash en cours. + @set_time_limit(0); + @ignore_user_abort(true); + if (!is_file($this->manifestPath)) { $this->out("Manifest introuvable : {$this->manifestPath}"); return ['ok' => false, 'log' => $this->log];