diff --git a/installer/HermesHubSetup.cs b/installer/HermesHubSetup.cs index f9a3fd5..7d11d8a 100644 --- a/installer/HermesHubSetup.cs +++ b/installer/HermesHubSetup.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; +using System.Reflection; +using System.IO.Compression; using System.Text; using System.Threading; using System.Windows.Forms; @@ -1019,6 +1021,32 @@ namespace HermesHubSetup static class Program { + + private static string ExtractEmbeddedPayload() + { + try + { + var asm = Assembly.GetExecutingAssembly(); + using (Stream res = asm.GetManifestResourceStream("payload")) + { + if (res == null) return null; + string target = Path.Combine(Path.GetTempPath(), + "HermesHubSetup_" + Guid.NewGuid().ToString("N").Substring(0, 8)); + Directory.CreateDirectory(target); + string tmpZip = Path.Combine(target, "_payload.zip"); + using (var fs = File.Create(tmpZip)) res.CopyTo(fs); + ZipFile.ExtractToDirectory(tmpZip, target); + File.Delete(tmpZip); + return Directory.Exists(Path.Combine(target, "src")) ? target : null; + } + } + catch (Exception ex) + { + Console.Error.WriteLine("Не удалось распаковать встроенные файлы: " + ex.Message); + return null; + } + } + [STAThread] static int Main(string[] args) { @@ -1052,6 +1080,15 @@ namespace HermesHubSetup { sourceRoot = Path.GetFullPath(Path.Combine(appDir, "..")); } + // Ни рядом с exe, ни уровнем выше исходников нет — значит установщик + // запущен как самостоятельный файл. Содержимое вшито в него ресурсом + // и распаковывается во временный каталог. Так владельцу достаточно + // одного exe, без копирования репозитория на целевую машину. + if (!Directory.Exists(Path.Combine(sourceRoot, "src"))) + { + string extracted = ExtractEmbeddedPayload(); + if (extracted != null) sourceRoot = extracted; + } // Uninstall Mode if (isUninstall) diff --git a/installer/build_installer.ps1 b/installer/build_installer.ps1 index 623135a..f399103 100644 --- a/installer/build_installer.ps1 +++ b/installer/build_installer.ps1 @@ -26,8 +26,29 @@ if (Test-Path $HubWebCs) { & $CscPath /target:winexe /out:"$HubWebExe" /r:System.Windows.Forms.dll /r:System.Drawing.dll "$HubWebCs" } +# Собираем полезную нагрузку: всё, что нужно PerformInstall на целевой машине. +# Она вшивается в exe ресурсом, чтобы установщик был одним файлом и не требовал +# копировать репозиторий. +Write-Host "Packing payload..." -ForegroundColor Cyan +$PayloadDir = Join-Path $env:TEMP ("hubpayload_" + [guid]::NewGuid().ToString("N").Substring(0,8)) +New-Item -ItemType Directory -Force $PayloadDir | Out-Null +foreach ($item in @("src", "launcher", "assets", "config", "scripts")) { + $srcPath = Join-Path $RepoRoot $item + if (Test-Path $srcPath) { + Copy-Item $srcPath -Destination (Join-Path $PayloadDir $item) -Recurse -Force + } +} +# Каталоги сборки и кеши в дистрибутив не нужны. +Get-ChildItem $PayloadDir -Recurse -Directory -Include "__pycache__", ".venv", "node_modules" -ErrorAction SilentlyContinue | + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue +$PayloadZip = Join-Path $env:TEMP "hub_payload.zip" +if (Test-Path $PayloadZip) { Remove-Item $PayloadZip -Force } +Compress-Archive -Path (Join-Path $PayloadDir "*") -DestinationPath $PayloadZip -CompressionLevel Optimal +$payloadKB = [int]((Get-Item $PayloadZip).Length / 1KB) +Write-Host "Payload packed: $payloadKB KB" -ForegroundColor Gray + Write-Host "Compiling HermesHubSetup.exe..." -ForegroundColor Cyan -& $CscPath /target:winexe /out:"$OutFile" /r:System.Windows.Forms.dll /r:System.Drawing.dll "$SourceFile" +& $CscPath /target:winexe /out:"$OutFile" /r:System.Windows.Forms.dll /r:System.Drawing.dll /r:System.IO.Compression.FileSystem.dll /resource:"$PayloadZip",payload "$SourceFile" if ($LASTEXITCODE -eq 0) { Write-Host "Installer compiled successfully: $OutFile" -ForegroundColor Green @@ -42,3 +63,6 @@ if ($LASTEXITCODE -eq 0) { } else { Write-Error "Installer compilation FAILED with exit code $LASTEXITCODE" } + +Remove-Item $PayloadDir -Recurse -Force -ErrorAction SilentlyContinue +Remove-Item $PayloadZip -Force -ErrorAction SilentlyContinue diff --git a/launcher/HermesHub.exe b/launcher/HermesHub.exe index 17d4a27..11a17cb 100644 Binary files a/launcher/HermesHub.exe and b/launcher/HermesHub.exe differ diff --git a/launcher/HermesHubWeb.exe b/launcher/HermesHubWeb.exe index 1b5a167..d7c22dd 100644 Binary files a/launcher/HermesHubWeb.exe and b/launcher/HermesHubWeb.exe differ diff --git a/tests/test_installer_windows_and_linux.py b/tests/test_installer_windows_and_linux.py index 6263858..78a25a8 100644 --- a/tests/test_installer_windows_and_linux.py +++ b/tests/test_installer_windows_and_linux.py @@ -47,7 +47,10 @@ def test_windows_csharp_launchers_and_setup_compile(): setup_cs = INSTALLER_DIR / "HermesHubSetup.cs" res3 = subprocess.run([ csc_path, "/target:winexe", f"/out:{temp_out / 'HermesHubSetup.exe'}", - "/r:System.Windows.Forms.dll", "/r:System.Drawing.dll", str(setup_cs) + # Установщик несёт содержимое вшитым ресурсом и распаковывает его через + # ZipFile — сборка требует System.IO.Compression.FileSystem. + "/r:System.Windows.Forms.dll", "/r:System.Drawing.dll", + "/r:System.IO.Compression.FileSystem.dll", str(setup_cs) ], capture_output=True, text=True) assert res3.returncode == 0, f"HermesHubSetup.cs compilation failed: {res3.stdout}\n{res3.stderr}"