diff --git a/server/tools/SignManifest.php b/server/tools/SignManifest.php index 65f9f4b..2c70af4 100644 --- a/server/tools/SignManifest.php +++ b/server/tools/SignManifest.php @@ -154,6 +154,32 @@ final class SignManifest @set_time_limit(0); @ignore_user_abort(true); + // Capture toutes les erreurs PHP (warnings + fatals + exceptions non-catchées) + // dans un fichier accessible via SFTP, à côté du manifest. Sur OVH mutualisé + // les logs Apache ne sont accessibles que via le manager web — pas pratique + // pour un diagnostic rapide de 500. Ce fichier permet à l'opérateur de le + // grep post-clic sans passer par le manager. + $errorLogPath = dirname($this->manifestPath) . '/.signmanifest-error.log'; + @ini_set('log_errors', '1'); + @ini_set('error_log', $errorLogPath); + @error_reporting(E_ALL); + $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", + FILE_APPEND); + // Fatal errors → capturés par un shutdown handler. Sinon Apache renvoie + // juste 500 sans qu'on sache ce qui a claqué. + register_shutdown_function(function () use ($errorLogPath) { + $err = error_get_last(); + if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR], true)) { + @file_put_contents($errorLogPath, + "[" . date('Y-m-d H:i:s') . "] FATAL " . $err['type'] + . " : {$err['message']} @ {$err['file']}:{$err['line']}\n", + FILE_APPEND); + } + }); + if (!is_file($this->manifestPath)) { $this->out("Manifest introuvable : {$this->manifestPath}"); return ['ok' => false, 'log' => $this->log]; @@ -242,7 +268,26 @@ final class SignManifest } } - $r = $this->getOrComputeSha256($zip, $cache, $force); + // Log préalable au hash — utile pour identifier QUEL ZIP a fait + // planter le process si on retombe sur le 500. Sans ça, l'error log + // dit juste "PHP Fatal…" sans savoir si c'est le 1er ou le Nième ZIP. + @file_put_contents($errorLogPath, + "[" . date('Y-m-d H:i:s') . "] START hash $version → $zip ($size octets)\n", + FILE_APPEND); + try { + $r = $this->getOrComputeSha256($zip, $cache, $force); + } catch (\Throwable $e) { + @file_put_contents($errorLogPath, + "[" . date('Y-m-d H:i:s') . "] EXCEPTION during hash of $version : " + . get_class($e) . " : {$e->getMessage()} @ {$e->getFile()}:{$e->getLine()}\n" + . $e->getTraceAsString() . "\n", + FILE_APPEND); + $this->out(" [ERROR] $version : hash failed — " . $e->getMessage()); + continue; + } + @file_put_contents($errorLogPath, + "[" . date('Y-m-d H:i:s') . "] DONE hash $version → sha256={$r['sha256']} ({$r['durationMs']} ms, fromCache=" . ($r['fromCache']?'1':'0') . ")\n", + FILE_APPEND); $cacheChanged = true; $note = $r['fromCache'] ? '(cache)' : "(calculé en {$r['durationMs']} ms)"; $this->out(" [hash] $version : " . basename($zip) . " ($size octets) sha256={$r['sha256']} $note");