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:
2026-05-12 16:56:55 +02:00
parent 9b17dfa84c
commit efb53f079e
31 changed files with 1994 additions and 77 deletions

View File

@@ -4,6 +4,7 @@ using System.Net.Sockets;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using PSLauncher.Core.Lan;
using PSLauncher.Core.Security;
using PSLauncher.Models;
namespace PSLauncher.Core.Manifests;
@@ -20,6 +21,7 @@ public sealed class ManifestService : IManifestService
private readonly Func<string?> _channelProvider;
private readonly IManifestCache _manifestCache;
private readonly IPeerManifestFetcher _peerManifestFetcher;
private readonly ISettingsLockService _settingsLock;
private readonly ILogger<ManifestService> _logger;
public ManifestSource LastSource { get; private set; } = ManifestSource.None;
@@ -30,6 +32,7 @@ public sealed class ManifestService : IManifestService
Func<string?> channelProvider,
IManifestCache manifestCache,
IPeerManifestFetcher peerManifestFetcher,
ISettingsLockService settingsLock,
ILogger<ManifestService> logger)
{
_http = http;
@@ -37,9 +40,23 @@ public sealed class ManifestService : IManifestService
_channelProvider = channelProvider;
_manifestCache = manifestCache;
_peerManifestFetcher = peerManifestFetcher;
_settingsLock = settingsLock;
_logger = logger;
}
/// <summary>
/// No-op depuis v0.28 : le hash settings-lock vient maintenant de la
/// <c>LicenseValidationResponse</c> (per-license). Voir
/// <see cref="PSLauncher.Core.Licensing.LicenseService.ValidateAsync"/> et
/// <see cref="PSLauncher.Core.Licensing.LicenseService.GetCached"/> pour la
/// propagation effective vers <see cref="ISettingsLockService"/>.
/// </summary>
private void PropagateSettingsLock(RemoteManifest? manifest)
{
_ = manifest;
_ = _settingsLock;
}
/// <summary>
/// Timeout court sur la tentative OVH : si le serveur Internet ne répond pas
/// rapidement, on bascule sur les fallbacks LAN/cache plutôt que d'attendre les
@@ -76,6 +93,7 @@ public sealed class ManifestService : IManifestService
?? throw new InvalidOperationException("Empty manifest");
await _manifestCache.SaveAsync(rawJson, ct).ConfigureAwait(false);
LastSource = ManifestSource.Ovh;
PropagateSettingsLock(manifest);
return manifest;
}
catch (OperationCanceledException) when (ct.IsCancellationRequested) { throw; }
@@ -86,6 +104,7 @@ public sealed class ManifestService : IManifestService
if (cached is not null)
{
LastSource = ManifestSource.DiskCache;
PropagateSettingsLock(cached);
return cached;
}
throw;
@@ -102,6 +121,7 @@ public sealed class ManifestService : IManifestService
// Cache aussi le peer manifest pour servir aux autres clients du LAN.
await _manifestCache.SaveAsync(peerJson, ct).ConfigureAwait(false);
LastSource = ManifestSource.Peer;
PropagateSettingsLock(manifest);
return manifest;
}
@@ -111,6 +131,7 @@ public sealed class ManifestService : IManifestService
{
_logger.LogInformation("Manifest récupéré depuis le cache disque (offline + pas de peer)");
LastSource = ManifestSource.DiskCache;
PropagateSettingsLock(localCache);
return localCache;
}