Files
edubox/agent/installer/setup-wizard/PrerequisiteChecker.cs
EduBox Dev fc61404271 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
2026-07-02 22:52:28 +00:00

400 lines
14 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 linfo utile est affichée
// (par exemple si aucune distribution nest 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;
}
}
}