1, 'product' => 'PROSERVE_UE', 'latest' => null, 'versions' => []]; return json_decode(file_get_contents($path), true) ?? []; } function saveManifest(string $path, array $manifest): void { // Re-tri par SemVer descendant usort($manifest['versions'], fn($a, $b) => version_compare($b['version'], $a['version'])); file_put_contents( $path, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n" ); } if ($_SERVER['REQUEST_METHOD'] === 'POST') { Auth::checkCsrf(); $action = $_POST['action'] ?? ''; $manifest = loadManifest($manifestPath); try { if ($action === 'add') { $version = trim($_POST['version'] ?? ''); $minLicDate = trim($_POST['min_license_date'] ?? ''); $releasedAt = trim($_POST['released_at'] ?? ''); $notes = $_POST['notes'] ?? ''; $available = isset($_POST['available']); if (!preg_match('/^\d+\.\d+\.\d+$/', $version)) { throw new Exception('Numéro de version invalide (format X.Y.Z attendu).'); } if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $minLicDate)) { throw new Exception('Date min_license_date invalide.'); } foreach ($manifest['versions'] as $v) { if ($v['version'] === $version) throw new Exception("v{$version} existe déjà dans le manifest."); } $releasedAtIso = $releasedAt !== '' ? (new DateTime($releasedAt, new DateTimeZone('UTC')))->format('Y-m-d\TH:i:s\Z') : (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format('Y-m-d\TH:i:s\Z'); $base = $config['base_url'] ?? 'https://asterionvr.com/PS_Launcher'; $manifest['versions'][] = [ 'version' => $version, 'releasedAt' => $releasedAtIso, 'executable' => 'PROSERVE_UE_5_5.exe', 'installFolderTemplate' => 'Proserve v{version}', 'download' => [ 'url' => "{$base}/builds/proserve-{$version}.zip", 'sizeBytes' => 0, 'sha256' => 'REPLACE_AFTER_BUILD', ], 'releaseNotesUrl' => "{$base}/api/releasenotes/{$version}", 'minLicenseDate' => $minLicDate, 'availableForDownload' => $available, ]; saveManifest($manifestPath, $manifest); if ($notes !== '') { if (!is_dir($notesDir)) mkdir($notesDir, 0755, true); file_put_contents("$notesDir/{$version}.md", $notes); } $message = "v{$version} ajoutée. Upload le ZIP en SFTP dans builds/, puis clique « Sync (sign-manifest) »."; } elseif ($action === 'edit_notes') { $version = $_POST['version'] ?? ''; $notes = $_POST['notes'] ?? ''; if (!preg_match('/^\d+\.\d+\.\d+$/', $version)) throw new Exception('Version invalide.'); if (!is_dir($notesDir)) mkdir($notesDir, 0755, true); file_put_contents("$notesDir/{$version}.md", $notes); $message = "Release notes de v{$version} mises à jour."; } elseif ($action === 'edit_meta') { $version = $_POST['version'] ?? ''; $minLicDate = trim($_POST['min_license_date'] ?? ''); $releasedAt = trim($_POST['released_at'] ?? ''); if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $minLicDate)) { throw new Exception('min_license_date invalide.'); } foreach ($manifest['versions'] as &$v) { if ($v['version'] === $version) { $v['minLicenseDate'] = $minLicDate; if ($releasedAt !== '') { $v['releasedAt'] = (new DateTime($releasedAt, new DateTimeZone('UTC')))->format('Y-m-d\TH:i:s\Z'); } break; } } unset($v); saveManifest($manifestPath, $manifest); $message = "Méta de v{$version} mises à jour."; } elseif ($action === 'toggle_available') { $version = $_POST['version'] ?? ''; foreach ($manifest['versions'] as &$v) { if ($v['version'] === $version) { $v['availableForDownload'] = !($v['availableForDownload'] ?? true); break; } } unset($v); saveManifest($manifestPath, $manifest); $message = "Disponibilité de v{$version} mise à jour."; } elseif ($action === 'delete') { $version = $_POST['version'] ?? ''; $manifest['versions'] = array_values(array_filter( $manifest['versions'], fn($v) => $v['version'] !== $version )); saveManifest($manifestPath, $manifest); $message = "v{$version} retirée du manifest. Le ZIP reste dans builds/ (à supprimer en SFTP si voulu)."; } elseif ($action === 'set_launcher') { $lver = trim($_POST['launcher_version'] ?? ''); $lmin = trim($_POST['launcher_min_required'] ?? ''); if (!preg_match('/^\d+\.\d+\.\d+$/', $lver)) { throw new Exception('Numéro de version launcher invalide (X.Y.Z attendu).'); } if ($lmin !== '' && !preg_match('/^\d+\.\d+\.\d+$/', $lmin)) { throw new Exception('Numéro de version "minRequired" invalide.'); } $base = $config['base_url'] ?? 'https://asterionvr.com/PS_Launcher'; $manifest['launcher'] = [ 'version' => $lver, 'minRequired' => $lmin !== '' ? $lmin : '0.5.0', 'download' => [ 'url' => "{$base}/builds/launcher/PSLauncher-{$lver}.exe", 'sizeBytes' => 0, 'sha256' => 'REPLACE_AFTER_BUILD', ], 'releaseNotesUrl' => null, ]; saveManifest($manifestPath, $manifest); $message = "Section launcher mise à jour (v{$lver}). Upload PSLauncher-{$lver}.exe dans builds/launcher/ puis clique « Sync »."; } elseif ($action === 'remove_launcher') { unset($manifest['launcher']); saveManifest($manifestPath, $manifest); $message = "Section launcher retirée du manifest (auto-update désactivé)."; } elseif ($action === 'sync') { // Appel direct à la classe — pas d'exec(), fonctionne même quand la fonction // est désactivée par le mutualisé OVH. require_once "$root/tools/SignManifest.php"; $signer = new \PSLauncher\Tools\SignManifest($root); $result = $signer->run(); $message = "Sortie du script :\n" . implode("\n", $result['log']); if (!$result['ok']) $messageType = 'error'; } } catch (Exception $e) { $message = $e->getMessage(); $messageType = 'error'; } } $manifest = loadManifest($manifestPath); $zips = is_dir($buildsDir) ? array_map('basename', glob("$buildsDir/*.zip") ?: []) : []; $zipSizes = []; foreach ($zips as $z) $zipSizes[$z] = filesize("$buildsDir/$z"); // Pour l'édition : pré-charge les release notes existantes $existingNotes = []; foreach ($manifest['versions'] ?? [] as $v) { $f = "$notesDir/{$v['version']}.md"; $existingNotes[$v['version']] = is_file($f) ? file_get_contents($f) : ''; } Layout::header('Versions', 'versions'); ?>
www/PS_Launcher/builds/ en respectant le nom proserve-{version}.zip.sizeBytes, bumper latest, et signer le manifest avec Ed25519.
Version annoncée : v= htmlspecialchars($launcher['version']) ?>
• minRequired : = htmlspecialchars($launcher['minRequired'] ?? '—') ?>
• Exe : = $launcherZipPresent
? "présent"
: "absent" ?>
• Hash : = $launcherHashed
? "OK"
: "à calculer" ?>
URL attendue de l'exe : = htmlspecialchars($launcher['download']['url']) ?>
Section launcher absente du manifest — l'auto-update est désactivé. Configure-la ci-dessous pour l'activer.
0.6.0).PSLauncher-X.Y.Z.exe via SFTP dans www/PS_Launcher/builds/launcher/.= htmlspecialchars($f) ?> — = Layout::formatBytes(filesize("$buildsDir/launcher/$f")) ?>| Version | Release | min_license | ZIP | Hash | Visible | Actions |
|---|---|---|---|---|---|---|
| v= htmlspecialchars($v['version']) ?> | = htmlspecialchars(substr($v['releasedAt'] ?? '', 0, 10)) ?> | = htmlspecialchars($v['minLicenseDate'] ?? '—') ?> |
présent
= Layout::formatBytes($zipSizes[$zipBase] ?? 0) ?>
absent
attendu :
= htmlspecialchars($zipBase) ?> |
= substr($v['download']['sha256'], 0, 12) ?>…
à calculer
|
|
MétaNotes |
| Aucune version dans le manifest. | ||||||
Aucun ZIP. Upload tes fichiers via SFTP dans www/PS_Launcher/builds/, puis clique « Sync » ci-dessus.
| Fichier | Taille | Référencé ? |
|---|---|---|
= htmlspecialchars($z) ?> |
= Layout::formatBytes($zipSizes[$z]) ?> | = $referenced ? "oui" : "orphelin" ?> |