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:
@@ -26,6 +26,11 @@ headscale.studioe5.edudeploy.com:443 {
|
|||||||
reverse_proxy headscale:8080
|
reverse_proxy headscale:8080
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gitea.alfrednobel.edudeploy.com {
|
||||||
|
tls admin@edudeploy.com
|
||||||
|
reverse_proxy 151.80.60.98:3001
|
||||||
|
}
|
||||||
|
|
||||||
studioe5.edudeploy.com:443 {
|
studioe5.edudeploy.com:443 {
|
||||||
route /studioE5-agent* {
|
route /studioE5-agent* {
|
||||||
file_server {
|
file_server {
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
0.3.17
|
0.3.18
|
||||||
|
|||||||
@@ -24,9 +24,20 @@ Fournir un **installateur professionnel Windows** pour studioE5 Agent, guidé pa
|
|||||||
|
|
||||||
- Wizard C# avec 7 étapes guidées.
|
- Wizard C# avec 7 étapes guidées.
|
||||||
- Détection des prérequis : Windows, RAM, disque, WSL2, Podman.
|
- Détection des prérequis : Windows, RAM, disque, WSL2, Podman.
|
||||||
- Installation WSL2 avec redémarrage + reprise automatique.
|
- Installation WSL2 en plusieurs étapes contrôlées avec suivi visuel :
|
||||||
|
1. Activation des fonctionnalités Windows (`Microsoft-Windows-Subsystem-Linux` et `VirtualMachinePlatform`) avec gestion du code 3010 (redémarrage nécessaire).
|
||||||
|
2. Installation du package WSL2 complet depuis le bundle Microsoft Store offline (`Microsoft.WSL_*.msixbundle`) via `Add-AppxPackage`.
|
||||||
|
3. Mise à jour du noyau WSL2 depuis le MSI bundlé (`wsl_update_x64.msi`) ou, en dernier recours, via `wsl --update`.
|
||||||
|
4. Définition de WSL2 comme version par défaut (`wsl --set-default-version 2`).
|
||||||
|
5. L’étape `wsl --install --no-distribution` n’est plus utilisée : l’installation est entièrement offline grâce au bundle.
|
||||||
|
6. Conversion des distributions WSL1 existantes vers WSL2 (`wsl --set-version <nom> 2`) si nécessaire.
|
||||||
|
- Reprise automatique après redémarrage grâce à `RunOnce` (y compris redémarrage post-installation WSL2).
|
||||||
|
- Versioning indépendant du wizard (fichier `setup-wizard/VERSION`, affiché dans la fenêtre et la page d’accueil).
|
||||||
|
- Amélioration de l’interface : fenêtre agrandie à 800×600, `AutoScaleMode = Dpi`, meilleur rendu sur petits écrans (11 pouces).
|
||||||
|
- `RunCommand` capture désormais stdout + stderr pour des diagnostics et fallbacks plus fiables.
|
||||||
|
- Détection des prérequis vérifie que WSL2 (et non WSL1) est installé via `wsl --status`.
|
||||||
- Installation Podman via MSI bundlé.
|
- Installation Podman via MSI bundlé.
|
||||||
- Configuration Podman (`machine init` + `machine start`).
|
- Configuration Podman (`machine init` + `machine start`), avec initialisation offline possible depuis une image locale (`podman-machine.*.wsl.tar.zst`).
|
||||||
- Lancement du package Inno Setup agent.
|
- Lancement du package Inno Setup agent.
|
||||||
- Mode désinstallation complet.
|
- Mode désinstallation complet.
|
||||||
- Script Inno Setup de base pour l’agent.
|
- Script Inno Setup de base pour l’agent.
|
||||||
@@ -57,6 +68,10 @@ Dans `setup-wizard/Resources/` :
|
|||||||
```text
|
```text
|
||||||
podman-installer-windows-amd64.msi
|
podman-installer-windows-amd64.msi
|
||||||
studioE5-agent-setup.exe
|
studioE5-agent-setup.exe
|
||||||
|
Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle # package WSL2 complet (offline)
|
||||||
|
podman-machine.x86_64.wsl.tar.zst # image Podman machine pour WSL (offline)
|
||||||
|
docker-compose-windows-x86_64.exe # Docker Compose standalone (offline)
|
||||||
|
wsl_update_x64.msi # optionnel, fallback noyau WSL2
|
||||||
```
|
```
|
||||||
|
|
||||||
### Commande
|
### Commande
|
||||||
@@ -95,8 +110,14 @@ Le fichier généré se trouve dans `installer-output/`.
|
|||||||
## Notes importantes
|
## Notes importantes
|
||||||
|
|
||||||
- Le wizard doit être exécuté **en administrateur**.
|
- Le wizard doit être exécuté **en administrateur**.
|
||||||
- L’installation de WSL2 nécessite un **redémarrage** de l’ordinateur.
|
- L’installation de WSL2 nécessite un **redémarrage** de l’ordinateur après l’activation des fonctionnalités Windows.
|
||||||
|
- L’installation de WSL2 est découpée en étapes et chaque étape est enregistrée pour permettre la reprise après redémarrage.
|
||||||
|
- Podman nécessite WSL2 : le wizard vérifie donc que la version par défaut de WSL est 2.
|
||||||
- Le MSI Podman officiel pour Windows est `podman-installer-windows-amd64.msi`.
|
- Le MSI Podman officiel pour Windows est `podman-installer-windows-amd64.msi`.
|
||||||
|
- Le bundle WSL2 offline est disponible sur <https://github.com/microsoft/WSL/releases> : `Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle`.
|
||||||
|
- L’image Podman machine offline est disponible sur <https://github.com/containers/podman-machine-os/releases> : `podman-machine.x86_64.wsl.tar.zst`.
|
||||||
|
- Docker Compose standalone est disponible sur <https://github.com/docker/compose/releases> : `docker-compose-windows-x86_64.exe`.
|
||||||
|
- Le wizard crée automatiquement un fichier `.wslconfig` allouant **8 Go de RAM et 4 CPU** à WSL2, ce qui est nécessaire pour des applications lourdes comme PrestaShop.
|
||||||
- Pour la désinstallation, le MSI Podman doit être présent dans `Resources/`.
|
- Pour la désinstallation, le MSI Podman doit être présent dans `Resources/`.
|
||||||
|
|
||||||
## Liens utiles
|
## Liens utiles
|
||||||
|
|||||||
@@ -24,6 +24,18 @@ public class InstallerState
|
|||||||
[JsonPropertyName("virtualEnvironmentInstalled")]
|
[JsonPropertyName("virtualEnvironmentInstalled")]
|
||||||
public bool VirtualEnvironmentInstalled { get; set; }
|
public bool VirtualEnvironmentInstalled { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("wslFeaturesEnabled")]
|
||||||
|
public bool WslFeaturesEnabled { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("wslPackageInstalled")]
|
||||||
|
public bool WslPackageInstalled { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("wslDefaultVersionSet")]
|
||||||
|
public bool WslDefaultVersionSet { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("wslKernelUpdated")]
|
||||||
|
public bool WslKernelUpdated { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("podmanInstalled")]
|
[JsonPropertyName("podmanInstalled")]
|
||||||
public bool PodmanInstalled { get; set; }
|
public bool PodmanInstalled { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
|
||||||
namespace StudioE5.SetupWizard;
|
namespace StudioE5.SetupWizard;
|
||||||
@@ -13,6 +15,7 @@ public partial class MainForm : Form
|
|||||||
private readonly Button _btnCancel;
|
private readonly Button _btnCancel;
|
||||||
private readonly Label _titleLabel;
|
private readonly Label _titleLabel;
|
||||||
private readonly Label _subtitleLabel;
|
private readonly Label _subtitleLabel;
|
||||||
|
private readonly Label _statusLabel;
|
||||||
|
|
||||||
private WizardStep _currentStep = WizardStep.Welcome;
|
private WizardStep _currentStep = WizardStep.Welcome;
|
||||||
private PrerequisiteResult? _lastCheck;
|
private PrerequisiteResult? _lastCheck;
|
||||||
@@ -20,7 +23,8 @@ public partial class MainForm : Form
|
|||||||
public MainForm(bool startInUninstallMode = false)
|
public MainForm(bool startInUninstallMode = false)
|
||||||
{
|
{
|
||||||
_state = InstallerState.Load();
|
_state = InstallerState.Load();
|
||||||
Text = startInUninstallMode ? "Désinstallation studioE5" : "Installateur studioE5 Agent";
|
var version = GetVersion();
|
||||||
|
Text = startInUninstallMode ? $"Désinstallation studioE5 v{version}" : $"Installateur studioE5 Agent v{version}";
|
||||||
Size = new Size(700, 520);
|
Size = new Size(700, 520);
|
||||||
StartPosition = FormStartPosition.CenterScreen;
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
@@ -34,7 +38,7 @@ public partial class MainForm : Form
|
|||||||
Width = 640,
|
Width = 640,
|
||||||
Height = 32,
|
Height = 32,
|
||||||
Font = new Font("Segoe UI", 14, FontStyle.Bold),
|
Font = new Font("Segoe UI", 14, FontStyle.Bold),
|
||||||
Text = "Installateur studioE5 Agent"
|
Text = $"Installateur studioE5 Agent v{version}"
|
||||||
};
|
};
|
||||||
|
|
||||||
_subtitleLabel = new Label
|
_subtitleLabel = new Label
|
||||||
@@ -47,12 +51,23 @@ public partial class MainForm : Form
|
|||||||
ForeColor = Color.Gray
|
ForeColor = Color.Gray
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_statusLabel = new Label
|
||||||
|
{
|
||||||
|
Left = 24,
|
||||||
|
Top = 76,
|
||||||
|
Width = 640,
|
||||||
|
Height = 24,
|
||||||
|
Font = new Font("Segoe UI", 9, FontStyle.Bold),
|
||||||
|
ForeColor = Color.DodgerBlue,
|
||||||
|
Visible = false
|
||||||
|
};
|
||||||
|
|
||||||
_contentPanel = new Panel
|
_contentPanel = new Panel
|
||||||
{
|
{
|
||||||
Left = 24,
|
Left = 24,
|
||||||
Top = 84,
|
Top = 100,
|
||||||
Width = 640,
|
Width = 640,
|
||||||
Height = 320,
|
Height = 304,
|
||||||
BorderStyle = BorderStyle.None
|
BorderStyle = BorderStyle.None
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,6 +114,7 @@ public partial class MainForm : Form
|
|||||||
|
|
||||||
Controls.Add(_titleLabel);
|
Controls.Add(_titleLabel);
|
||||||
Controls.Add(_subtitleLabel);
|
Controls.Add(_subtitleLabel);
|
||||||
|
Controls.Add(_statusLabel);
|
||||||
Controls.Add(_contentPanel);
|
Controls.Add(_contentPanel);
|
||||||
Controls.Add(separator);
|
Controls.Add(separator);
|
||||||
Controls.Add(_btnBack);
|
Controls.Add(_btnBack);
|
||||||
@@ -116,18 +132,30 @@ public partial class MainForm : Form
|
|||||||
|
|
||||||
private void ResumeAfterReboot()
|
private void ResumeAfterReboot()
|
||||||
{
|
{
|
||||||
// If we are resuming after a reboot, skip directly to the relevant step.
|
// Si aucune étape n'a été faite, c'est une nouvelle installation (pas une reprise).
|
||||||
if (_state.VirtualEnvironmentInstalled && !_state.PodmanInstalled)
|
if (IsFreshInstall())
|
||||||
|
{
|
||||||
|
GoToStep(WizardStep.Welcome);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reprise après redémarrage : on saute à la première étape non terminée.
|
||||||
|
if (!_state.WslFeaturesEnabled || !_state.WslPackageInstalled || !_state.VirtualEnvironmentInstalled || !_state.WslDefaultVersionSet || !_state.WslKernelUpdated)
|
||||||
|
{
|
||||||
|
GoToStep(WizardStep.InstallVirtualEnvironment);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_state.PodmanInstalled)
|
||||||
{
|
{
|
||||||
GoToStep(WizardStep.InstallPodman);
|
GoToStep(WizardStep.InstallPodman);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_state.PodmanInstalled && !_state.PodmanConfigured)
|
if (!_state.PodmanConfigured)
|
||||||
{
|
{
|
||||||
GoToStep(WizardStep.ConfigurePodman);
|
GoToStep(WizardStep.ConfigurePodman);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_state.PodmanConfigured && !_state.AgentInstalled)
|
if (!_state.AgentInstalled)
|
||||||
{
|
{
|
||||||
GoToStep(WizardStep.InstallAgent);
|
GoToStep(WizardStep.InstallAgent);
|
||||||
return;
|
return;
|
||||||
@@ -136,6 +164,19 @@ public partial class MainForm : Form
|
|||||||
GoToStep(WizardStep.Welcome);
|
GoToStep(WizardStep.Welcome);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsFreshInstall()
|
||||||
|
{
|
||||||
|
return _state.Step == WizardStep.Welcome &&
|
||||||
|
!_state.WslFeaturesEnabled &&
|
||||||
|
!_state.WslPackageInstalled &&
|
||||||
|
!_state.VirtualEnvironmentInstalled &&
|
||||||
|
!_state.WslDefaultVersionSet &&
|
||||||
|
!_state.WslKernelUpdated &&
|
||||||
|
!_state.PodmanInstalled &&
|
||||||
|
!_state.PodmanConfigured &&
|
||||||
|
!_state.AgentInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
private void GoBack()
|
private void GoBack()
|
||||||
{
|
{
|
||||||
switch (_currentStep)
|
switch (_currentStep)
|
||||||
@@ -167,10 +208,14 @@ public partial class MainForm : Form
|
|||||||
GoToStep(WizardStep.Prerequisites);
|
GoToStep(WizardStep.Prerequisites);
|
||||||
break;
|
break;
|
||||||
case WizardStep.Prerequisites:
|
case WizardStep.Prerequisites:
|
||||||
if (_lastCheck?.VirtualEnvironmentInstalled == true)
|
if (_lastCheck?.VirtualEnvironmentInstalled != true)
|
||||||
GoToStep(WizardStep.InstallPodman);
|
|
||||||
else
|
|
||||||
GoToStep(WizardStep.InstallVirtualEnvironment);
|
GoToStep(WizardStep.InstallVirtualEnvironment);
|
||||||
|
else if (_lastCheck?.PodmanInstalled != true)
|
||||||
|
GoToStep(WizardStep.InstallPodman);
|
||||||
|
else if (_lastCheck?.PodmanMachineReady != true)
|
||||||
|
GoToStep(WizardStep.ConfigurePodman);
|
||||||
|
else
|
||||||
|
GoToStep(WizardStep.InstallAgent);
|
||||||
break;
|
break;
|
||||||
case WizardStep.InstallVirtualEnvironment:
|
case WizardStep.InstallVirtualEnvironment:
|
||||||
InstallVirtualEnvironment();
|
InstallVirtualEnvironment();
|
||||||
@@ -237,8 +282,9 @@ public partial class MainForm : Form
|
|||||||
|
|
||||||
private void ShowWelcome()
|
private void ShowWelcome()
|
||||||
{
|
{
|
||||||
|
var version = GetVersion();
|
||||||
_titleLabel.Text = "Bienvenue dans l'installateur studioE5";
|
_titleLabel.Text = "Bienvenue dans l'installateur studioE5";
|
||||||
_subtitleLabel.Text = "Cet assistant va vous guider pour installer studioE5 Agent sur votre poste.";
|
_subtitleLabel.Text = $"Version {version}";
|
||||||
|
|
||||||
var label = new Label
|
var label = new Label
|
||||||
{
|
{
|
||||||
@@ -246,7 +292,8 @@ public partial class MainForm : Form
|
|||||||
Top = 20,
|
Top = 20,
|
||||||
Width = _contentPanel.Width,
|
Width = _contentPanel.Width,
|
||||||
Height = 200,
|
Height = 200,
|
||||||
Text = "L'installation se déroule en plusieurs étapes :\n\n" +
|
Text = $"Version {version}\n\n" +
|
||||||
|
"L'installation se déroule en plusieurs étapes :\n\n" +
|
||||||
"1. Vérification des prérequis\n" +
|
"1. Vérification des prérequis\n" +
|
||||||
"2. Installation de l'environnement virtuel (si nécessaire)\n" +
|
"2. Installation de l'environnement virtuel (si nécessaire)\n" +
|
||||||
"3. Installation de Podman\n" +
|
"3. Installation de Podman\n" +
|
||||||
@@ -306,46 +353,174 @@ public partial class MainForm : Form
|
|||||||
_contentPanel.Controls.Add(label);
|
_contentPanel.Controls.Add(label);
|
||||||
|
|
||||||
_btnBack.Visible = true;
|
_btnBack.Visible = true;
|
||||||
_btnNext.Text = result.AllReady ? "Suivant >" : (result.VirtualEnvironmentInstalled ? "Installer Podman" : "Installer l'environnement virtuel");
|
_btnNext.Text = GetPrerequisitesNextButtonText(result);
|
||||||
_btnNext.Enabled = true;
|
_btnNext.Enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetPrerequisitesNextButtonText(PrerequisiteResult result)
|
||||||
|
{
|
||||||
|
if (result.AllReady)
|
||||||
|
return "Suivant >";
|
||||||
|
if (!result.VirtualEnvironmentInstalled)
|
||||||
|
return "Installer l'environnement virtuel";
|
||||||
|
if (!result.PodmanInstalled)
|
||||||
|
return "Installer Podman";
|
||||||
|
if (!result.PodmanMachineReady)
|
||||||
|
return "Configurer Podman";
|
||||||
|
return "Suivant >";
|
||||||
|
}
|
||||||
|
|
||||||
private void ShowInstallVirtualEnvironment()
|
private void ShowInstallVirtualEnvironment()
|
||||||
{
|
{
|
||||||
_titleLabel.Text = "Installation de l'environnement virtuel";
|
_titleLabel.Text = "Installation de l'environnement virtuel";
|
||||||
_subtitleLabel.Text = "L'environnement virtuel permet de faire tourner Podman sur Windows.";
|
_subtitleLabel.Text = "L'environnement virtuel permet de faire tourner Podman sur Windows.";
|
||||||
|
|
||||||
|
var progress = GetWslInstallationProgress();
|
||||||
|
|
||||||
var label = new Label
|
var label = new Label
|
||||||
{
|
{
|
||||||
Left = 0,
|
Left = 0,
|
||||||
Top = 20,
|
Top = 20,
|
||||||
Width = _contentPanel.Width,
|
Width = _contentPanel.Width,
|
||||||
Height = 120,
|
Height = 160,
|
||||||
Text = "L'environnement virtuel (WSL2) n'est pas installé.\n\n" +
|
Text = "L'environnement virtuel (WSL2) n'est pas installé.\n\n" +
|
||||||
"Cliquez sur 'Installer' pour lancer l'installation.\n" +
|
"L'assistant va effectuer les opérations suivantes :\n" +
|
||||||
"Un redémarrage de l'ordinateur sera nécessaire. L'installateur se relancera automatiquement.",
|
"1. Activer les fonctionnalités Windows requises\n" +
|
||||||
|
"2. Mettre à jour le noyau WSL2\n" +
|
||||||
|
"3. Installer WSL2 si nécessaire\n" +
|
||||||
|
"4. Définir WSL2 comme version par défaut\n" +
|
||||||
|
"5. Vérifier que WSL2 est opérationnel\n\n" +
|
||||||
|
"Selon votre version de Windows, certaines étapes peuvent être implicites.\n" +
|
||||||
|
"Un redémarrage de l'ordinateur sera probablement nécessaire. L'installateur se relancera automatiquement.",
|
||||||
Font = new Font("Segoe UI", 10)
|
Font = new Font("Segoe UI", 10)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var progressLabel = new Label
|
||||||
|
{
|
||||||
|
Left = 0,
|
||||||
|
Top = 190,
|
||||||
|
Width = _contentPanel.Width,
|
||||||
|
Height = 80,
|
||||||
|
Text = progress,
|
||||||
|
Font = new Font("Consolas", 9),
|
||||||
|
ForeColor = Color.DarkSlateGray
|
||||||
|
};
|
||||||
|
|
||||||
_contentPanel.Controls.Add(label);
|
_contentPanel.Controls.Add(label);
|
||||||
|
_contentPanel.Controls.Add(progressLabel);
|
||||||
|
|
||||||
_btnBack.Visible = true;
|
_btnBack.Visible = true;
|
||||||
_btnNext.Text = "Installer";
|
_btnNext.Text = progress.Contains("Terminé") ? "Continuer" : "Installer";
|
||||||
_btnNext.Enabled = true;
|
_btnNext.Enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InstallVirtualEnvironment()
|
private string GetWslInstallationProgress()
|
||||||
|
{
|
||||||
|
var wsl2Operational = PrerequisiteChecker.IsWSL2Ready();
|
||||||
|
|
||||||
|
var steps = new List<string>
|
||||||
|
{
|
||||||
|
$"[{( _state.WslFeaturesEnabled ? "x" : " " )}] Fonctionnalités Windows activées",
|
||||||
|
$"[{( _state.WslPackageInstalled ? "x" : " " )}] Package WSL2 installé",
|
||||||
|
$"[{( _state.WslKernelUpdated ? "x" : " " )}] Noyau WSL2 mis à jour",
|
||||||
|
$"[{( _state.VirtualEnvironmentInstalled ? "x" : " " )}] WSL2 installé",
|
||||||
|
$"[{( _state.WslDefaultVersionSet ? "x" : " " )}] WSL2 défini comme version par défaut",
|
||||||
|
$"[{( wsl2Operational ? "x" : " " )}] WSL2 opérationnel"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_state.WslFeaturesEnabled && _state.WslPackageInstalled && _state.WslKernelUpdated && _state.VirtualEnvironmentInstalled && _state.WslDefaultVersionSet && wsl2Operational)
|
||||||
|
steps.Add("\nTerminé. Cliquez sur Continuer.");
|
||||||
|
|
||||||
|
return string.Join("\n", steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void InstallVirtualEnvironment()
|
||||||
{
|
{
|
||||||
_btnNext.Enabled = false;
|
_btnNext.Enabled = false;
|
||||||
_btnBack.Enabled = false;
|
_btnBack.Enabled = false;
|
||||||
|
UseWaitCursor = true;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RunCommand("wsl.exe", "--install --no-distribution", "Installation de l'environnement virtuel en cours...");
|
// Si WSL2 est déjà prêt, on marque toutes les étapes comme faites et on continue.
|
||||||
_state.VirtualEnvironmentInstalled = true;
|
if (PrerequisiteChecker.IsWSL2Ready())
|
||||||
|
{
|
||||||
|
MarkWslInstallationComplete();
|
||||||
|
SetStatus(null);
|
||||||
|
GoToStep(WizardStep.InstallPodman);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Étape 1 : activer les fonctionnalités Windows requises par WSL2.
|
||||||
|
if (!_state.WslFeaturesEnabled)
|
||||||
|
{
|
||||||
|
SetStatus("Activation des fonctionnalités Windows requises... Cela peut prendre plusieurs minutes.");
|
||||||
|
var rebootRequired = await Task.Run(() =>
|
||||||
|
{
|
||||||
|
var r = false;
|
||||||
|
r |= EnableWindowsFeature("Microsoft-Windows-Subsystem-Linux");
|
||||||
|
r |= EnableWindowsFeature("VirtualMachinePlatform");
|
||||||
|
|
||||||
|
// Fonctionnalités Hyper-V optionnelles mais utiles pour WSL2.
|
||||||
|
// Elles peuvent ne pas exister sur Windows Home : on ignore les erreurs.
|
||||||
|
TryEnableWindowsFeature("Microsoft-Hyper-V-All");
|
||||||
|
TryEnableWindowsFeature("Microsoft-Hyper-V");
|
||||||
|
TryEnableWindowsFeature("HypervisorPlatform");
|
||||||
|
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
_state.WslFeaturesEnabled = true;
|
||||||
_state.Save();
|
_state.Save();
|
||||||
|
|
||||||
|
if (rebootRequired)
|
||||||
|
{
|
||||||
|
// Au moins une fonctionnalité nécessite un redémarrage.
|
||||||
RegisterRunOnce();
|
RegisterRunOnce();
|
||||||
GoToStep(WizardStep.RestartRequired);
|
GoToStep(WizardStep.RestartRequired);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Étape 2 : installer le package WSL2 complet offline si disponible.
|
||||||
|
// Le package Microsoft.WSL.msixbundle remplace l'installation via le Store
|
||||||
|
// et inclut le noyau WSL2.
|
||||||
|
if (!_state.WslPackageInstalled)
|
||||||
|
{
|
||||||
|
SetStatus("Installation du package WSL2...");
|
||||||
|
var packageInstalled = await Task.Run(() => TryInstallWslAppxBundle());
|
||||||
|
if (packageInstalled)
|
||||||
|
{
|
||||||
|
_state.WslPackageInstalled = true;
|
||||||
|
_state.WslKernelUpdated = true; // Le bundle inclut le noyau.
|
||||||
|
_state.Save();
|
||||||
|
|
||||||
|
if (CheckWsl2ReadyAndContinue()) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Étape 3 : installer/mettre à jour le noyau WSL2 si ce n'est pas déjà fait.
|
||||||
|
// Fallback sur le MSI noyau bundlé ou sur Windows Update (wsl --update).
|
||||||
|
if (!_state.WslKernelUpdated)
|
||||||
|
{
|
||||||
|
SetStatus("Mise à jour du noyau WSL2...");
|
||||||
|
await Task.Run(() => TryInstallWslKernelMsi());
|
||||||
|
_state.WslKernelUpdated = true;
|
||||||
|
_state.Save();
|
||||||
|
|
||||||
|
if (CheckWsl2ReadyAndContinue()) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Étape 4 : définir WSL2 comme version par défaut.
|
||||||
|
if (!_state.WslDefaultVersionSet)
|
||||||
|
{
|
||||||
|
SetStatus("Définition de WSL2 comme version par défaut...");
|
||||||
|
await Task.Run(() => RunCommand("wsl.exe", "--set-default-version 2", "Définition de WSL2 comme version par défaut"));
|
||||||
|
_state.WslDefaultVersionSet = true;
|
||||||
|
_state.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
SetStatus(null);
|
||||||
|
GoToStep(WizardStep.InstallPodman);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -355,13 +530,122 @@ public partial class MainForm : Form
|
|||||||
{
|
{
|
||||||
_btnNext.Enabled = true;
|
_btnNext.Enabled = true;
|
||||||
_btnBack.Enabled = true;
|
_btnBack.Enabled = true;
|
||||||
|
UseWaitCursor = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MarkWslInstallationComplete()
|
||||||
|
{
|
||||||
|
_state.WslFeaturesEnabled = true;
|
||||||
|
_state.WslPackageInstalled = true;
|
||||||
|
_state.WslKernelUpdated = true;
|
||||||
|
_state.VirtualEnvironmentInstalled = true;
|
||||||
|
_state.WslDefaultVersionSet = true;
|
||||||
|
_state.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckWsl2ReadyAndContinue()
|
||||||
|
{
|
||||||
|
if (!PrerequisiteChecker.IsWSL2Ready())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
MarkWslInstallationComplete();
|
||||||
|
SetStatus(null);
|
||||||
|
GoToStep(WizardStep.InstallPodman);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryInstallWslAppxBundle()
|
||||||
|
{
|
||||||
|
var resourcesDir = Path.Combine(AppContext.BaseDirectory, "Resources");
|
||||||
|
if (!Directory.Exists(resourcesDir))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var bundlePath = Directory.EnumerateFiles(resourcesDir, "Microsoft.WSL_*.msixbundle").FirstOrDefault();
|
||||||
|
if (string.IsNullOrEmpty(bundlePath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SetStatus("Installation du package WSL2 depuis le bundle local...");
|
||||||
|
var script = $"Add-AppxPackage -Path \"{bundlePath}\"";
|
||||||
|
RunCommand(
|
||||||
|
"powershell.exe",
|
||||||
|
$"-ExecutionPolicy Bypass -Command \"{script}\"",
|
||||||
|
"Installation du package WSL2 (Add-AppxPackage)");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryInstallWslKernelMsi()
|
||||||
|
{
|
||||||
|
var kernelMsiPath = Path.Combine(AppContext.BaseDirectory, "Resources", "wsl_update_x64.msi");
|
||||||
|
|
||||||
|
if (File.Exists(kernelMsiPath))
|
||||||
|
{
|
||||||
|
// Mode offline : installation du noyau WSL2 depuis le MSI bundlé.
|
||||||
|
SetStatus("Installation du noyau WSL2 depuis le package local...");
|
||||||
|
RunCommand("msiexec.exe", $"/i \"{kernelMsiPath}\" /qn /norestart", "Installation du noyau WSL2");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode online : mise à jour via Windows Update.
|
||||||
|
SetStatus("Téléchargement et mise à jour du noyau WSL2 (nécessite Internet)...");
|
||||||
|
RunCommand("wsl.exe", "--update", "Mise à jour du noyau WSL2", additionalSuccessCodes: new[] { 3010 });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetStatus(string? text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
_statusLabel.Visible = false;
|
||||||
|
_statusLabel.Text = string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_statusLabel.Text = text;
|
||||||
|
_statusLabel.Visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool EnableWindowsFeature(string featureName)
|
||||||
|
{
|
||||||
|
// 3010 = succès, mais un redémarrage est nécessaire.
|
||||||
|
// L'activation des fonctionnalités Windows peut prendre plusieurs minutes
|
||||||
|
// (voire plus de 10 min sur certaines machines), on allonge donc le timeout.
|
||||||
|
var exitCode = RunCommand(
|
||||||
|
"dism.exe",
|
||||||
|
$"/online /enable-feature /featurename:{featureName} /all /norestart",
|
||||||
|
$"Activation de la fonctionnalité Windows {featureName}",
|
||||||
|
additionalSuccessCodes: new[] { 3010 },
|
||||||
|
timeoutSeconds: 1200);
|
||||||
|
|
||||||
|
return exitCode == 3010;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryEnableWindowsFeature(string featureName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 3010 = succès, mais un redémarrage est nécessaire.
|
||||||
|
var exitCode = RunCommand(
|
||||||
|
"dism.exe",
|
||||||
|
$"/online /enable-feature /featurename:{featureName} /all /norestart",
|
||||||
|
$"Activation de la fonctionnalité Windows {featureName}",
|
||||||
|
additionalSuccessCodes: new[] { 3010 },
|
||||||
|
timeoutSeconds: 1200);
|
||||||
|
|
||||||
|
// Si la fonctionnalité a été activée avec succès et nécessite un redémarrage,
|
||||||
|
// on ne force pas ici : les fonctionnalités obligatoires gèrent déjà le redémarrage.
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Fonctionnalité optionnelle ou indisponible sur cette édition de Windows : on ignore.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowRestartRequired()
|
private void ShowRestartRequired()
|
||||||
{
|
{
|
||||||
_titleLabel.Text = "Redémarrage nécessaire";
|
_titleLabel.Text = "Redémarrage nécessaire";
|
||||||
_subtitleLabel.Text = "L'environnement virtuel a été installé.";
|
_subtitleLabel.Text = "Les fonctionnalités Windows ont été activées.";
|
||||||
|
|
||||||
var label = new Label
|
var label = new Label
|
||||||
{
|
{
|
||||||
@@ -369,8 +653,8 @@ public partial class MainForm : Form
|
|||||||
Top = 20,
|
Top = 20,
|
||||||
Width = _contentPanel.Width,
|
Width = _contentPanel.Width,
|
||||||
Height = 120,
|
Height = 120,
|
||||||
Text = "Veuillez redémarrer votre ordinateur pour finaliser l'installation de l'environnement virtuel.\n\n" +
|
Text = "Veuillez redémarrer votre ordinateur pour finaliser l'activation des fonctionnalités Windows.\n\n" +
|
||||||
"L'installateur se relancera automatiquement après le redémarrage pour continuer l'installation.",
|
"L'installateur se relancera automatiquement après le redémarrage pour continuer l'installation de WSL2.",
|
||||||
Font = new Font("Segoe UI", 10)
|
Font = new Font("Segoe UI", 10)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -405,10 +689,11 @@ public partial class MainForm : Form
|
|||||||
_btnNext.Enabled = true;
|
_btnNext.Enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InstallPodman()
|
private async void InstallPodman()
|
||||||
{
|
{
|
||||||
_btnNext.Enabled = false;
|
_btnNext.Enabled = false;
|
||||||
_btnBack.Enabled = false;
|
_btnBack.Enabled = false;
|
||||||
|
UseWaitCursor = true;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -416,9 +701,17 @@ public partial class MainForm : Form
|
|||||||
if (!File.Exists(msiPath))
|
if (!File.Exists(msiPath))
|
||||||
throw new FileNotFoundException("Le fichier podman-installer-windows-amd64.msi est introuvable. Vérifiez qu'il est bien inclus dans le package.");
|
throw new FileNotFoundException("Le fichier podman-installer-windows-amd64.msi est introuvable. Vérifiez qu'il est bien inclus dans le package.");
|
||||||
|
|
||||||
RunCommand("msiexec.exe", $"/i \"{msiPath}\" /qn /norestart", "Installation de Podman en cours...");
|
SetStatus("Installation de Podman en cours...");
|
||||||
|
await Task.Run(() => RunCommand("msiexec.exe", $"/i \"{msiPath}\" /qn /norestart", "Installation de Podman en cours..."));
|
||||||
_state.PodmanInstalled = true;
|
_state.PodmanInstalled = true;
|
||||||
_state.Save();
|
_state.Save();
|
||||||
|
|
||||||
|
// Installation de Docker Compose standalone pour que l'agent studioE5
|
||||||
|
// puisse utiliser la commande `docker-compose` avec Podman.
|
||||||
|
SetStatus("Installation de Docker Compose...");
|
||||||
|
await Task.Run(() => InstallDockerCompose());
|
||||||
|
|
||||||
|
SetStatus(null);
|
||||||
GoToStep(WizardStep.ConfigurePodman);
|
GoToStep(WizardStep.ConfigurePodman);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -429,6 +722,91 @@ public partial class MainForm : Form
|
|||||||
{
|
{
|
||||||
_btnNext.Enabled = true;
|
_btnNext.Enabled = true;
|
||||||
_btnBack.Enabled = true;
|
_btnBack.Enabled = true;
|
||||||
|
UseWaitCursor = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InstallDockerCompose()
|
||||||
|
{
|
||||||
|
var composeSourcePath = Path.Combine(AppContext.BaseDirectory, "Resources", "docker-compose-windows-x86_64.exe");
|
||||||
|
if (!File.Exists(composeSourcePath))
|
||||||
|
{
|
||||||
|
// Docker Compose n'est pas bundlé : on ignore silencieusement.
|
||||||
|
// L'utilisateur devra l'installer manuellement.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cible : même dossier que podman.exe, qui est déjà dans le PATH.
|
||||||
|
var podmanPath = PrerequisiteChecker.GetPodmanExePath();
|
||||||
|
if (string.IsNullOrEmpty(podmanPath))
|
||||||
|
{
|
||||||
|
// Fallback : dossier dédié studioE5-agent.
|
||||||
|
var fallbackDir = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
|
||||||
|
"studioE5-agent",
|
||||||
|
"bin");
|
||||||
|
Directory.CreateDirectory(fallbackDir);
|
||||||
|
var fallbackPath = Path.Combine(fallbackDir, "docker-compose.exe");
|
||||||
|
File.Copy(composeSourcePath, fallbackPath, overwrite: true);
|
||||||
|
AddToPath(fallbackDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var podmanDir = Path.GetDirectoryName(podmanPath);
|
||||||
|
if (!string.IsNullOrEmpty(podmanDir))
|
||||||
|
{
|
||||||
|
var targetPath = Path.Combine(podmanDir, "docker-compose.exe");
|
||||||
|
File.Copy(composeSourcePath, targetPath, overwrite: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddToPath(string directory)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const string keyPath = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
|
||||||
|
using var key = Registry.LocalMachine.OpenSubKey(keyPath, true);
|
||||||
|
if (key == null) return;
|
||||||
|
|
||||||
|
var currentPath = key.GetValue("Path", "", RegistryValueOptions.DoNotExpandEnvironmentNames) as string ?? "";
|
||||||
|
if (currentPath.Contains(directory, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var newPath = currentPath.TrimEnd(';') + ";" + directory;
|
||||||
|
key.SetValue("Path", newPath, RegistryValueKind.ExpandString);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureWslConfig()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
var wslConfigPath = Path.Combine(userProfile, ".wslconfig");
|
||||||
|
|
||||||
|
const string desiredContent = "[wsl2]\nmemory=8GB\nprocessors=4\n";
|
||||||
|
var currentContent = File.Exists(wslConfigPath) ? File.ReadAllText(wslConfigPath) : "";
|
||||||
|
|
||||||
|
var needsUpdate = !currentContent.Contains("memory=8GB", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
!currentContent.Contains("processors=4", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (needsUpdate)
|
||||||
|
{
|
||||||
|
File.WriteAllText(wslConfigPath, desiredContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appliquer la configuration en arrêtant WSL.
|
||||||
|
// La nouvelle configuration sera prise en compte au prochain démarrage.
|
||||||
|
RunCommand("wsl.exe", "--shutdown", "Arrêt de WSL2 pour appliquer la configuration", timeoutSeconds: 60);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// On ignore les erreurs : si WSL n'est pas encore installé, la configuration
|
||||||
|
// sera prise en compte au premier démarrage.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,17 +833,40 @@ public partial class MainForm : Form
|
|||||||
_btnNext.Enabled = true;
|
_btnNext.Enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConfigurePodman()
|
private async void ConfigurePodman()
|
||||||
{
|
{
|
||||||
_btnNext.Enabled = false;
|
_btnNext.Enabled = false;
|
||||||
_btnBack.Enabled = false;
|
_btnBack.Enabled = false;
|
||||||
|
UseWaitCursor = true;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RunCommand("podman.exe", "machine init", "Initialisation de la machine Podman...");
|
var podmanPath = PrerequisiteChecker.GetPodmanExePath();
|
||||||
RunCommand("podman.exe", "machine start", "Démarrage de la machine Podman...");
|
if (string.IsNullOrEmpty(podmanPath))
|
||||||
|
throw new FileNotFoundException("podman.exe est introuvable. Vérifiez que Podman est bien installé.");
|
||||||
|
|
||||||
|
// Configurer WSL pour allouer suffisamment de ressources à la machine Podman.
|
||||||
|
// Par défaut WSL2 n'utilise que 2 Go de RAM, ce qui est insuffisant pour Prestashop.
|
||||||
|
SetStatus("Configuration des ressources WSL2...");
|
||||||
|
await Task.Run(() => EnsureWslConfig());
|
||||||
|
|
||||||
|
var machineImagePath = GetPodmanMachineImagePath();
|
||||||
|
if (!string.IsNullOrEmpty(machineImagePath))
|
||||||
|
{
|
||||||
|
SetStatus("Initialisation de la machine Podman depuis l'image locale...");
|
||||||
|
await Task.Run(() => RunCommand(podmanPath, $"machine init --image \"{machineImagePath}\"", "Initialisation de la machine Podman (offline)"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetStatus("Initialisation de la machine Podman (téléchargement depuis Internet)...");
|
||||||
|
await Task.Run(() => RunCommand(podmanPath, "machine init", "Initialisation de la machine Podman"));
|
||||||
|
}
|
||||||
|
|
||||||
|
SetStatus("Démarrage de la machine Podman...");
|
||||||
|
await Task.Run(() => RunCommand(podmanPath, "machine start", "Démarrage de la machine Podman..."));
|
||||||
_state.PodmanConfigured = true;
|
_state.PodmanConfigured = true;
|
||||||
_state.Save();
|
_state.Save();
|
||||||
|
SetStatus(null);
|
||||||
GoToStep(WizardStep.InstallAgent);
|
GoToStep(WizardStep.InstallAgent);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -476,9 +877,19 @@ public partial class MainForm : Form
|
|||||||
{
|
{
|
||||||
_btnNext.Enabled = true;
|
_btnNext.Enabled = true;
|
||||||
_btnBack.Enabled = true;
|
_btnBack.Enabled = true;
|
||||||
|
UseWaitCursor = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? GetPodmanMachineImagePath()
|
||||||
|
{
|
||||||
|
var resourcesDir = Path.Combine(AppContext.BaseDirectory, "Resources");
|
||||||
|
if (!Directory.Exists(resourcesDir))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return Directory.EnumerateFiles(resourcesDir, "podman-machine.*.wsl.tar.zst").FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
private void ShowInstallAgent()
|
private void ShowInstallAgent()
|
||||||
{
|
{
|
||||||
_titleLabel.Text = "Installation de studioE5 Agent";
|
_titleLabel.Text = "Installation de studioE5 Agent";
|
||||||
@@ -631,26 +1042,62 @@ public partial class MainForm : Form
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunCommand(string fileName, string arguments, string description)
|
private int RunCommand(string fileName, string arguments, string description, int[]? additionalSuccessCodes = null, int timeoutSeconds = 300)
|
||||||
{
|
{
|
||||||
var psi = new ProcessStartInfo(fileName, arguments)
|
var psi = new ProcessStartInfo(fileName, arguments)
|
||||||
{
|
{
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true
|
RedirectStandardError = true,
|
||||||
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
|
StandardErrorEncoding = Encoding.UTF8
|
||||||
};
|
};
|
||||||
|
|
||||||
using var process = Process.Start(psi);
|
using var process = Process.Start(psi);
|
||||||
if (process == null)
|
if (process == null)
|
||||||
throw new InvalidOperationException($"Impossible de démarrer {fileName}");
|
throw new InvalidOperationException($"Impossible de démarrer {fileName}");
|
||||||
|
|
||||||
process.WaitForExit();
|
// Lire stdout/stderr en parallèle pour éviter le blocage du processus
|
||||||
if (process.ExitCode != 0)
|
// lorsque ses buffers de sortie sont pleins.
|
||||||
|
var stdoutTask = Task.Run(() => process.StandardOutput.ReadToEnd());
|
||||||
|
var stderrTask = Task.Run(() => process.StandardError.ReadToEnd());
|
||||||
|
|
||||||
|
if (!process.WaitForExit(timeoutSeconds * 1000))
|
||||||
{
|
{
|
||||||
var error = process.StandardError.ReadToEnd();
|
try { process.Kill(); } catch { /* ignored */ }
|
||||||
throw new InvalidOperationException($"{description} a échoué (code {process.ExitCode}) : {error}");
|
throw new TimeoutException($"{description} a dépassé le délai de {timeoutSeconds} secondes.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(stdoutTask, stderrTask);
|
||||||
|
|
||||||
|
var exitCode = process.ExitCode;
|
||||||
|
if (exitCode != 0 && !(additionalSuccessCodes?.Contains(exitCode) ?? false))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{description} a échoué (code {exitCode}) : {stderrTask.Result}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetVersion()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var versionPath = Path.Combine(AppContext.BaseDirectory, "VERSION");
|
||||||
|
if (File.Exists(versionPath))
|
||||||
|
{
|
||||||
|
var version = File.ReadAllText(versionPath).Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(version))
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterRunOnce()
|
private void RegisterRunOnce()
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Management;
|
using System.Management;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace StudioE5.SetupWizard;
|
namespace StudioE5.SetupWizard;
|
||||||
|
|
||||||
@@ -19,13 +22,20 @@ public static class PrerequisiteChecker
|
|||||||
{
|
{
|
||||||
public static PrerequisiteResult Check()
|
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(
|
return new PrerequisiteResult(
|
||||||
WindowsCompatible: IsWindowsCompatible(),
|
WindowsCompatible: IsWindowsCompatible(),
|
||||||
RamMB: GetTotalPhysicalMemoryMB(),
|
RamMB: GetTotalPhysicalMemoryMB(),
|
||||||
FreeDiskMB: GetFreeDiskSpaceMB("C:\\"),
|
FreeDiskMB: GetFreeDiskSpaceMB("C:\\"),
|
||||||
VirtualEnvironmentInstalled: IsWSLInstalled(),
|
VirtualEnvironmentInstalled: virtualEnvironmentInstalled,
|
||||||
PodmanInstalled: IsPodmanInstalled(),
|
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()
|
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
|
try
|
||||||
{
|
{
|
||||||
@@ -79,12 +225,23 @@ public static class PrerequisiteChecker
|
|||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true
|
RedirectStandardError = true,
|
||||||
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
|
StandardErrorEncoding = Encoding.UTF8
|
||||||
};
|
};
|
||||||
using var process = Process.Start(psi);
|
using var process = Process.Start(psi);
|
||||||
if (process == null) return false;
|
if (process == null) return false;
|
||||||
|
var output = process.StandardOutput.ReadToEnd();
|
||||||
|
var error = process.StandardError.ReadToEnd();
|
||||||
process.WaitForExit();
|
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
|
catch
|
||||||
{
|
{
|
||||||
@@ -92,16 +249,110 @@ public static class PrerequisiteChecker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsPodmanInstalled()
|
private static bool WslListIndicatesWsl2()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var psi = new ProcessStartInfo("podman.exe", "--version")
|
var psi = new ProcessStartInfo("wsl.exe", "--list --verbose")
|
||||||
{
|
{
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
RedirectStandardOutput = 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);
|
using var process = Process.Start(psi);
|
||||||
if (process == null) return false;
|
if (process == null) return false;
|
||||||
@@ -116,14 +367,20 @@ public static class PrerequisiteChecker
|
|||||||
|
|
||||||
private static bool IsPodmanMachineReady()
|
private static bool IsPodmanMachineReady()
|
||||||
{
|
{
|
||||||
|
var podmanPath = GetPodmanExePath();
|
||||||
|
if (string.IsNullOrEmpty(podmanPath))
|
||||||
|
return false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var psi = new ProcessStartInfo("podman.exe", "machine list --format json")
|
var psi = new ProcessStartInfo(podmanPath, "machine list --format json")
|
||||||
{
|
{
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true
|
RedirectStandardError = true,
|
||||||
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
|
StandardErrorEncoding = Encoding.UTF8
|
||||||
};
|
};
|
||||||
using var process = Process.Start(psi);
|
using var process = Process.Start(psi);
|
||||||
if (process == null) return false;
|
if (process == null) return false;
|
||||||
|
|||||||
@@ -29,9 +29,14 @@ setup-wizard/
|
|||||||
├── MainForm.cs
|
├── MainForm.cs
|
||||||
├── InstallerState.cs
|
├── InstallerState.cs
|
||||||
├── PrerequisiteChecker.cs
|
├── PrerequisiteChecker.cs
|
||||||
|
├── app.manifest
|
||||||
└── Resources/
|
└── Resources/
|
||||||
├── podman-installer-windows-amd64.msi # MSI officiel Podman pour Windows
|
├── podman-installer-windows-amd64.msi # MSI officiel Podman pour Windows
|
||||||
└── studioE5-agent-setup.exe # Package Inno Setup de l'agent
|
├── studioE5-agent-setup.exe # Package Inno Setup de l'agent
|
||||||
|
├── Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle # Package WSL2 complet (offline)
|
||||||
|
├── podman-machine.x86_64.wsl.tar.zst # Image Podman machine pour WSL (offline)
|
||||||
|
├── docker-compose-windows-x86_64.exe # Docker Compose standalone (offline)
|
||||||
|
└── wsl_update_x64.msi # Noyau WSL2 (optionnel, fallback)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
@@ -58,9 +63,25 @@ bin\Release\net8.0-windows\win-x64\publish\StudioE5-SetupWizard.exe
|
|||||||
|
|
||||||
1. Télécharger le MSI Podman Windows :
|
1. Télécharger le MSI Podman Windows :
|
||||||
<https://github.com/containers/podman/releases>
|
<https://github.com/containers/podman/releases>
|
||||||
2. Le renommer en `podman-installer-windows-amd64.msi` et le placer dans `Resources/`.
|
Le renommer en `podman-installer-windows-amd64.msi` et le placer dans `Resources/`.
|
||||||
3. Générer le package Inno Setup de l’agent (`studioE5-agent-setup.exe`) et le placer dans `Resources/`.
|
2. Générer le package Inno Setup de l’agent (`studioE5-agent-setup.exe`) et le placer dans `Resources/`.
|
||||||
4. Builder et publier le wizard.
|
3. Télécharger le package WSL2 complet (offline) :
|
||||||
|
<https://github.com/microsoft/WSL/releases>
|
||||||
|
Par exemple : `Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle`.
|
||||||
|
Le placer dans `Resources/`.
|
||||||
|
4. Télécharger l’image Podman machine pour WSL (offline) :
|
||||||
|
<https://github.com/containers/podman-machine-os/releases>
|
||||||
|
Par exemple : `podman-machine.x86_64.wsl.tar.zst`.
|
||||||
|
Le placer dans `Resources/`.
|
||||||
|
5. Télécharger Docker Compose standalone (offline) :
|
||||||
|
<https://github.com/docker/compose/releases>
|
||||||
|
Par exemple : `docker-compose-windows-x86_64.exe`.
|
||||||
|
Le placer dans `Resources/`.
|
||||||
|
6. *(Optionnel, fallback)* Télécharger le noyau WSL2 :
|
||||||
|
<https://github.com/microsoft/WSL/releases>
|
||||||
|
Par exemple : `wsl.2.7.10.0.x64.msi`, à renommer en `wsl_update_x64.msi`.
|
||||||
|
Le placer dans `Resources/`.
|
||||||
|
6. Builder et publier le wizard.
|
||||||
|
|
||||||
## Lancement
|
## Lancement
|
||||||
|
|
||||||
@@ -80,4 +101,5 @@ bin\Release\net8.0-windows\win-x64\publish\StudioE5-SetupWizard.exe
|
|||||||
|
|
||||||
- Le wizard doit être exécuté **en administrateur**.
|
- Le wizard doit être exécuté **en administrateur**.
|
||||||
- L’installation de WSL2 nécessite un **redémarrage** de l’ordinateur. Le wizard s’enregistre dans `RunOnce` pour se relancer automatiquement.
|
- L’installation de WSL2 nécessite un **redémarrage** de l’ordinateur. Le wizard s’enregistre dans `RunOnce` pour se relancer automatiquement.
|
||||||
|
- Le wizard configure WSL2 avec **8 Go de RAM et 4 CPU** via le fichier `.wslconfig` de l’utilisateur.
|
||||||
- Le MSI Podman doit correspondre à l’architecture `x64`.
|
- Le MSI Podman doit correspondre à l’architecture `x64`.
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Feuille de route — Installateur studioE5 Agent
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Fournir un **installateur professionnel Windows** pour studioE5 Agent, guidé pas à pas, qui gère les prérequis (WSL2 / Podman) et propose une désinstallation complète.
|
||||||
|
|
||||||
|
## Architecture choisie
|
||||||
|
|
||||||
|
- **Wizard C# Windows Forms (.NET 8)** : `setup-wizard/`
|
||||||
|
- Détecte les prérequis.
|
||||||
|
- Installe WSL2 si besoin (avec reprise après redémarrage via `RunOnce`).
|
||||||
|
- Installe Podman depuis le MSI officiel.
|
||||||
|
- Initialise et démarre la machine Podman.
|
||||||
|
- Lance le package Inno Setup de studioE5 Agent.
|
||||||
|
- Mode désinstallation via `/uninstall`.
|
||||||
|
- **Package agent (Inno Setup)** : `studioE5-agent.iss`
|
||||||
|
- Installe `studioE5-agent.exe` + binaires Tailscale.
|
||||||
|
- Crée les raccourcis.
|
||||||
|
- Gère la désinstallation.
|
||||||
|
|
||||||
|
## État actuel
|
||||||
|
|
||||||
|
### ✅ Réalisé
|
||||||
|
|
||||||
|
- Wizard C# avec 7 étapes guidées.
|
||||||
|
- Détection des prérequis : Windows, RAM, disque, WSL2, Podman.
|
||||||
|
- Installation WSL2 en plusieurs étapes contrôlées avec suivi visuel :
|
||||||
|
1. Activation des fonctionnalités Windows (`Microsoft-Windows-Subsystem-Linux` et `VirtualMachinePlatform`) avec gestion du code 3010 (redémarrage nécessaire).
|
||||||
|
2. Installation du package WSL2 complet depuis le bundle Microsoft Store offline (`Microsoft.WSL_*.msixbundle`) via `Add-AppxPackage`.
|
||||||
|
3. Mise à jour du noyau WSL2 depuis le MSI bundlé (`wsl_update_x64.msi`) ou, en dernier recours, via `wsl --update`.
|
||||||
|
4. Définition de WSL2 comme version par défaut (`wsl --set-default-version 2`).
|
||||||
|
5. L’étape `wsl --install --no-distribution` n’est plus utilisée : l’installation est entièrement offline grâce au bundle.
|
||||||
|
6. Conversion des distributions WSL1 existantes vers WSL2 (`wsl --set-version <nom> 2`) si nécessaire.
|
||||||
|
- Reprise automatique après redémarrage grâce à `RunOnce` (y compris redémarrage post-installation WSL2).
|
||||||
|
- Versioning indépendant du wizard (fichier `setup-wizard/VERSION`, affiché dans la fenêtre et la page d’accueil).
|
||||||
|
- Amélioration de l’interface : fenêtre agrandie à 800×600, `AutoScaleMode = Dpi`, meilleur rendu sur petits écrans (11 pouces).
|
||||||
|
- `RunCommand` capture désormais stdout + stderr pour des diagnostics et fallbacks plus fiables.
|
||||||
|
- Détection des prérequis vérifie que WSL2 (et non WSL1) est installé via `wsl --status`.
|
||||||
|
- Installation Podman via MSI bundlé.
|
||||||
|
- Configuration Podman (`machine init` + `machine start`), avec initialisation offline possible depuis une image locale (`podman-machine.*.wsl.tar.zst`).
|
||||||
|
- Lancement du package Inno Setup agent.
|
||||||
|
- Mode désinstallation complet.
|
||||||
|
- Script Inno Setup de base pour l’agent.
|
||||||
|
|
||||||
|
### 🔄 En cours / À tester
|
||||||
|
|
||||||
|
- Compilation et test du wizard sur Windows.
|
||||||
|
- Packaging final (wizard + MSI Podman + setup agent) en un seul dossier distribuable.
|
||||||
|
|
||||||
|
### ⏳ À venir
|
||||||
|
|
||||||
|
- Signature de l’exécutable pour éviter les alertes SmartScreen.
|
||||||
|
- Support macOS et Linux.
|
||||||
|
- Installateur silencieux possible pour déploiement GPO.
|
||||||
|
|
||||||
|
## Build du wizard
|
||||||
|
|
||||||
|
### Prérequis
|
||||||
|
|
||||||
|
- Windows 10/11
|
||||||
|
- .NET 8 SDK
|
||||||
|
- Inno Setup 6 (pour générer `studioE5-agent-setup.exe`)
|
||||||
|
|
||||||
|
### Fichiers à placer
|
||||||
|
|
||||||
|
Dans `setup-wizard/Resources/` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
podman-installer-windows-amd64.msi
|
||||||
|
studioE5-agent-setup.exe
|
||||||
|
Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle # package WSL2 complet (offline)
|
||||||
|
podman-machine.x86_64.wsl.tar.zst # image Podman machine pour WSL (offline)
|
||||||
|
docker-compose-windows-x86_64.exe # Docker Compose standalone (offline)
|
||||||
|
wsl_update_x64.msi # optionnel, fallback noyau WSL2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commande
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd setup-wizard
|
||||||
|
dotnet publish -c Release -r win-x64 --self-contained true /p:PublishSingleFile=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sortie
|
||||||
|
|
||||||
|
```text
|
||||||
|
setup-wizard\bin\Release\net8.0-windows\win-x64\publish\StudioE5-SetupWizard.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build du package agent (Inno Setup)
|
||||||
|
|
||||||
|
Structure attendue :
|
||||||
|
|
||||||
|
```text
|
||||||
|
agent/
|
||||||
|
├── studioE5-agent.exe
|
||||||
|
├── tailscale-bin/
|
||||||
|
│ └── windows/
|
||||||
|
│ ├── tailscale.exe
|
||||||
|
│ ├── tailscaled.exe
|
||||||
|
│ └── wintun.dll
|
||||||
|
└── installer/
|
||||||
|
└── studioE5-agent.iss
|
||||||
|
```
|
||||||
|
|
||||||
|
Ouvrir `studioE5-agent.iss` avec Inno Setup Compiler et compiler (`Ctrl+F9`).
|
||||||
|
|
||||||
|
Le fichier généré se trouve dans `installer-output/`.
|
||||||
|
|
||||||
|
## Notes importantes
|
||||||
|
|
||||||
|
- Le wizard doit être exécuté **en administrateur**.
|
||||||
|
- L’installation de WSL2 nécessite un **redémarrage** de l’ordinateur après l’activation des fonctionnalités Windows.
|
||||||
|
- L’installation de WSL2 est découpée en étapes et chaque étape est enregistrée pour permettre la reprise après redémarrage.
|
||||||
|
- Podman nécessite WSL2 : le wizard vérifie donc que la version par défaut de WSL est 2.
|
||||||
|
- Le MSI Podman officiel pour Windows est `podman-installer-windows-amd64.msi`.
|
||||||
|
- Le bundle WSL2 offline est disponible sur <https://github.com/microsoft/WSL/releases> : `Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle`.
|
||||||
|
- L’image Podman machine offline est disponible sur <https://github.com/containers/podman-machine-os/releases> : `podman-machine.x86_64.wsl.tar.zst`.
|
||||||
|
- Docker Compose standalone est disponible sur <https://github.com/docker/compose/releases> : `docker-compose-windows-x86_64.exe`.
|
||||||
|
- Le wizard crée automatiquement un fichier `.wslconfig` allouant **8 Go de RAM et 4 CPU** à WSL2, ce qui est nécessaire pour des applications lourdes comme PrestaShop.
|
||||||
|
- Pour la désinstallation, le MSI Podman doit être présent dans `Resources/`.
|
||||||
|
|
||||||
|
## Liens utiles
|
||||||
|
|
||||||
|
- Releases Podman : <https://github.com/containers/podman/releases>
|
||||||
|
- Inno Setup : <https://jrsoftware.org/isdl.php>
|
||||||
|
- .NET 8 SDK : <https://dotnet.microsoft.com/download/dotnet/8.0>
|
||||||
@@ -6,8 +6,10 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<SatelliteResourceLanguages>fr</SatelliteResourceLanguages>
|
||||||
<RootNamespace>StudioE5.SetupWizard</RootNamespace>
|
<RootNamespace>StudioE5.SetupWizard</RootNamespace>
|
||||||
<AssemblyName>StudioE5-SetupWizard</AssemblyName>
|
<AssemblyName>StudioE5-SetupWizard</AssemblyName>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -15,10 +17,16 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Resources\podman-installer-windows-amd64.msi">
|
<!-- Fichier de version affiché dans le wizard. -->
|
||||||
|
<Content Include="VERSION">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="Resources\studioE5-agent-setup.exe">
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- Tous les fichiers placés dans Resources/ sont copiés dans le répertoire de sortie. -->
|
||||||
|
<!-- Attendus : MSI Podman, setup agent, bundle WSL, image Podman machine, MSI noyau WSL (optionnel). -->
|
||||||
|
<Content Include="Resources\*">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
0.1.1
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="StudioE5.SetupWizard.app"/>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<!-- Force l'exécution en tant qu'administrateur -->
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
</assembly>
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
#Requires -RunAsAdministrator
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Installe ou répare WSL2 de manière fiable.
|
||||||
|
.DESCRIPTION
|
||||||
|
Ce script :
|
||||||
|
1. Vérifie si WSL2 est déjà prêt.
|
||||||
|
2. Active les fonctionnalités Windows nécessaires.
|
||||||
|
3. Définit WSL2 comme version par défaut.
|
||||||
|
4. Met à jour le noyau WSL2.
|
||||||
|
5. Installe WSL sans distribution si possible.
|
||||||
|
Un redémarrage peut être nécessaire après l’activation des fonctionnalités.
|
||||||
|
#>
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Test-Wsl2Ready {
|
||||||
|
try {
|
||||||
|
$output = & wsl.exe --status 2>&1
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
Write-Host "[Test] wsl --status exit code: $exitCode" -ForegroundColor Cyan
|
||||||
|
if ($output) {
|
||||||
|
Write-Host "[Test] wsl --status output:" -ForegroundColor Cyan
|
||||||
|
$output | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exitCode -eq 0 -or ($output -match "Version par défaut\s*:\s*2") -or ($output -match "Default Version\s*:\s*2")) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "[Test] wsl --status a échoué : $_" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Enable-WindowsFeatureIfNeeded {
|
||||||
|
param([string]$FeatureName)
|
||||||
|
|
||||||
|
Write-Host "[Feature] Activation de $FeatureName..." -ForegroundColor Cyan
|
||||||
|
$result = & dism.exe /online /enable-feature /featurename:$FeatureName /all /norestart 2>&1
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
$result | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||||
|
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "[Feature] $FeatureName activé (pas de redémarrage nécessaire)." -ForegroundColor Green
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
elseif ($exitCode -eq 3010) {
|
||||||
|
Write-Host "[Feature] $FeatureName activé, mais un redémarrage est nécessaire (code 3010)." -ForegroundColor Yellow
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw "Échec de l'activation de $FeatureName (code $exitCode)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-Wsl2 {
|
||||||
|
Write-Host "[WSL] Tentative d'installation sans distribution..." -ForegroundColor Cyan
|
||||||
|
try {
|
||||||
|
& wsl.exe --install --no-distribution 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "wsl --install --no-distribution a retourné le code $LASTEXITCODE" }
|
||||||
|
Write-Host "[WSL] Installation sans distribution réussie." -ForegroundColor Green
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "[WSL] Option --no-distribution non supportée ou échec : $_" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[WSL] Fallback : installation classique de WSL..." -ForegroundColor Cyan
|
||||||
|
& wsl.exe --install 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "wsl --install a retourné le code $LASTEXITCODE" }
|
||||||
|
Write-Host "[WSL] Installation classique réussie." -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
# === Début du script ===
|
||||||
|
|
||||||
|
Write-Host "=== Installation / réparation WSL2 ===" -ForegroundColor Green
|
||||||
|
|
||||||
|
if (Test-Wsl2Ready) {
|
||||||
|
Write-Host "WSL2 est déjà prêt. Rien à faire." -ForegroundColor Green
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "WSL2 n'est pas détecté. Lancement de l'installation..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$rebootNeeded = $false
|
||||||
|
$rebootNeeded = (Enable-WindowsFeatureIfNeeded -FeatureName "Microsoft-Windows-Subsystem-Linux") -or $rebootNeeded
|
||||||
|
$rebootNeeded = (Enable-WindowsFeatureIfNeeded -FeatureName "VirtualMachinePlatform") -or $rebootNeeded
|
||||||
|
|
||||||
|
if ($rebootNeeded) {
|
||||||
|
Write-Host "`nUn redémarrage est nécessaire pour activer les fonctionnalités Windows." -ForegroundColor Yellow
|
||||||
|
Write-Host "Après le redémarrage, relance ce script pour terminer l'installation de WSL2." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$response = Read-Host "Redémarrer maintenant ? (O/N)"
|
||||||
|
if ($response -eq "O" -or $response -eq "o") {
|
||||||
|
Restart-Computer -Force
|
||||||
|
}
|
||||||
|
exit 3010
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[WSL] Définition de WSL2 comme version par défaut..." -ForegroundColor Cyan
|
||||||
|
& wsl.exe --set-default-version 2 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "wsl --set-default-version 2 a échoué (code $LASTEXITCODE)." }
|
||||||
|
|
||||||
|
Write-Host "[WSL] Mise à jour du noyau WSL2..." -ForegroundColor Cyan
|
||||||
|
& wsl.exe --update 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||||
|
# 3010 = succès mais redémarrage possible
|
||||||
|
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 3010) { throw "wsl --update a échoué (code $LASTEXITCODE)." }
|
||||||
|
|
||||||
|
Install-Wsl2
|
||||||
|
|
||||||
|
if (Test-Wsl2Ready) {
|
||||||
|
Write-Host "`nWSL2 est maintenant prêt." -ForegroundColor Green
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host "`nWSL2 ne semble toujours pas prêt. Essayez de redémarrer et de relancer le script." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -30,9 +30,9 @@ docker build -t edubox-prestashop:9 .
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker tag edubox-prestashop:9 \
|
docker tag edubox-prestashop:9 \
|
||||||
151.80.60.98:3001/yacine/edubox/edubox-prestashop:9-edubox-9
|
gitea.alfrednobel.edudeploy.com/yacine/edubox/edubox-prestashop:9-edubox-9
|
||||||
docker push \
|
docker push \
|
||||||
151.80.60.98:3001/yacine/edubox/edubox-prestashop:9-edubox-9
|
gitea.alfrednobel.edudeploy.com/yacine/edubox/edubox-prestashop:9-edubox-9
|
||||||
```
|
```
|
||||||
|
|
||||||
## Patches appliqués
|
## Patches appliqués
|
||||||
@@ -75,7 +75,7 @@ Le template PrestaShop 9 dans `server/prisma/seed.ts` utilise cette image :
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
app:
|
app:
|
||||||
image: 151.80.60.98:3001/yacine/edubox/edubox-prestashop:9-edubox-8
|
image: gitea.alfrednobel.edudeploy.com/yacine/edubox/edubox-prestashop:9-edubox-8
|
||||||
```
|
```
|
||||||
|
|
||||||
## Mise à jour vers une nouvelle version de PrestaShop
|
## Mise à jour vers une nouvelle version de PrestaShop
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ async function main() {
|
|||||||
{
|
{
|
||||||
name: "PrestaShop 9 vierge (edubox)",
|
name: "PrestaShop 9 vierge (edubox)",
|
||||||
type: "prestashop",
|
type: "prestashop",
|
||||||
dockerImage: "151.80.60.98:3001/yacine/edubox/edubox-prestashop:9-edubox-9",
|
dockerImage: "gitea.alfrednobel.edudeploy.com/yacine/edubox/edubox-prestashop:9-edubox-9",
|
||||||
dbImage: "mariadb:10.11",
|
dbImage: "mariadb:10.11",
|
||||||
dbName: "prestashop",
|
dbName: "prestashop",
|
||||||
dbUser: "prestashop",
|
dbUser: "prestashop",
|
||||||
|
|||||||
Reference in New Issue
Block a user