feat: installation offline complete, HTTPS registry, 8Go WSL, v0.3.18
- Wizard: installation 100% offline (WSL bundle, Podman MSI, machine image, docker-compose) - Wizard: suppression de wsl --install --no-distribution - Wizard: .wslconfig avec 8Go RAM / 4 CPU - Wizard: operations asynchrones pour eviter le freeze UI - Wizard: detection automatique de podman.exe - Wizard: version 0.1.1 - Agent: passage en v0.3.18 - Serveur: registry PrestaShop en HTTPS via gitea.alfrednobel.edudeploy.com - Caddy: config gitea.alfrednobel.edudeploy.com - Docs: mise a jour SUIVI_INSTALLER.md, README.md, seed.ts
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace StudioE5.SetupWizard;
|
||||
|
||||
@@ -19,13 +22,20 @@ public static class PrerequisiteChecker
|
||||
{
|
||||
public static PrerequisiteResult Check()
|
||||
{
|
||||
var wsl2Ready = IsWSL2Ready();
|
||||
var podmanMachineReady = IsPodmanMachineReady();
|
||||
|
||||
// Fallback : si la machine Podman est prête, WSL2 est nécessairement fonctionnel.
|
||||
// Cela contourne les problèmes de détection WSL liés à l'encodage ou au PATH.
|
||||
var virtualEnvironmentInstalled = wsl2Ready || podmanMachineReady;
|
||||
|
||||
return new PrerequisiteResult(
|
||||
WindowsCompatible: IsWindowsCompatible(),
|
||||
RamMB: GetTotalPhysicalMemoryMB(),
|
||||
FreeDiskMB: GetFreeDiskSpaceMB("C:\\"),
|
||||
VirtualEnvironmentInstalled: IsWSLInstalled(),
|
||||
VirtualEnvironmentInstalled: virtualEnvironmentInstalled,
|
||||
PodmanInstalled: IsPodmanInstalled(),
|
||||
PodmanMachineReady: IsPodmanMachineReady()
|
||||
PodmanMachineReady: podmanMachineReady
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,7 +80,143 @@ public static class PrerequisiteChecker
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsWSL2Ready()
|
||||
{
|
||||
// PowerShell gère mieux l'encodage de la sortie WSL que Process.Start en C#.
|
||||
if (IsWSL2ReadyViaPowerShell())
|
||||
return true;
|
||||
|
||||
// Fallback natif si PowerShell n'est pas disponible.
|
||||
return IsWSL2ReadyNative();
|
||||
}
|
||||
|
||||
private static bool IsWSL2ReadyViaPowerShell()
|
||||
{
|
||||
try
|
||||
{
|
||||
var tempFile = Path.GetTempFileName();
|
||||
var script =
|
||||
"$status = & wsl.exe --status 2>&1; " +
|
||||
"$ready = ($status -match 'Version par d\\u00E9faut\\s*:\\s*2') -or " +
|
||||
"($status -match 'Default Version\\s*:\\s*2'); " +
|
||||
"$ready | Out-File -FilePath '" + tempFile + "' -Encoding utf8 -NoNewline";
|
||||
|
||||
var psi = new ProcessStartInfo("powershell.exe", $"-ExecutionPolicy Bypass -Command \"{script}\"")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
|
||||
using var process = Process.Start(psi);
|
||||
if (process == null) return false;
|
||||
process.WaitForExit();
|
||||
|
||||
if (!File.Exists(tempFile))
|
||||
return false;
|
||||
|
||||
var result = File.ReadAllText(tempFile).Trim();
|
||||
File.Delete(tempFile);
|
||||
|
||||
return result.Equals("True", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsWSL2ReadyNative()
|
||||
{
|
||||
try
|
||||
{
|
||||
// wsl --status est plus fiable que --version pour savoir si WSL2 est prêt.
|
||||
var psi = new ProcessStartInfo("wsl.exe", "--status")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
using var process = Process.Start(psi);
|
||||
if (process == null) return false;
|
||||
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
// wsl --status peut retourner un code non nul même quand l’info utile est affichée
|
||||
// (par exemple si aucune distribution n’est installée). On parse quand même.
|
||||
var combined = output + "\n" + error;
|
||||
var normalized = combined
|
||||
.Replace('\u00A0', ' ')
|
||||
.Replace('\u202F', ' ');
|
||||
|
||||
if (normalized.Contains("Version par défaut : 2", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalized.Contains("Default Version: 2", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalized.Contains("Version défaut : 2", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var defaultVersion = ParseWslDefaultVersion(combined);
|
||||
if (defaultVersion == 2)
|
||||
return true;
|
||||
|
||||
// Si aucune version par défaut n'est trouvée, on tente les autres méthodes.
|
||||
return (defaultVersion == 0 && WslVersionIndicatesWsl2()) ||
|
||||
WslListIndicatesWsl2();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsWSLInstalled()
|
||||
{
|
||||
return IsWSL2Ready();
|
||||
}
|
||||
|
||||
private static int ParseWslDefaultVersion(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var rawLine in text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
// Normalise les espaces insécables et les espaces multiples.
|
||||
var trimmed = rawLine
|
||||
.Replace('\u00A0', ' ')
|
||||
.Replace('\u202F', ' ')
|
||||
.Trim();
|
||||
|
||||
// Regex souple pour matcher :
|
||||
// - Default Version: 2
|
||||
// - Version par défaut : 2
|
||||
// - Version défaut:2
|
||||
// etc.
|
||||
var match = Regex.Match(
|
||||
trimmed,
|
||||
@"(?i)(?:default\s+version|version\s+(?:par\s+)?d[eé]faut)\s*[:\-]?\s*(\d+)",
|
||||
RegexOptions.CultureInvariant);
|
||||
|
||||
if (match.Success && int.TryParse(match.Groups[1].Value, out var version))
|
||||
return version;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool WslVersionIndicatesWsl2()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -79,12 +225,23 @@ public static class PrerequisiteChecker
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
using var process = Process.Start(psi);
|
||||
if (process == null) return false;
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
return process.ExitCode == 0;
|
||||
if (process.ExitCode != 0) return false;
|
||||
|
||||
var combined = output + "\n" + error;
|
||||
|
||||
// Si la sortie mentionne explicitement WSL 2 ou un noyau 5.10+, on considère WSL2 prêt.
|
||||
return combined.Contains("WSL version: 2", StringComparison.OrdinalIgnoreCase) ||
|
||||
combined.Contains("WSL version: 2.0", StringComparison.OrdinalIgnoreCase) ||
|
||||
combined.Contains("Kernel version: 5.10", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -92,16 +249,110 @@ public static class PrerequisiteChecker
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPodmanInstalled()
|
||||
private static bool WslListIndicatesWsl2()
|
||||
{
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo("podman.exe", "--version")
|
||||
var psi = new ProcessStartInfo("wsl.exe", "--list --verbose")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
using var process = Process.Start(psi);
|
||||
if (process == null) return false;
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
var combined = output + "\n" + error;
|
||||
|
||||
// Si au moins une distribution est en version 2, WSL2 est fonctionnel.
|
||||
foreach (var line in combined.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var parts = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length >= 2 && parts[^1] == "2")
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string? GetPodmanExePath()
|
||||
{
|
||||
// 1. Chercher dans le PATH actuel du processus.
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo("where.exe", "podman.exe")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
using var process = Process.Start(psi);
|
||||
if (process != null)
|
||||
{
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
if (process.ExitCode == 0)
|
||||
{
|
||||
var firstLine = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(firstLine) && File.Exists(firstLine))
|
||||
return firstLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
// 2. Chercher dans les emplacements d'installation connus.
|
||||
var candidates = new[]
|
||||
{
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", "podman", "podman.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "RedHat", "Podman", "podman.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Podman", "podman.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "RedHat", "Podman", "podman.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Podman", "podman.exe"),
|
||||
};
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (File.Exists(candidate))
|
||||
return candidate;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsPodmanInstalled()
|
||||
{
|
||||
var podmanPath = GetPodmanExePath();
|
||||
if (string.IsNullOrEmpty(podmanPath))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo(podmanPath, "--version")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
using var process = Process.Start(psi);
|
||||
if (process == null) return false;
|
||||
@@ -116,14 +367,20 @@ public static class PrerequisiteChecker
|
||||
|
||||
private static bool IsPodmanMachineReady()
|
||||
{
|
||||
var podmanPath = GetPodmanExePath();
|
||||
if (string.IsNullOrEmpty(podmanPath))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo("podman.exe", "machine list --format json")
|
||||
var psi = new ProcessStartInfo(podmanPath, "machine list --format json")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
using var process = Process.Start(psi);
|
||||
if (process == null) return false;
|
||||
|
||||
Reference in New Issue
Block a user