From 8f62adb7609977ecddde5ac506978373a2323972 Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Mon, 24 Aug 2026 09:21:08 +0700 Subject: [PATCH] =?UTF-8?q?feat(installer):=20=D1=81=D0=B0=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D0=BE=D1=81=D1=82=D0=B0=D1=82=D0=BE=D1=87=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20exe=20=D0=B4=D0=BB=D1=8F=20Windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Владелец: «для винды я думаю нужен exe». Справедливо — «склонируй репозиторий и собери» это не установка. HermesHubSetup.exe требовал, чтобы рядом лежали src/, launcher/, assets/, config/ и scripts/: PerformInstall берёт их из sourceRoot. Поэтому одного файла не хватало, и на целевую машину пришлось бы копировать репозиторий. Теперь содержимое упаковывается при сборке и вшивается в exe ресурсом (/resource:payload.zip,payload). Если рядом с exe и уровнем выше исходников нет, установщик распаковывает вшитое во временный каталог и работает с ним. Прежнее поведение сохранено: при запуске из репозитория используются файлы на диске, ресурс не трогается. Проверено исполнением: exe скопирован в пустой каталог, из него извлечены assets, config, launcher, scripts, src; src/antigravity_provider и launcher/HermesHubWeb.exe на месте. Размер 11.78 МБ. Тест сборки установщика приведён к реальным ссылкам: добавлена System.IO.Compression.FileSystem, без неё ZipFile не разрешался. Тесты: 373 passed, ruff чисто. Co-Authored-By: Claude Opus 5 --- installer/HermesHubSetup.cs | 37 ++++++++++++++++++++++ installer/build_installer.ps1 | 26 ++++++++++++++- launcher/HermesHub.exe | Bin 6144 -> 6144 bytes launcher/HermesHubWeb.exe | Bin 11776 -> 11776 bytes tests/test_installer_windows_and_linux.py | 5 ++- 5 files changed, 66 insertions(+), 2 deletions(-) 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 17d4a2776823500e9a892f1d38703496707dfc1b..11a17cb64fd0e5928d12031bebe1fee130225386 100644 GIT binary patch delta 35 rcmZoLXfT-2!E|!v#;#L50;*vQlK21p_FQ!8piq8ePtoQS-Z@+V8Bh

yW7jDjfh*b($%3-?Jk~xqBGg;<#dmWG?;I`w2W<}j diff --git a/launcher/HermesHubWeb.exe b/launcher/HermesHubWeb.exe index 1b5a16781e7c1bf77768ac0f23616f74c45cc15f..d7c22ddae5925611a2026100fde4b090b36d94e3 100644 GIT binary patch delta 77 zcmV-T0J8spT!37Vhyux}v5NN+Br!KdHZnOwMlD4`LNqNjMK>}nK{GTtEjTqpHbyZ- jK{Q1)Gqa@=(+?2ODdrcn>vB$`NrlGo+D=2Wh$DCk0Wurj delta 77 zcmV-T0J8spT!37VhypyQv5NN+Btb+mF-0~+F)cwbMl>xnI5;*fK{GHoEk-doGd40d jF-9;&F|(x;(+?2Q<`J#*poU1J2=RkrVesLzh$DCk*d`mV 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}"