diff --git a/server/admin/launcher.php b/server/admin/launcher.php
new file mode 100644
index 0000000..e3e9f85
--- /dev/null
+++ b/server/admin/launcher.php
@@ -0,0 +1,189 @@
+ 1, 'versions' => []];
+ return json_decode(file_get_contents($path), true) ?? [];
+}
+
+function saveManifest(string $path, array $manifest): void
+{
+ 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 === '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 « Hasher le launcher + signer ».";
+ }
+ elseif ($action === 'remove_launcher') {
+ unset($manifest['launcher']);
+ saveManifest($manifestPath, $manifest);
+ $message = "Section launcher retirée du manifest (auto-update désactivé).";
+ }
+ elseif ($action === 'sync_launcher') {
+ require_once "$root/tools/SignManifest.php";
+ $signer = new \PSLauncher\Tools\SignManifest($root);
+ $result = $signer->run('launcher');
+ $message = "Sortie du script (scope: launcher) :\n" . implode("\n", $result['log']);
+ if (!$result['ok']) $messageType = 'error';
+ }
+ } catch (Exception $e) {
+ $message = $e->getMessage();
+ $messageType = 'error';
+ }
+}
+
+$manifest = loadManifest($manifestPath);
+$launcher = $manifest['launcher'] ?? null;
+$launcherFiles = is_dir("$buildsDir/launcher")
+ ? array_map('basename', glob("$buildsDir/launcher/*.exe") ?: [])
+ : [];
+
+$launcherZipPresent = false;
+$launcherHashed = false;
+if ($launcher) {
+ $expectedFile = basename(parse_url($launcher['download']['url'] ?? '', PHP_URL_PATH) ?? '');
+ $launcherZipPresent = in_array($expectedFile, $launcherFiles, true);
+ $launcherHashed = !empty($launcher['download']['sha256'])
+ && !str_starts_with($launcher['download']['sha256'], 'REPLACE');
+}
+
+Layout::header('Launcher', 'launcher');
+?>
+
Launcher — auto-update
+
+
+
+
+
État actuel
+
+
+ Version annoncée : v= htmlspecialchars($launcher['version']) ?>
+ • minRequired : = htmlspecialchars($launcher['minRequired'] ?? '—') ?>
+ • Exe : = $launcherZipPresent
+ ? "présent"
+ : "absent" ?>
+ • Hash : = $launcherHashed
+ ? "OK"
+ : "à calculer" ?>
+ • Signature manifest : = empty($manifest['signature'])
+ ? "NON SIGNÉ"
+ : "SIGNÉ Ed25519" ?>
+
+
URL attendue de l'exe : = htmlspecialchars($launcher['download']['url']) ?>
+
+
Aucune version du launcher déclarée — l'auto-update est désactivé. Configure-la ci-dessous pour l'activer.
+
+
+
+
+
Workflow d'une nouvelle version du launcher
+
+ - Remplis le formulaire Définir / mettre à jour ci-dessous (ex :
0.6.0).
+ - Sur ton poste :
dotnet publish src\PSLauncher.App -c Release et dotnet publish src\PSLauncher.Updater -c Release (les exes sont copiés à la racine du repo).
+ - Renomme la copie :
PSLauncher.exe → PSLauncher-X.Y.Z.exe.
+ - Upload via SFTP dans
www/PS_Launcher/builds/launcher/.
+ - Clique 🔁 Hasher le launcher + signer.
+ - Les launchers déjà déployés détecteront la nouvelle version au prochain « Vérifier les MAJ ».
+
+
+
+
+
Définir / mettre à jour
+
+
+
+
+
+
+
+
+
+
+
Exes présents dans builds/launcher/
+
+
Aucun exe pour l'instant. Upload les binaires via SFTP dans www/PS_Launcher/builds/launcher/.
+
+
+ | Fichier | Taille |
+
+
+
+ = htmlspecialchars($f) ?> |
+ = Layout::formatBytes(filesize("$buildsDir/launcher/$f")) ?> |
+
+
+
+
+
+
+ ['index.php', 'Dashboard'],
'licenses' => ['licenses.php', 'Licenses'],
'versions' => ['versions.php', 'Versions'],
+ 'launcher' => ['launcher.php', 'Launcher'],
'audit' => ['audit.php', 'Audit'],
];
$links = '';
diff --git a/server/admin/versions.php b/server/admin/versions.php
index bf44fed..af3e3b0 100644
--- a/server/admin/versions.php
+++ b/server/admin/versions.php
@@ -132,41 +132,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
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.
+ elseif ($action === 'sync' || $action === 'sync_versions') {
require_once "$root/tools/SignManifest.php";
$signer = new \PSLauncher\Tools\SignManifest($root);
- $result = $signer->run();
- $message = "Sortie du script :\n" . implode("\n", $result['log']);
+ $scope = $action === 'sync_versions' ? 'versions' : 'all';
+ $result = $signer->run($scope);
+ $label = $scope === 'versions' ? 'versions Proserve' : 'manifest complet';
+ $message = "Sortie du script (scope: {$label}) :\n" . implode("\n", $result['log']);
if (!$result['ok']) $messageType = 'error';
}
} catch (Exception $e) {
@@ -233,81 +205,6 @@ Layout::header('Versions', 'versions');
-
-
-
Auto-update du launcher
-
-
-
- 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.
-
-
-
Définir / mettre à jour
-
-
-
-
-
-
-
Workflow
-
- - Définis la version ci-dessus (ex :
0.6.0).
- - Upload
PSLauncher-X.Y.Z.exe via SFTP dans www/PS_Launcher/builds/launcher/.
- - Clique 🔁 Sync (sign-manifest) dans le tableau du manifest ci-dessous — il hashera la section launcher en plus des versions Proserve.
-
-
-
-
Exes présents dans builds/launcher/
-
-
- = htmlspecialchars($f) ?> — = Layout::formatBytes(filesize("$buildsDir/launcher/$f")) ?>
-
-
-
-
-
Manifest actuel
@@ -322,8 +219,8 @@ Layout::header('Versions', 'versions');
diff --git a/server/tools/SignManifest.php b/server/tools/SignManifest.php
index ba88726..2bb7e92 100644
--- a/server/tools/SignManifest.php
+++ b/server/tools/SignManifest.php
@@ -26,13 +26,12 @@ final class SignManifest
private function out(string $line): void { $this->log[] = $line; }
/**
- * Recompute sizes / sha256 for every version that has its ZIP uploaded,
- * bump 'latest' to the highest signed version, then sign the manifest
- * with the Ed25519 private key from config.php (if available).
+ * Recompute sizes / sha256 selon le scope, puis re-signe le manifest avec Ed25519.
*
+ * @param string $scope 'all' (défaut) | 'versions' (ne touche que les Proserve) | 'launcher'
* @return array{ok:bool, log:string[]}
*/
- public function run(): array
+ public function run(string $scope = 'all'): array
{
if (!is_file($this->manifestPath)) {
$this->out("Manifest introuvable : {$this->manifestPath}");
@@ -45,8 +44,11 @@ final class SignManifest
return ['ok' => false, 'log' => $this->log];
}
+ $doVersions = ($scope === 'all' || $scope === 'versions');
+ $doLauncher = ($scope === 'all' || $scope === 'launcher');
+
$hashedVersions = [];
- foreach ($manifest['versions'] as &$v) {
+ if ($doVersions) foreach ($manifest['versions'] as &$v) {
$version = $v['version'] ?? '?';
$url = $v['download']['url'] ?? '';
if ($url === '') {
@@ -84,7 +86,7 @@ final class SignManifest
// Section launcher : si présente, on tente de hasher le PSLauncher-{ver}.exe
// dans builds/launcher/. On ignore proprement si l'exe n'est pas là.
- if (isset($manifest['launcher']) && is_array($manifest['launcher'])) {
+ if ($doLauncher && isset($manifest['launcher']) && is_array($manifest['launcher'])) {
$launcher = &$manifest['launcher'];
$lver = $launcher['version'] ?? '';
$lurl = $launcher['download']['url'] ?? '';
@@ -110,7 +112,8 @@ final class SignManifest
}
// Bump auto de `latest` sur la plus haute version effectivement uploadée
- if (!empty($hashedVersions)) {
+ // (uniquement si on a touché aux versions)
+ if ($doVersions && !empty($hashedVersions)) {
usort($hashedVersions, 'version_compare');
$newLatest = end($hashedVersions);
if (($manifest['latest'] ?? null) !== $newLatest) {