v0.28.10 — Mode auto + settings lock par license + fix post-install state
Mode auto (auto-launch d'une version désignée au démarrage et après exit) : - Master toggle + version désignée via bouton AUTO (orange) sur chaque row - Countdown 5 s configurable avec popup d'annulation (StartAutoLaunchCountdown) - Args CLI passés à PROSERVE : opérateur tape juste le nom (autoconnect, login), le launcher ajoute -/=/quotes. Format produit identique à un .bat. Fix : passage de ArgumentList à Arguments string pour éviter le quotage auto .NET sur les args contenant '=' (cassait FParse::Value). - Garde-fou anti double-lancement : refuse de lancer si _runningProserve vivant OU IProcessLauncher.IsRunning() détecte une instance externe. Silent pour les triggers auto, popup pour les clics manuels. - Option « Attendre santé système » : countdown différé tant que les health checks ne sont pas tous OK (phase 1 du dialog avec liste des pending). Settings lock par license (remplace l'ancien lock global manifest) : - Colonne settings_lock_password_hash sur table licenses (migration 003) - Backoffice : bouton 🔒 par license pour set/clear hash SHA-256 - Payload /license/validate inclut settingsLockPasswordHash (v3 canonique) - 3-tier fallback signature : v3 → v2 → legacy pour compat cache offline - SettingsLockService : in-memory unlock state, gate l'expander Avancés - SettingsLockDialog : prompt mdp, persiste déverrouillé jusqu'au restart Fix post-install : la row restait en visuel « Installing » jusqu'au prochain Check Updates. Reset explicite row.State=InstalledIdle + _activeRow=null AVANT le RebuildList post-install pour purger l'instance orpheline. Migrations : - 002_channel_betas.sql réécrit en ALTER simples (DELIMITER cassait migrate.php qui split sur ';\n') - migrate.php tolère « Duplicate column/key name » comme idempotent Strings (5 langues, ~25 nouvelles) : - Renomme « Relance automatique » → « Lancement automatique » (dialog utilisé pour les 3 entry points : clic, startup, post-exit) - Tooltips auto-mode, settings lock prompts, health wait phase, etc. Bumps : 0.27.4 → 0.28.10 (csproj + .iss). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$message = "Sortie du script (scope: launcher{$forceLabel}) :\n" . implode("\n", $result['log']);
|
||||
if (!$result['ok']) $messageType = 'error';
|
||||
}
|
||||
// (Le settings lock est maintenant par-license, géré dans licenses.php — voir migration 003.)
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
$messageType = 'error';
|
||||
|
||||
@@ -145,6 +145,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$db->prepare('UPDATE licenses SET max_machines = ? WHERE id = ?')->execute([$newMax, $id]);
|
||||
$message = "License #{$id} : limite passée à {$newMax} machine(s).";
|
||||
}
|
||||
elseif ($action === 'set_settings_lock_password') {
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$pwd = (string)($_POST['settings_lock_password'] ?? '');
|
||||
if ($id <= 0) {
|
||||
throw new Exception('License id invalide.');
|
||||
}
|
||||
if ($pwd === '') {
|
||||
// Vide = retire le verrouillage pour cette license
|
||||
$db->prepare('UPDATE licenses SET settings_lock_password_hash = NULL WHERE id = ?')->execute([$id]);
|
||||
$message = "License #{$id} : verrouillage des paramètres avancés retiré.";
|
||||
} else {
|
||||
if (strlen($pwd) < 4) {
|
||||
throw new Exception('Mot de passe trop court (minimum 4 caractères).');
|
||||
}
|
||||
// SHA-256 hex lowercase, format compatible avec SettingsLockService côté launcher.
|
||||
$hash = hash('sha256', $pwd);
|
||||
$db->prepare('UPDATE licenses SET settings_lock_password_hash = ? WHERE id = ?')->execute([$hash, $id]);
|
||||
$message = "License #{$id} : mot de passe des paramètres avancés mis à jour.";
|
||||
}
|
||||
}
|
||||
elseif ($action === 'reset_machines') {
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$db->prepare('DELETE FROM license_machines WHERE license_id = ?')->execute([$id]);
|
||||
@@ -363,6 +383,24 @@ Layout::header('Licenses', 'licenses');
|
||||
<button class="btn btn-primary" type="submit">OK</button>
|
||||
</form>
|
||||
</details>
|
||||
<?php $hasLock = !empty($l['settings_lock_password_hash']); ?>
|
||||
<details style="display: inline-block; margin: 0 4px;">
|
||||
<summary class="btn btn-secondary"
|
||||
title="Mot de passe qui verrouille la section Settings → Avancés du launcher pour cette license. Laisser vide pour retirer le verrouillage.">
|
||||
<?= $hasLock ? '🔒 Lock' : 'Lock' ?>
|
||||
</summary>
|
||||
<form method="post" style="margin-top: 8px; display: flex; gap: 4px; align-items: center;">
|
||||
<?= Layout::csrfField() ?>
|
||||
<input type="hidden" name="action" value="set_settings_lock_password">
|
||||
<input type="hidden" name="id" value="<?= $l['id'] ?>">
|
||||
<input type="password" name="settings_lock_password"
|
||||
placeholder="<?= $hasLock ? 'Nouveau mot de passe (vide = retirer)' : 'Mot de passe (≥4)' ?>"
|
||||
minlength="0" maxlength="100"
|
||||
style="width: 200px;"
|
||||
autocomplete="new-password">
|
||||
<button class="btn btn-primary" type="submit">OK</button>
|
||||
</form>
|
||||
</details>
|
||||
<?php if ($l['machines_count'] > 0): ?>
|
||||
<form method="post" style="display:inline" onsubmit="return confirm('Libérer TOUTES les machines de cette license ?\n\nUtilise plutôt « Voir machines » ci-dessous pour libérer un slot précis.')">
|
||||
<?= Layout::csrfField() ?>
|
||||
|
||||
@@ -30,7 +30,7 @@ final class ValidateLicense
|
||||
|
||||
$stmt = $db->prepare(
|
||||
'SELECT id, license_key, owner_name, issued_at, download_entitlement_until,
|
||||
max_machines, channel, can_see_betas, revoked_at
|
||||
max_machines, channel, can_see_betas, settings_lock_password_hash, revoked_at
|
||||
FROM licenses WHERE license_key = ? LIMIT 1'
|
||||
);
|
||||
$stmt->execute([$licenseKey]);
|
||||
@@ -92,6 +92,12 @@ final class ValidateLicense
|
||||
? (string)$lic['channel']
|
||||
: null;
|
||||
$canSeeBetas = (bool)($lic['can_see_betas'] ?? 0);
|
||||
// SettingsLockPasswordHash : présent depuis migration 003, NULL = pas de
|
||||
// verrouillage. On renvoie tel quel (null ou hex 64). Le launcher applique
|
||||
// côté ISettingsLockService.SetPasswordHash après validation signée.
|
||||
$settingsLockHash = isset($lic['settings_lock_password_hash']) && $lic['settings_lock_password_hash'] !== ''
|
||||
? (string)$lic['settings_lock_password_hash']
|
||||
: null;
|
||||
|
||||
$payload = [
|
||||
'status' => $expired ? 'expired' : 'valid',
|
||||
@@ -102,6 +108,7 @@ final class ValidateLicense
|
||||
'maxMachines' => (int)$lic['max_machines'],
|
||||
'channel' => $channel,
|
||||
'canSeeBetas' => $canSeeBetas,
|
||||
'settingsLockPasswordHash' => $settingsLockHash,
|
||||
'serverTime' => $serverTime,
|
||||
];
|
||||
|
||||
|
||||
@@ -5,45 +5,14 @@
|
||||
--
|
||||
-- À jouer après 001_init.sql.
|
||||
--
|
||||
-- IDEMPOTENCE : OVH mutualisé peut tourner sur MariaDB pré-10.0.2 où
|
||||
-- ALTER TABLE ... IF NOT EXISTS n'existe pas. On contourne avec une PROCEDURE
|
||||
-- temporaire qui consulte INFORMATION_SCHEMA avant chaque ALTER. Comme ça la
|
||||
-- migration peut être re-jouée sans erreur sur n'importe quelle version
|
||||
-- MariaDB / MySQL 5.5+.
|
||||
-- IDEMPOTENCE : les ALTER ci-dessous throw une exception sur OVH si la colonne /
|
||||
-- l'index existe déjà. migrate.php attrape ces erreurs spécifiques ("Duplicate
|
||||
-- column", "Duplicate key name") et continue. Tu peux rejouer le script sans
|
||||
-- risque sur n'importe quelle DB (vide, partiellement migrée, complètement).
|
||||
|
||||
DROP PROCEDURE IF EXISTS ps_launcher_migrate_002;
|
||||
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE ps_launcher_migrate_002()
|
||||
BEGIN
|
||||
-- channel
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'licenses' AND COLUMN_NAME = 'channel'
|
||||
) THEN
|
||||
ALTER TABLE licenses ADD COLUMN channel VARCHAR(64) NULL AFTER max_machines;
|
||||
END IF;
|
||||
|
||||
-- can_see_betas
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'licenses' AND COLUMN_NAME = 'can_see_betas'
|
||||
) THEN
|
||||
ALTER TABLE licenses ADD COLUMN can_see_betas TINYINT(1) NOT NULL DEFAULT 0 AFTER channel;
|
||||
END IF;
|
||||
|
||||
-- index sur channel (une simple recherche bénéficie peu mais c'est pas cher)
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'licenses' AND INDEX_NAME = 'idx_channel'
|
||||
) THEN
|
||||
ALTER TABLE licenses ADD INDEX idx_channel (channel);
|
||||
END IF;
|
||||
END $$
|
||||
DELIMITER ;
|
||||
|
||||
CALL ps_launcher_migrate_002();
|
||||
DROP PROCEDURE ps_launcher_migrate_002;
|
||||
ALTER TABLE licenses ADD COLUMN channel VARCHAR(64) NULL AFTER max_machines;
|
||||
ALTER TABLE licenses ADD COLUMN can_see_betas TINYINT(1) NOT NULL DEFAULT 0 AFTER channel;
|
||||
ALTER TABLE licenses ADD INDEX idx_channel (channel);
|
||||
|
||||
-- Note : pas de migration de data. Les licenses existantes gardent
|
||||
-- channel = NULL (= manifest default) et can_see_betas = 0 (= pas d'accès aux betas).
|
||||
|
||||
15
server/migrations/003_settings_lock.sql
Normal file
15
server/migrations/003_settings_lock.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- PS_Launcher schema v3
|
||||
-- Ajoute settings_lock_password_hash sur la table licenses : permet à l'admin
|
||||
-- de définir un mot de passe par-license qui verrouille la section "Avancés"
|
||||
-- des Settings côté launcher. Le hash SHA-256 (hex lowercase) est renvoyé au
|
||||
-- launcher dans la réponse signée de /license/validate, donc l'intégrité
|
||||
-- bénéficie de la signature Ed25519 existante.
|
||||
--
|
||||
-- À jouer après 001_init.sql et 002_channel_betas.sql.
|
||||
-- migrate.php attrape "Duplicate column" comme idempotent.
|
||||
|
||||
ALTER TABLE licenses ADD COLUMN settings_lock_password_hash CHAR(64) NULL AFTER can_see_betas;
|
||||
|
||||
-- Note : les licenses existantes ont settings_lock_password_hash = NULL → pas
|
||||
-- de verrouillage côté launcher → comportement identique à avant. L'admin
|
||||
-- attribue manuellement via le bouton 🔒 Lock dans licenses.php.
|
||||
@@ -76,7 +76,20 @@ foreach ($files as $f) {
|
||||
$first40 = substr(preg_replace('/\s+/', ' ', $stmt), 0, 60);
|
||||
out(" ✔ " . $first40 . "…");
|
||||
} catch (Exception $e) {
|
||||
out(" ✘ ERREUR : " . $e->getMessage());
|
||||
$msg = $e->getMessage();
|
||||
// Tolérance idempotence : on n'aboie pas sur les ALTER qui re-touchent
|
||||
// une colonne / un index existant. Permet de rejouer migrate.php sans
|
||||
// risque sur une DB déjà partiellement / complètement migrée.
|
||||
$idempotent = str_contains($msg, 'Duplicate column')
|
||||
|| str_contains($msg, 'Duplicate key name')
|
||||
|| str_contains($msg, "Can't DROP") // ALTER DROP COLUMN inexistante
|
||||
|| str_contains($msg, 'check that column/key exists');
|
||||
if ($idempotent) {
|
||||
$first40 = substr(preg_replace('/\s+/', ' ', $stmt), 0, 60);
|
||||
out(" ↺ (déjà appliqué) " . $first40 . "…");
|
||||
continue;
|
||||
}
|
||||
out(" ✘ ERREUR : " . $msg);
|
||||
out(" Sur : " . substr($stmt, 0, 80) . "…");
|
||||
exit(3);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user