fc61404271
- 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
400 lines
14 KiB
C#
400 lines
14 KiB
C#
using System;
|
||
using System.Diagnostics;
|
||
using System.Management;
|
||
using System.Runtime.InteropServices;
|
||
using System.Text;
|
||
using System.Text.RegularExpressions;
|
||
|
||
namespace StudioE5.SetupWizard;
|
||
|
||
public record PrerequisiteResult(
|
||
bool WindowsCompatible,
|
||
ulong RamMB,
|
||
ulong FreeDiskMB,
|
||
bool VirtualEnvironmentInstalled,
|
||
bool PodmanInstalled,
|
||
bool PodmanMachineReady)
|
||
{
|
||
public bool AllReady => WindowsCompatible && RamMB >= 4096 && FreeDiskMB >= 5120 && VirtualEnvironmentInstalled && PodmanInstalled && PodmanMachineReady;
|
||
}
|
||
|
||
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: virtualEnvironmentInstalled,
|
||
PodmanInstalled: IsPodmanInstalled(),
|
||
PodmanMachineReady: podmanMachineReady
|
||
);
|
||
}
|
||
|
||
private static bool IsWindowsCompatible()
|
||
{
|
||
var os = Environment.OSVersion;
|
||
if (os.Platform != PlatformID.Win32NT)
|
||
return false;
|
||
|
||
// Windows 10 version 2004 (build 19041) or Windows 11.
|
||
return Environment.OSVersion.Version.Build >= 19041;
|
||
}
|
||
|
||
private static ulong GetTotalPhysicalMemoryMB()
|
||
{
|
||
try
|
||
{
|
||
using var searcher = new ManagementObjectSearcher("SELECT TotalVisibleMemorySize FROM Win32_OperatingSystem");
|
||
foreach (ManagementObject obj in searcher.Get())
|
||
{
|
||
var kb = Convert.ToUInt64(obj["TotalVisibleMemorySize"]);
|
||
return kb / 1024;
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// ignored
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
private static ulong GetFreeDiskSpaceMB(string path)
|
||
{
|
||
try
|
||
{
|
||
var drive = new DriveInfo(Path.GetPathRoot(path) ?? path);
|
||
return (ulong)(drive.AvailableFreeSpace / (1024 * 1024));
|
||
}
|
||
catch
|
||
{
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
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
|
||
{
|
||
var psi = new ProcessStartInfo("wsl.exe", "--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;
|
||
var output = process.StandardOutput.ReadToEnd();
|
||
var error = process.StandardError.ReadToEnd();
|
||
process.WaitForExit();
|
||
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
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private static bool WslListIndicatesWsl2()
|
||
{
|
||
try
|
||
{
|
||
var psi = new ProcessStartInfo("wsl.exe", "--list --verbose")
|
||
{
|
||
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();
|
||
|
||
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;
|
||
process.WaitForExit();
|
||
return process.ExitCode == 0;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private static bool IsPodmanMachineReady()
|
||
{
|
||
var podmanPath = GetPodmanExePath();
|
||
if (string.IsNullOrEmpty(podmanPath))
|
||
return false;
|
||
|
||
try
|
||
{
|
||
var psi = new ProcessStartInfo(podmanPath, "machine list --format json")
|
||
{
|
||
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();
|
||
process.WaitForExit();
|
||
if (process.ExitCode != 0) return false;
|
||
|
||
// Very permissive check: if podman machine list returns any JSON, we consider it ready.
|
||
return output.TrimStart().StartsWith("[") || output.TrimStart().StartsWith("{");
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
}
|