v0.29.7 — SteamVR merge + résilience DL (404 retry, force fresh, SHA purge)
SteamVR + Vive Business Streaming settings merge :
- Nouveau ISteamVrSettingsDeployer : si _steamvr/steamvr.vrsettings est
dans le ZIP, deep-merge récursif des blocs racine dans le fichier user
(clé absente → ajout, deux objets → recurse, sinon → replace ; les
sous-clés cible non touchées sont préservées — crucial pour "trackers")
- Auto-localisation via registre Steam (HKCU/HKLM) + fallback Program Files
- Phase pré-check (CheckMergeNeededAsync) : skip silencieux si la cible
est déjà à jour (deep equality) → pas de kill SteamVR/VBS inutile, pas
de popup à l'opérateur. Si changements nécessaires → popup OK/Annuler
avec nombre de blocs qui changeront + liste des process à fermer.
- Liste de kill construite depuis les health checks de type "Process"
(l'opérateur connaît déjà sa stack VR) + guardians (Vive Business
Streaming en tête car il relance SteamVR auto)
- Settings UI : section dédiée avec opt-in, override path, liste process
éditable + affichage du path canonique attendu
Résilience téléchargements :
- 404 mid-DL traité comme 403/410 (URL refresh trigger) au niveau segment :
on extrait le nouveau filename via /download-url server-side, retry transparent
- Auto-retry install une fois après refresh manifest sur 404 : l'opérateur
ne voit rien si la cause était un manifest local stale
- Si retry échoue aussi en 404 → message ciblé "manifest serveur stale"
(problème côté serveur, pas client)
- Sur SHA-256 mismatch : auto-purge du cache LAN local (.zip + .sha256)
+ message d'erreur dédié avec source du DL (peer ou OVH) + nouveau menu
"↻ Forcer re-téléchargement" pour purger état + cache manuellement
- Détection client-side de l'incohérence "signed URL filename != manifest
URL filename" avant DL : abort immédiat plutôt que 14 Go pour rien
- IZipCacheStore.InvalidateAsync : nouvelle API pour purger un cache par
version (utilisée par SHA mismatch handler + Force Fresh menu)
Bug serveur (DownloadUrl.php) :
- L'endpoint construisait l'URL signée avec un template hardcodé
`/builds/proserve-{version}.zip`, ignorant complètement
download.url du manifest. Conséquence : un opérateur qui rename
son ZIP pour buster le cache CDN OVH (ex. proserve-full-1.5.4.zip)
voyait toutes ses releases retournent du 404 silencieux côté client.
- Fix : on lit basename(parse_url(entry.download.url).path), whitelist
sur le filename, vérif is_file() avant de signer, erreur 500 explicite
si manifest et filesystem désynchros.
Strings (5 langues) :
- ~20 nouvelles : SteamVR popups + status, SHA mismatch dialog + source
labels, force fresh confirm + menu, manifest stale + auto-retry status
Bumps : 0.29.6 (déployé hors-commit pendant la session) → 0.29.7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,46 @@ final class DownloadUrl
|
||||
]);
|
||||
}
|
||||
|
||||
// Extrait le NOM DE FICHIER depuis l'URL du manifest (= source de vérité).
|
||||
// Avant : on utilisait un template hardcodé `proserve-{version}.zip`,
|
||||
// ce qui ignorait toute personnalisation du nom (ex. opérateur qui rename
|
||||
// en `proserve-full-1.5.4.zip` pour buster un cache CDN). Conséquence :
|
||||
// l'endpoint retournait une URL signée pointant vers un fichier inexistant
|
||||
// → 404 systématique côté client sans aucune indication serveur.
|
||||
// Maintenant : on lit `download.url` du manifest, on extrait le filename
|
||||
// via parse_url + basename, on vérifie qu'il existe physiquement dans
|
||||
// /builds/, et seulement après on signe.
|
||||
$manifestUrl = $entry['download']['url'] ?? '';
|
||||
if ($manifestUrl === '') {
|
||||
Response::error('manifest_incomplete',
|
||||
"L'entrée manifest pour v{$version} est sans download.url", 500);
|
||||
}
|
||||
$parsedUrl = parse_url($manifestUrl);
|
||||
$urlPath = $parsedUrl['path'] ?? '';
|
||||
$filename = basename($urlPath);
|
||||
// Whitelist défensive sur le filename : caractères safe + suffixe .zip.
|
||||
// Évite path traversal et autres injections via un manifest corrompu.
|
||||
if ($filename === '' || !preg_match('/^[a-zA-Z0-9._-]+\.zip$/', $filename)) {
|
||||
Response::error('manifest_invalid_filename',
|
||||
"Nom de fichier invalide extrait du manifest pour v{$version} : '{$filename}'", 500);
|
||||
}
|
||||
// Vérifie que le fichier existe physiquement avant de signer une URL morte.
|
||||
// Cas concret de bug remonté côté client : l'opérateur rename le ZIP dans
|
||||
// /builds/ mais oublie de mettre à jour versions.json (ou inverse). Au
|
||||
// lieu d'envoyer le client en 404 silencieux, on retourne une erreur
|
||||
// serveur claire qui apparaît dans les logs PHP + client.
|
||||
$buildsDir = dirname(__DIR__, 2) . '/builds';
|
||||
$physicalPath = $buildsDir . '/' . $filename;
|
||||
if (!is_file($physicalPath)) {
|
||||
Response::error('file_missing',
|
||||
"Le fichier ZIP « {$filename} » référencé par le manifest pour v{$version} est absent du dossier /builds/. Vérifie que le manifest et le filesystem sont synchros.",
|
||||
500,
|
||||
[
|
||||
'manifestUrl' => $manifestUrl,
|
||||
'expectedPath' => $physicalPath,
|
||||
]);
|
||||
}
|
||||
|
||||
// Génère l'URL HMAC-signée
|
||||
// TTL : 6 h. Compromis entre :
|
||||
// - sécurité (limite la fenêtre de replay si une URL fuit)
|
||||
@@ -102,7 +142,7 @@ final class DownloadUrl
|
||||
// Pour une connexion plus lente, le client sait auto-refresher l'URL
|
||||
// pendant le DL (cf DownloadManager → 403 retry avec nouvelle URL).
|
||||
$baseUrl = rtrim($config['base_url'], '/');
|
||||
$relPath = '/builds/proserve-' . $version . '.zip';
|
||||
$relPath = '/builds/' . $filename;
|
||||
$exp = time() + 21600; // 6 h
|
||||
$secret = $config['hmac_secret'] ?? '';
|
||||
if ($secret === '') {
|
||||
|
||||
Reference in New Issue
Block a user