fix: версия в интерфейсе была зашита в разметке; установка падала на занятом файле

Версия. В index.html номер стоял руками в двух местах, а в app.js был
запасным значением '0.1.1'. Номер сборки приходил из API и обновлялся,
версия — нет: владелец обновился до e6eab12 и увидел v0.1.1. Подъём
версии в пяти местах бэкенда до экрана не доходил вовсе. Теперь версия
берётся только из API; если сервер её не передал, пишется Н/Д с причиной,
а не правдоподобный номер.

Установка. Отказ остановки прежнего хаба прерывал установку целиком, и
владелец получал голый код 15. Теперь неудачная остановка не отменяет
установку: причина показывается, работа продолжается, и если файл
действительно занят, об этом скажет копирование с именем файла.

Копирование файлов получило повтор: процесс мог не успеть отпустить файл
после остановки. Пять попыток с паузой вместо отказа с первой.

599 passed, ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-31 21:46:30 +07:00
parent e6eab12616
commit b2ca7cdd4d
3 changed files with 45 additions and 12 deletions

View file

@ -264,6 +264,19 @@ namespace HermesHubSetup
}
// Restrict cleanup to this installation. Never kill arbitrary Python/browser processes.
public static string StopWarning = "";
// Процесс мог не успеть отпустить файл. Один отказ по занятости — не приговор.
static void CopyWithRetry(string source, string destination)
{
for (int attempt = 1; ; attempt++)
{
try { File.Copy(source, destination, true); return; }
catch (IOException) { if (attempt >= 5) throw; System.Threading.Thread.Sleep(700); }
catch (UnauthorizedAccessException) { if (attempt >= 5) throw; System.Threading.Thread.Sleep(700); }
}
}
public static void StopOwnedRuntime(string home, bool includeLauncher)
{
string escaped = Path.GetFullPath(home).TrimEnd('\\').Replace("'", "''");
@ -296,7 +309,18 @@ namespace HermesHubSetup
try
{
StopOwnedRuntime(HermesHome, true);
// Неудачная остановка прежнего хаба не повод отменять установку.
// Раньше любой ненулевой выход скрипта останавливал всё, и владелец
// видел голый код 15. Если процесс уцелел, копирование само скажет,
// какой файл занят.
try { StopOwnedRuntime(HermesHome, true); }
catch (Exception stopEx)
{
StopWarning = stopEx.Message;
if (progressCallback != null)
progressCallback("Не удалось остановить прежний Hermes Hub: " + stopEx.Message
+ ". Продолжаю установку.", 5);
}
if (progressCallback != null) progressCallback("Preparing installation directory...", 10);
if (!Directory.Exists(TargetInstallDir))
{
@ -313,8 +337,8 @@ namespace HermesHubSetup
if (File.Exists(launcherSrc))
{
File.Copy(launcherSrc, Path.Combine(TargetInstallDir, "HermesHub.exe"), true);
File.Copy(launcherSrc, Path.Combine(HermesHome, "HermesHub.exe"), true);
CopyWithRetry(launcherSrc, Path.Combine(TargetInstallDir, "HermesHub.exe"));
CopyWithRetry(launcherSrc, Path.Combine(HermesHome, "HermesHub.exe"));
}
string webLauncherSrc = Path.Combine(sourceRoot, @"launcher\HermesHubWeb.exe");
@ -325,15 +349,15 @@ namespace HermesHubSetup
if (File.Exists(webLauncherSrc))
{
File.Copy(webLauncherSrc, Path.Combine(TargetInstallDir, "HermesHubWeb.exe"), true);
File.Copy(webLauncherSrc, Path.Combine(HermesHome, "HermesHubWeb.exe"), true);
CopyWithRetry(webLauncherSrc, Path.Combine(TargetInstallDir, "HermesHubWeb.exe"));
CopyWithRetry(webLauncherSrc, Path.Combine(HermesHome, "HermesHubWeb.exe"));
}
// Copy Setup.exe itself to target dir for uninstaller/repair
string setupSrc = Process.GetCurrentProcess().MainModule.FileName;
if (File.Exists(setupSrc))
{
try { File.Copy(setupSrc, Path.Combine(TargetInstallDir, "HermesHubSetup.exe"), true); } catch { }
try { CopyWithRetry(setupSrc, Path.Combine(TargetInstallDir, "HermesHubSetup.exe")); } catch { }
}
// 2. Install UI & System Dependencies into Hermes Python Environment
@ -392,7 +416,7 @@ namespace HermesHubSetup
string templateConfig = Path.Combine(sourceRoot, @"config\router_profiles.example.yaml");
if (!File.Exists(runtimeConfig) && File.Exists(templateConfig))
{
File.Copy(templateConfig, runtimeConfig, true);
CopyWithRetry(templateConfig, runtimeConfig);
}
// 6. Create Start Menu Shortcut
@ -529,7 +553,7 @@ namespace HermesHubSetup
string fileName = Path.GetFileName(file);
srcFiles.Add(fileName);
string destFile = Path.Combine(dst, fileName);
File.Copy(file, destFile, true);
CopyWithRetry(file, destFile);
}
// Remove destination files that do not exist in source or are .pyc

View file

@ -2293,10 +2293,19 @@ function renderUpdateUI() {
const releaseMeta = document.getElementById('update-release-meta');
const releaseNotes = document.getElementById('update-release-notes');
const curVer = (latestUpdateInfo && latestUpdateInfo.current_version) || (currentSettings && currentSettings.version) || '0.1.1';
// Версия берётся ТОЛЬКО из API. Раньше номер был зашит в разметке и в
// запасном значении: подъём версии в коде до интерфейса не доходил, и
// владелец видел старый номер при новой сборке.
const curVer = (latestUpdateInfo && latestUpdateInfo.current_version) || (currentSettings && currentSettings.version) || '';
const cDisplay = installedCommit ? installedCommit.slice(0, 7) : 'неизвестно';
if (updateInfoDesc) {
updateInfoDesc.textContent = `Hermes Hub v${curVer} (сборка: ${cDisplay})`;
updateInfoDesc.textContent = curVer
? `Hermes Hub v${curVer} (сборка: ${cDisplay})`
: `Hermes Hub (сборка: ${cDisplay}) — Н/Д: версия не передана сервером`;
}
const versionTag = document.getElementById('version-tag');
if (versionTag) {
versionTag.textContent = curVer ? `Hermes Hub Web v${curVer}` : 'Hermes Hub Web — Н/Д: версия не передана сервером';
}
if (statusBadge) {

View file

@ -66,7 +66,7 @@
<span class="status-dot healthy"></span>
<span class="source-text" id="source-text">Загрузка данных...</span>
</div>
<div class="version-tag" id="version-tag">Hermes Hub Web v0.1.1</div>
<div class="version-tag" id="version-tag">Hermes Hub Web</div>
<div class="commit-tag" id="commit-tag" style="font-size:11px; color:var(--text-muted); font-family:var(--font-mono); margin-top:2px;"></div>
</div>
</aside>
@ -600,7 +600,7 @@
<div class="setting-row">
<div class="setting-info">
<div class="setting-label">Текущая версия и сборка</div>
<div class="setting-desc" id="update-installed-info">Hermes Hub v0.1.1</div>
<div class="setting-desc" id="update-installed-info">Hermes Hub</div>
</div>
<div class="setting-control" style="display:flex; align-items:center; gap:8px;">
<span id="update-status-badge" class="badge">Не проверено</span>