mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-21 16:01:04 +00:00
fix(windows): use active release directory on PATH (#3618)
* fix(windows): use active release directory on PATH refs #3611 * fix(windows): expand variables in PATH comparisons refs #3611 * fix(windows): restrict installer channel detection refs #3611 --------- Co-authored-by: Can Celik <ogulcancelik@gmail.com>
This commit is contained in:
+125
-61
@@ -55,56 +55,86 @@ function Get-HerdrCommandSource {
|
||||
return $existing.Source
|
||||
}
|
||||
|
||||
function Test-PathStartsWith {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string]$Prefix
|
||||
)
|
||||
function Get-HerdrMigrationFallback {
|
||||
param([string]$CurrentDir)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Path) -or [string]::IsNullOrWhiteSpace($Prefix)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
try {
|
||||
$normalizedPath = [System.IO.Path]::GetFullPath($Path)
|
||||
$normalizedPrefix = [System.IO.Path]::GetFullPath($Prefix).TrimEnd("\") + "\"
|
||||
return $normalizedPath.StartsWith($normalizedPrefix, [System.StringComparison]::OrdinalIgnoreCase)
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Path-Contains {
|
||||
param(
|
||||
[string]$PathValue,
|
||||
[string]$Entry
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($PathValue)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
$needle = $Entry.TrimEnd("\")
|
||||
foreach ($segment in $PathValue.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries)) {
|
||||
if ($segment.TrimEnd("\") -ieq $needle) {
|
||||
return $true
|
||||
if (Test-IsJunction -Path $CurrentDir) {
|
||||
$target = [string](Get-Item -LiteralPath $CurrentDir -Force).Target
|
||||
$candidate = Join-Path $target "herdr.exe"
|
||||
if (Test-RegularFile -Path $candidate) {
|
||||
return $candidate
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-HerdrExecutableKind {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string]$ReleasesDir,
|
||||
[string]$CurrentDir,
|
||||
[string]$VisibleBinDir
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Path) -or
|
||||
-not [System.IO.Path]::GetFileName($Path).Equals("herdr.exe", [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
try {
|
||||
$fullPath = [System.IO.Path]::GetFullPath($Path)
|
||||
foreach ($alias in @($CurrentDir, $VisibleBinDir)) {
|
||||
$aliasHerdr = [System.IO.Path]::GetFullPath((Join-Path $alias "herdr.exe"))
|
||||
if ($fullPath.Equals($aliasHerdr, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
return "alias"
|
||||
}
|
||||
}
|
||||
|
||||
$parent = Split-Path -Parent $fullPath
|
||||
if ([System.IO.Path]::GetFullPath((Split-Path -Parent $parent)).TrimEnd("\").Equals(
|
||||
[System.IO.Path]::GetFullPath($ReleasesDir).TrimEnd("\"),
|
||||
[System.StringComparison]::OrdinalIgnoreCase
|
||||
)) {
|
||||
return "release"
|
||||
}
|
||||
} catch {
|
||||
return $null
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Prepend-PathEntry {
|
||||
param(
|
||||
[string]$PathValue,
|
||||
[string]$Entry
|
||||
[string]$Entry,
|
||||
[string[]]$OwnedEntriesToRemove = @(),
|
||||
[string]$OwnedEntryParentToRemove
|
||||
)
|
||||
|
||||
$needle = $Entry.TrimEnd("\")
|
||||
$normalize = {
|
||||
param([string]$Value)
|
||||
$comparison = [Environment]::ExpandEnvironmentVariables($Value.Trim().Trim('"')).TrimEnd("\")
|
||||
try { [System.IO.Path]::GetFullPath($comparison).TrimEnd("\") } catch { $comparison }
|
||||
}
|
||||
$needle = & $normalize $Entry
|
||||
$owned = @($OwnedEntriesToRemove | ForEach-Object { & $normalize $_ })
|
||||
$ownedParent = if ([string]::IsNullOrWhiteSpace($OwnedEntryParentToRemove)) {
|
||||
$null
|
||||
} else {
|
||||
& $normalize $OwnedEntryParentToRemove
|
||||
}
|
||||
$segments = @($Entry)
|
||||
if (-not [string]::IsNullOrWhiteSpace($PathValue)) {
|
||||
$segments += $PathValue.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries) |
|
||||
Where-Object { $_.TrimEnd("\") -ine $needle }
|
||||
Where-Object {
|
||||
$segment = & $normalize $_
|
||||
try { $parent = [System.IO.Path]::GetDirectoryName($segment) } catch { $parent = $null }
|
||||
$segment -ine $needle -and
|
||||
-not ($owned -icontains $segment) -and
|
||||
($null -eq $ownedParent -or $parent -ine $ownedParent)
|
||||
}
|
||||
}
|
||||
|
||||
return ($segments -join ";")
|
||||
@@ -113,7 +143,9 @@ function Prepend-PathEntry {
|
||||
function Update-PathRegistryEntry {
|
||||
param(
|
||||
[Microsoft.Win32.RegistryKey]$EnvironmentKey,
|
||||
[string]$Entry
|
||||
[string]$Entry,
|
||||
[string[]]$OwnedEntriesToRemove = @(),
|
||||
[string]$OwnedEntryParentToRemove
|
||||
)
|
||||
|
||||
$options = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
|
||||
@@ -123,7 +155,11 @@ function Update-PathRegistryEntry {
|
||||
} else {
|
||||
$EnvironmentKey.GetValueKind("Path")
|
||||
}
|
||||
$newValue = Prepend-PathEntry -PathValue $value -Entry $Entry
|
||||
$newValue = Prepend-PathEntry `
|
||||
-PathValue $value `
|
||||
-Entry $Entry `
|
||||
-OwnedEntriesToRemove $OwnedEntriesToRemove `
|
||||
-OwnedEntryParentToRemove $OwnedEntryParentToRemove
|
||||
if ($newValue -ceq $value) {
|
||||
return $false
|
||||
}
|
||||
@@ -703,10 +739,17 @@ try {
|
||||
$allowLegacyVisibleBinMigration = $false
|
||||
}
|
||||
|
||||
$existingHerdr = Get-HerdrCommandSource
|
||||
if (-not [string]::IsNullOrWhiteSpace($existingHerdr) -and -not (Test-PathStartsWith -Path $existingHerdr -Prefix $visibleBinDir)) {
|
||||
Write-Step "Detected existing Herdr command at $existingHerdr"
|
||||
Write-WarningStep "PATH order decides which Herdr runs. This installer will put $visibleBinDir first for future and current PowerShell sessions."
|
||||
$commandHerdr = Get-HerdrCommandSource
|
||||
$channelHerdr = $commandHerdr
|
||||
if ([string]::IsNullOrWhiteSpace($channelHerdr)) { $channelHerdr = Get-HerdrMigrationFallback -CurrentDir $currentDir }
|
||||
$existingHerdrKind = Get-HerdrExecutableKind `
|
||||
-Path $channelHerdr `
|
||||
-ReleasesDir $releasesDir `
|
||||
-CurrentDir $currentDir `
|
||||
-VisibleBinDir $visibleBinDir
|
||||
if (-not [string]::IsNullOrWhiteSpace($commandHerdr) -and $null -eq $existingHerdrKind) {
|
||||
Write-Step "Detected existing Herdr command at $commandHerdr"
|
||||
Write-WarningStep "PATH order decides which Herdr runs. This installer will put the active versioned release first for future and current PowerShell sessions."
|
||||
}
|
||||
|
||||
if ($useLocalPackage) {
|
||||
@@ -717,8 +760,11 @@ if ($useLocalPackage) {
|
||||
}
|
||||
} else {
|
||||
if (-not $channelWasExplicit) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($existingHerdr)) {
|
||||
$detectedChannel = [string](& $existingHerdr channel show 2>$null | Select-Object -Last 1)
|
||||
if (-not [string]::IsNullOrWhiteSpace($channelHerdr) -and $null -eq $existingHerdrKind) {
|
||||
throw "Refusing to run unrecognized Herdr command at $channelHerdr to detect its update channel. Rerun with -Channel stable or -Channel preview."
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($channelHerdr)) {
|
||||
$detectedChannel = [string](& $channelHerdr channel show 2>$null | Select-Object -Last 1)
|
||||
$detectedChannel = $detectedChannel.Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or $detectedChannel -notin @("stable", "preview")) {
|
||||
throw "Could not determine the existing Herdr update channel. Rerun with -Channel stable or -Channel preview."
|
||||
@@ -776,8 +822,9 @@ Write-Step "Installing Herdr $versionIdentity for $targetTriple"
|
||||
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("herdr-install-" + [System.Guid]::NewGuid().ToString("N"))
|
||||
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
||||
|
||||
$userPathChanged = $false
|
||||
try {
|
||||
Invoke-WithInstallLock -LockPath $lockPath -Script {
|
||||
$userPathChanged = Invoke-WithInstallLock -LockPath $lockPath -Script {
|
||||
Remove-StaleInstallArtifacts -ReleasesDir $releasesDir
|
||||
|
||||
if (-not (Test-HerdrReleaseComplete -ReleaseDir $releaseDir -Format $asset.Format)) {
|
||||
@@ -834,35 +881,52 @@ try {
|
||||
Set-ManagedJunction -LinkPath $currentDir -TargetPath $releaseDir -ManagedTargetPrefix $releasesDir
|
||||
Set-ManagedJunction -LinkPath $visibleBinDir -TargetPath $releaseDir -ManagedTargetPrefix $standaloneRoot -AllowLegacyHerdrBinMigration $allowLegacyVisibleBinMigration
|
||||
|
||||
$ownedPathEntries = @($visibleBinDir, $currentDir)
|
||||
$userEnvironmentKey = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey("Environment")
|
||||
if ($null -eq $userEnvironmentKey) {
|
||||
throw "Unable to open the current user's environment registry key."
|
||||
}
|
||||
try {
|
||||
$pathChanged = Update-PathRegistryEntry `
|
||||
-EnvironmentKey $userEnvironmentKey `
|
||||
-Entry $releaseDir `
|
||||
-OwnedEntriesToRemove $ownedPathEntries `
|
||||
-OwnedEntryParentToRemove $releasesDir
|
||||
} finally {
|
||||
$userEnvironmentKey.Dispose()
|
||||
}
|
||||
|
||||
$env:Path = Prepend-PathEntry `
|
||||
-PathValue $env:Path `
|
||||
-Entry $releaseDir `
|
||||
-OwnedEntriesToRemove $ownedPathEntries `
|
||||
-OwnedEntryParentToRemove $releasesDir
|
||||
Remove-OldReleases -ReleasesDir $releasesDir -CurrentReleaseDir $releaseDir -Keep $Retain
|
||||
return $pathChanged
|
||||
}
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$userEnvironmentKey = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey("Environment")
|
||||
if ($null -eq $userEnvironmentKey) {
|
||||
throw "Unable to open the current user's environment registry key."
|
||||
}
|
||||
try {
|
||||
$userPathChanged = Update-PathRegistryEntry -EnvironmentKey $userEnvironmentKey -Entry $visibleBinDir
|
||||
} finally {
|
||||
$userEnvironmentKey.Dispose()
|
||||
}
|
||||
if ($userPathChanged) {
|
||||
Publish-EnvironmentChange
|
||||
Write-Step "PATH updated for future PowerShell sessions."
|
||||
} else {
|
||||
Write-Step "$visibleBinDir is already first on PATH."
|
||||
}
|
||||
|
||||
$newProcessPath = Prepend-PathEntry -PathValue $env:Path -Entry $visibleBinDir
|
||||
if ($newProcessPath -cne $env:Path) {
|
||||
$env:Path = $newProcessPath
|
||||
Write-Step "$releaseDir is already first on PATH."
|
||||
}
|
||||
|
||||
$resolvedHerdr = Get-HerdrCommandSource
|
||||
if (-not (Test-PathStartsWith -Path $resolvedHerdr -Prefix $visibleBinDir)) {
|
||||
$resolvedHerdrKind = Get-HerdrExecutableKind `
|
||||
-Path $resolvedHerdr `
|
||||
-ReleasesDir $releasesDir `
|
||||
-CurrentDir $currentDir `
|
||||
-VisibleBinDir $visibleBinDir
|
||||
$releaseHerdr = Join-Path $releaseDir "herdr.exe"
|
||||
if ($resolvedHerdrKind -ne "release" -or
|
||||
-not [System.IO.Path]::GetFullPath($resolvedHerdr).Equals(
|
||||
[System.IO.Path]::GetFullPath($releaseHerdr),
|
||||
[System.StringComparison]::OrdinalIgnoreCase
|
||||
)) {
|
||||
Write-WarningStep "PowerShell still resolves herdr to $resolvedHerdr. Open a new PowerShell window or inspect PATH order manually."
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ If endpoint security blocks that fileless PowerShell command, open Command Promp
|
||||
curl.exe -fsSLo install.cmd https://herdr.dev/install.cmd && install.cmd && del install.cmd
|
||||
```
|
||||
|
||||
The installer downloads the release binary for your platform and places it on your PATH. New direct installs use the stable update channel. Existing Windows preview installs stay on preview until you switch them with `herdr channel set stable`. The Windows installer uses versioned install folders and updates a `current` junction, so updates do not need to overwrite a running `herdr.exe`.
|
||||
The installer downloads the release binary for your platform and places it on your PATH. New direct installs use the stable update channel. Existing Windows preview installs stay on preview until you switch them with `herdr channel set stable`. On Windows, PATH points to the active versioned release directory; the installer also updates the `current` and stable `bin` compatibility aliases, so updates do not need to overwrite a running `herdr.exe`.
|
||||
|
||||
## Install with Homebrew
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex"
|
||||
curl.exe -fsSLo install.cmd https://herdr.dev/install.cmd && install.cmd && del install.cmd
|
||||
```
|
||||
|
||||
インストーラーはプラットフォームに合ったリリースバイナリをダウンロードして PATH 上に配置します。新しい直接インストールは安定版アップデートチャンネルを使います。既存の Windows プレビューインストールは、`herdr channel set stable` で切り替えるまでプレビューに残ります。Windows インストーラーはバージョン付きインストールフォルダーを使い、`current` ジャンクションを更新します。そのため、アップデート時に実行中の `herdr.exe` を上書きする必要がありません。
|
||||
インストーラーはプラットフォームに合ったリリースバイナリをダウンロードして PATH 上に配置します。新しい直接インストールは安定版アップデートチャンネルを使います。既存の Windows プレビューインストールは、`herdr channel set stable` で切り替えるまでプレビューに残ります。Windows では PATH が有効なバージョン付きリリースディレクトリを指し、インストーラーは互換性のため `current` と安定した `bin` エイリアスも更新します。そのため、アップデート時に実行中の `herdr.exe` を上書きする必要がありません。
|
||||
|
||||
## Homebrew でインストール
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ curl.exe -fsSLo install.cmd https://herdr.dev/install.cmd && install.cmd && del
|
||||
|
||||
Windows ビルドは安定版とプレビューの両方のアップデートチャンネルで提供されます。新規インストールはデフォルトで安定版を使い、通常の利用には安定版を推奨します。既存のプレビューインストールは、`herdr channel set stable` を実行するまでプレビューに残ります。古いプレビュービルドでこのコマンドが拒否される場合は、プレビューのまま一度 `herdr update` を実行してから再試行してください。プレビューでは `master` からの新しく検証の少ない修正を受け取れますが、リグレッションが起きる可能性があります。そのトレードオフを望む場合に限り、`herdr channel set preview` でオプトインしてください。
|
||||
|
||||
インストーラーはリリースを `%USERPROFILE%\.herdr\packages\standalone\releases` に保存し、`%LOCALAPPDATA%\Programs\Herdr\bin` を現在のリリースに向け、実行中のプロセスがアップデートを妨げないよう少数の古いリリースを保持します。
|
||||
インストーラーはリリースを `%USERPROFILE%\.herdr\packages\standalone\releases` に保存し、有効なバージョン付きリリースディレクトリを PATH に追加し、`%LOCALAPPDATA%\Programs\Herdr\bin` を安定した互換エイリアスとして維持します。また、実行中のプロセスがアップデートを妨げないよう少数の古いリリースを保持します。
|
||||
|
||||
内部テスト向けに、`HERDR_MANIFEST_URL` でインストーラーを Herdr の安定版/プレビューマニフェストではなくカスタムマニフェストに向けられます。カスタムプレビューマニフェストでは `HERDR_CHANNEL=preview` も設定してください。
|
||||
|
||||
@@ -133,7 +133,7 @@ herdr --remote workbox
|
||||
|
||||
接続先ホストは Linux または macOS である必要があります。Herdr はインストール済みの Windows OpenSSH クライアントと SSH 設定を使います。Windows OpenSSH では Herdr の Unix コントロールソケットによる接続再利用を使わないため、セットアップ中の繰り返しプロンプトを避けるには Windows の `ssh-agent` による鍵認証を推奨します。
|
||||
|
||||
Windows のアップデートは Windows インストーラー経由で行われ、バージョン付きインストールジャンクションを更新します。再度 Herdr を起動すると更新済みクライアントを使え、互換性のある実行中サーバーはペインを動かしたままにします。新しいリリースのサーバー側変更が必要になったときだけ、後からサーバーを再起動してください。ライブハンドオフは Unix 専用です。
|
||||
Windows のアップデートは Windows インストーラー経由で行われ、有効なバージョン付きリリースパスを更新します。新しいターミナルまたは再接続した SSH セッションはそのパスを受け取るため、そこで Herdr を起動すると更新済みクライアントを使えます。互換性のある実行中サーバーはペインを動かしたままにします。新しいリリースのサーバー側変更が必要になったときだけ、後からサーバーを再起動してください。ライブハンドオフは Unix 専用です。
|
||||
|
||||
## Windows の問題を報告する
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ curl.exe -fsSLo install.cmd https://herdr.dev/install.cmd && install.cmd && del
|
||||
|
||||
Windows builds are available through both stable and preview update channels. New installs use stable by default, and stable is recommended for normal use. Existing preview installs stay on preview until you run `herdr channel set stable`. If an older preview build rejects that command, run `herdr update` once on preview and retry. Preview provides newer, less-tested fixes from `master` and may regress; opt in with `herdr channel set preview` only when you want that tradeoff.
|
||||
|
||||
The installer stores releases under `%USERPROFILE%\.herdr\packages\standalone\releases`, points `%LOCALAPPDATA%\Programs\Herdr\bin` at the current release, and keeps a small number of older releases so running processes do not block updates.
|
||||
The installer stores releases under `%USERPROFILE%\.herdr\packages\standalone\releases`, puts the active versioned release directory on PATH, keeps `%LOCALAPPDATA%\Programs\Herdr\bin` as a stable compatibility alias, and retains a small number of older releases so running processes do not block updates.
|
||||
|
||||
For internal testing, `HERDR_MANIFEST_URL` can point the installer at a custom manifest instead of Herdr's stable or preview manifest. Set `HERDR_CHANNEL=preview` with a custom preview manifest.
|
||||
|
||||
@@ -133,7 +133,7 @@ herdr --remote workbox
|
||||
|
||||
The target host must run Linux or macOS. Herdr uses the installed Windows OpenSSH client and your SSH configuration. Windows OpenSSH does not use Herdr's Unix control-socket reuse, so key authentication through Windows `ssh-agent` is recommended to avoid repeated prompts during remote setup.
|
||||
|
||||
Windows updates run through the Windows installer and update the versioned install junction. Start Herdr again to use the updated client; compatible running servers keep their panes alive. Restart a server later only when you need server-side changes from the release. Live handoff is Unix-only.
|
||||
Windows updates run through the Windows installer and update the active versioned release path. New terminals and reconnected SSH sessions receive that path; start Herdr there to use the updated client. Compatible running servers keep their panes alive. Restart a server later only when you need server-side changes from the release. Live handoff is Unix-only.
|
||||
|
||||
## Reporting Windows issues
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex"
|
||||
curl.exe -fsSLo install.cmd https://herdr.dev/install.cmd && install.cmd && del install.cmd
|
||||
```
|
||||
|
||||
安装器会下载适合你平台的发布二进制文件并放到 PATH 上。新的直接安装默认使用稳定更新通道。现有的 Windows 预览安装会继续留在预览通道,直到你运行 `herdr channel set stable`。Windows 安装器使用带版本号的安装文件夹,并更新一个 `current` 联接点,因此更新时不需要覆盖正在运行的 `herdr.exe`。
|
||||
安装器会下载适合你平台的发布二进制文件并放到 PATH 上。新的直接安装默认使用稳定更新通道。现有的 Windows 预览安装会继续留在预览通道,直到你运行 `herdr channel set stable`。在 Windows 上,PATH 指向当前带版本号的发布目录;安装器还会更新 `current` 和稳定的 `bin` 兼容别名,因此更新时不需要覆盖正在运行的 `herdr.exe`。
|
||||
|
||||
## 用 Homebrew 安装
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ curl.exe -fsSLo install.cmd https://herdr.dev/install.cmd && install.cmd && del
|
||||
|
||||
Windows 构建同时通过稳定版和预览版更新通道提供。新安装默认使用稳定版,常规使用也建议选择稳定版。现有预览安装会继续留在预览通道,直到你运行 `herdr channel set stable`。如果旧预览构建拒绝该命令,请先在预览通道运行一次 `herdr update`,再重试。预览版会提供来自 `master` 的更新、测试较少的修复,也可能出现回归;只有在接受这一取舍时,才应通过 `herdr channel set preview` 主动启用。
|
||||
|
||||
安装器将发布版本保存在 `%USERPROFILE%\.herdr\packages\standalone\releases` 下,让 `%LOCALAPPDATA%\Programs\Herdr\bin` 指向当前版本,并保留少量旧版本,以免运行中的进程阻塞更新。
|
||||
安装器将发布版本保存在 `%USERPROFILE%\.herdr\packages\standalone\releases` 下,把当前带版本号的发布目录加入 PATH,将 `%LOCALAPPDATA%\Programs\Herdr\bin` 保留为稳定的兼容别名,并保留少量旧版本,以免运行中的进程阻塞更新。
|
||||
|
||||
对于内部测试,`HERDR_MANIFEST_URL` 可以让安装器指向自定义清单,而不是 Herdr 的稳定或预览清单。使用自定义预览清单时还应设置 `HERDR_CHANNEL=preview`。
|
||||
|
||||
@@ -133,7 +133,7 @@ herdr --remote workbox
|
||||
|
||||
目标主机必须运行 Linux 或 macOS。Herdr 使用已安装的 Windows OpenSSH 客户端和你的 SSH 配置。Windows OpenSSH 不使用 Herdr 的 Unix control socket 连接复用,因此建议通过 Windows `ssh-agent` 使用密钥认证,避免远程设置期间重复提示。
|
||||
|
||||
Windows 更新通过 Windows 安装器进行,并更新带版本号的安装联接点。再次启动 Herdr 即可使用更新后的客户端;兼容的运行中服务器会让窗格继续运行。只有需要新版本的服务器端改动时,才稍后重启服务器。实时交接仅限 Unix。
|
||||
Windows 更新通过 Windows 安装器进行,并更新当前带版本号的发布路径。新终端和重新连接的 SSH 会话会获得该路径;在其中启动 Herdr 即可使用更新后的客户端。兼容的运行中服务器会让窗格继续运行。只有需要新版本的服务器端改动时,才稍后重启服务器。实时交接仅限 Unix。
|
||||
|
||||
## 报告 Windows 问题
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@ foreach ($functionName in @("Prepend-PathEntry", "Update-PathRegistryEntry")) {
|
||||
|
||||
$pathTestVariable = "HERDR_INSTALLER_PATH_TEST"
|
||||
$oldPathTestVariable = [Environment]::GetEnvironmentVariable($pathTestVariable, "Process")
|
||||
$ownedPathTestVariable = "HERDR_INSTALLER_OWNED_PATH_TEST"
|
||||
$oldOwnedPathTestVariable = [Environment]::GetEnvironmentVariable($ownedPathTestVariable, "Process")
|
||||
$testRegistryPath = "Software\HerdrInstallerTests-$([Guid]::NewGuid().ToString('N'))"
|
||||
$testEnvironmentKey = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey($testRegistryPath)
|
||||
if ($null -eq $testEnvironmentKey) {
|
||||
@@ -64,22 +66,32 @@ if ($null -eq $testEnvironmentKey) {
|
||||
}
|
||||
try {
|
||||
[Environment]::SetEnvironmentVariable($pathTestVariable, "C:\expanded", "Process")
|
||||
[Environment]::SetEnvironmentVariable($ownedPathTestVariable, "C:\Herdr", "Process")
|
||||
$testEnvironmentKey.SetValue(
|
||||
"Path",
|
||||
"%$pathTestVariable%\bin;C:\existing",
|
||||
"C:\Herdr\releases\deleted;%$ownedPathTestVariable%\bin;C:\Herdr\bin;%$pathTestVariable%\bin;`"C:\Program Files\Tool`";C:\existing;C:\Herdr\releases\old\nested;C:\Herdr\releases-old\old;C:\Herdr\releases\deleted",
|
||||
[Microsoft.Win32.RegistryValueKind]::ExpandString
|
||||
)
|
||||
$pathChanged = Update-PathRegistryEntry -EnvironmentKey $testEnvironmentKey -Entry "C:\Herdr\bin"
|
||||
$ownedEntries = @("C:\Herdr\bin", "C:\Herdr\current")
|
||||
$pathChanged = Update-PathRegistryEntry `
|
||||
-EnvironmentKey $testEnvironmentKey `
|
||||
-Entry "C:\Herdr\releases\new" `
|
||||
-OwnedEntriesToRemove $ownedEntries `
|
||||
-OwnedEntryParentToRemove "C:\Herdr\releases"
|
||||
if (-not $pathChanged) {
|
||||
throw "installer PATH update reported no change"
|
||||
}
|
||||
if (Update-PathRegistryEntry -EnvironmentKey $testEnvironmentKey -Entry "C:\Herdr\bin") {
|
||||
if (Update-PathRegistryEntry `
|
||||
-EnvironmentKey $testEnvironmentKey `
|
||||
-Entry "C:\Herdr\releases\new" `
|
||||
-OwnedEntriesToRemove $ownedEntries `
|
||||
-OwnedEntryParentToRemove "C:\Herdr\releases") {
|
||||
throw "installer PATH update was not idempotent"
|
||||
}
|
||||
|
||||
$options = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
|
||||
$rawPath = $testEnvironmentKey.GetValue("Path", $null, $options)
|
||||
$expectedPath = "C:\Herdr\bin;%$pathTestVariable%\bin;C:\existing"
|
||||
$expectedPath = "C:\Herdr\releases\new;%$pathTestVariable%\bin;`"C:\Program Files\Tool`";C:\existing;C:\Herdr\releases\old\nested;C:\Herdr\releases-old\old"
|
||||
if ($rawPath -cne $expectedPath) {
|
||||
throw "installer changed raw PATH: expected '$expectedPath', got '$rawPath'"
|
||||
}
|
||||
@@ -90,6 +102,7 @@ try {
|
||||
$testEnvironmentKey.Dispose()
|
||||
[Microsoft.Win32.Registry]::CurrentUser.DeleteSubKeyTree($testRegistryPath, $false)
|
||||
[Environment]::SetEnvironmentVariable($pathTestVariable, $oldPathTestVariable, "Process")
|
||||
[Environment]::SetEnvironmentVariable($ownedPathTestVariable, $oldOwnedPathTestVariable, "Process")
|
||||
}
|
||||
|
||||
$archive = (Resolve-Path -LiteralPath $ArchivePath).Path
|
||||
@@ -133,13 +146,34 @@ $stableManifest = @{
|
||||
} | ConvertTo-Json -Depth 5
|
||||
$previewManifestPath = Join-Path $webRoot "preview.json"
|
||||
$stableManifestPath = Join-Path $webRoot "latest.json"
|
||||
$customPreviewManifestPath = Join-Path $webRoot "candidate.json"
|
||||
$customPreviewManifest = $previewManifest | ConvertFrom-Json
|
||||
$customPreviewManifest.PSObject.Properties.Remove("channel")
|
||||
$previewManifest | Out-File -LiteralPath $previewManifestPath -Encoding utf8
|
||||
$legacyStableManifest | Out-File -LiteralPath $stableManifestPath -Encoding utf8
|
||||
$customPreviewManifest | ConvertTo-Json -Depth 5 | Out-File -LiteralPath $customPreviewManifestPath -Encoding utf8
|
||||
|
||||
$server = $null
|
||||
$oldHerdrHome = $env:HERDR_HOME
|
||||
$oldInstallerUrl = $env:HERDR_INSTALLER_URL
|
||||
$oldProcessPath = $env:Path
|
||||
$registryOptions = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
|
||||
$realUserEnvironmentKey = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey("Environment")
|
||||
if ($null -eq $realUserEnvironmentKey) {
|
||||
throw "unable to open the current user's environment registry key"
|
||||
}
|
||||
$realUserPathExisted = $realUserEnvironmentKey.GetValueNames() -contains "Path"
|
||||
$realUserPath = if ($realUserPathExisted) {
|
||||
$realUserEnvironmentKey.GetValue("Path", $null, $registryOptions)
|
||||
} else {
|
||||
$null
|
||||
}
|
||||
$realUserPathKind = if ($realUserPathExisted) {
|
||||
$realUserEnvironmentKey.GetValueKind("Path")
|
||||
} else {
|
||||
$null
|
||||
}
|
||||
$realUserEnvironmentKey.Dispose()
|
||||
try {
|
||||
$server = Start-Process python -ArgumentList @("-m", "http.server", "$port", "--bind", "127.0.0.1", "--directory", $webRoot) -PassThru -WindowStyle Hidden
|
||||
$env:HERDR_HOME = Join-Path $root "unused\..\home"
|
||||
@@ -256,6 +290,39 @@ try {
|
||||
if ($null -eq $releaseDir) {
|
||||
throw "installer did not create a versioned release"
|
||||
}
|
||||
|
||||
$pathWithoutHerdr = @(
|
||||
$env:Path.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries) |
|
||||
Where-Object {
|
||||
-not (Test-Path -LiteralPath (Join-Path $_ "herdr.exe") -PathType Leaf) -and
|
||||
-not (Test-Path -LiteralPath (Join-Path $_ "herdr.cmd") -PathType Leaf)
|
||||
}
|
||||
) -join ";"
|
||||
$env:Path = $pathWithoutHerdr
|
||||
if ($null -ne (Get-Command herdr -ErrorAction SilentlyContinue)) {
|
||||
throw "test PATH still resolves Herdr before current-junction migration discovery"
|
||||
}
|
||||
$oldConfigPath = $env:HERDR_CONFIG_PATH
|
||||
try {
|
||||
$env:HERDR_CONFIG_PATH = Join-Path $root "preview-config.toml"
|
||||
"[update]`nchannel = `"preview`"" | Out-File -LiteralPath $env:HERDR_CONFIG_PATH -Encoding ascii
|
||||
& $installerPath `
|
||||
-ManifestUrl "http://127.0.0.1:$port/candidate.json" `
|
||||
-InstallDir $installDir `
|
||||
-ExpectedBuildId "installer-test"
|
||||
} finally {
|
||||
if ($null -eq $oldConfigPath) {
|
||||
Remove-Item Env:HERDR_CONFIG_PATH -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
$env:HERDR_CONFIG_PATH = $oldConfigPath
|
||||
}
|
||||
}
|
||||
if ($null -eq (Get-ChildItem -LiteralPath $releasesDir -Directory |
|
||||
Where-Object { $_.Name.StartsWith("0.0.0-preview.installer-test-") } |
|
||||
Select-Object -First 1)) {
|
||||
throw "installer did not discover the preview channel through the current junction"
|
||||
}
|
||||
|
||||
Remove-Item -LiteralPath (Join-Path $releaseDir.FullName "conpty\conpty.dll") -Force
|
||||
|
||||
$badManifest = $previewManifest | ConvertFrom-Json
|
||||
@@ -429,51 +496,113 @@ try {
|
||||
throw "installer accepted a manifest that did not match the updater-selected build"
|
||||
}
|
||||
|
||||
$currentDir = Join-Path $herdrHome "packages\standalone\current"
|
||||
$unrelatedPathOne = Join-Path $env:SystemRoot "System32"
|
||||
$unrelatedPathTwo = Join-Path $root "unrelated-two"
|
||||
New-Item -ItemType Directory -Force -Path $unrelatedPathTwo | Out-Null
|
||||
$staleReleasePath = Join-Path $releasesDir "0.0.0-deleted-x86_64-pc-windows-msvc"
|
||||
$nestedReleasePath = Join-Path $releaseDir.FullName "nested"
|
||||
$similarReleasePath = "$releasesDir-old\old"
|
||||
$pathBeforeStable = "$installDir;$staleReleasePath;$($releaseDir.FullName);$currentDir;$unrelatedPathOne;$nestedReleasePath;$similarReleasePath;$installDir;$unrelatedPathTwo"
|
||||
$realUserEnvironmentKey = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey("Environment")
|
||||
try {
|
||||
$realUserEnvironmentKey.SetValue("Path", $pathBeforeStable, [Microsoft.Win32.RegistryValueKind]::ExpandString)
|
||||
} finally {
|
||||
$realUserEnvironmentKey.Dispose()
|
||||
}
|
||||
$env:Path = $pathBeforeStable
|
||||
|
||||
$stableManifest | Out-File -LiteralPath $stableManifestPath -Encoding utf8
|
||||
& "$PSScriptRoot\..\distribution\install.ps1" `
|
||||
-Channel stable `
|
||||
-ManifestUrl $stableManifestUrl `
|
||||
-InstallDir $installDir
|
||||
-InstallDir $installDir `
|
||||
-Retain 1
|
||||
$stableReleaseDir = Get-ChildItem -LiteralPath (Join-Path $herdrHome "packages\standalone\releases") -Directory |
|
||||
Where-Object { $_.Name.StartsWith("0.0.1-") } |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $stableReleaseDir) {
|
||||
throw "installer did not install the stable Windows package"
|
||||
}
|
||||
if (Test-Path -LiteralPath $releaseDir.FullName) {
|
||||
throw "installer did not prune the old concrete preview release"
|
||||
}
|
||||
$expectedPath = "$($stableReleaseDir.FullName);$unrelatedPathOne;$nestedReleasePath;$similarReleasePath;$unrelatedPathTwo"
|
||||
$realUserEnvironmentKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey("Environment")
|
||||
try {
|
||||
$actualPath = $realUserEnvironmentKey.GetValue("Path", $null, $registryOptions)
|
||||
if ($actualPath -cne $expectedPath -or $env:Path -cne $expectedPath) {
|
||||
throw "installer did not replace managed PATH entries while preserving unrelated entries"
|
||||
}
|
||||
if ($realUserEnvironmentKey.GetValueKind("Path") -ne [Microsoft.Win32.RegistryValueKind]::ExpandString) {
|
||||
throw "installer changed the real PATH registry value kind"
|
||||
}
|
||||
} finally {
|
||||
$realUserEnvironmentKey.Dispose()
|
||||
}
|
||||
$pathDirectory = Get-Item -LiteralPath ($env:Path.Split(";")[0]) -Force
|
||||
if (($pathDirectory.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
|
||||
-not $pathDirectory.FullName.Equals($stableReleaseDir.FullName, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw "installer PATH does not start with the active concrete release directory"
|
||||
}
|
||||
$resolvedHerdr = Get-Command herdr -CommandType Application -ErrorAction Stop
|
||||
if (-not $resolvedHerdr.Source.Equals(
|
||||
(Join-Path $stableReleaseDir.FullName "herdr.exe"),
|
||||
[System.StringComparison]::OrdinalIgnoreCase
|
||||
)) {
|
||||
throw "installed command does not resolve through the active concrete release"
|
||||
}
|
||||
& herdr --version *> $null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "installed command failed from the concrete release PATH entry"
|
||||
}
|
||||
foreach ($junction in @($installDir, $currentDir)) {
|
||||
$junctionItem = Get-Item -LiteralPath $junction -Force
|
||||
if (-not ($junctionItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
|
||||
-not ([string]$junctionItem.Target).Equals($stableReleaseDir.FullName, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw "installer did not preserve compatibility junction $junction"
|
||||
}
|
||||
}
|
||||
foreach ($relative in $required) {
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $installDir $relative) -PathType Leaf)) {
|
||||
throw "stable installer did not activate required file $relative"
|
||||
}
|
||||
}
|
||||
|
||||
$customPreviewManifestPath = Join-Path $webRoot "candidate.json"
|
||||
$customPreviewManifest = $previewManifest | ConvertFrom-Json
|
||||
$customPreviewManifest.PSObject.Properties.Remove("channel")
|
||||
$customPreviewManifest | ConvertTo-Json -Depth 5 | Out-File -LiteralPath $customPreviewManifestPath -Encoding utf8
|
||||
$fakeBin = Join-Path $root "fake-existing"
|
||||
$fakeInvocationMarker = Join-Path $root "fake-existing-invoked"
|
||||
New-Item -ItemType Directory -Force -Path $fakeBin | Out-Null
|
||||
@'
|
||||
@"
|
||||
@echo off
|
||||
echo invoked>"$fakeInvocationMarker"
|
||||
if "%1"=="channel" if "%2"=="show" (
|
||||
echo preview
|
||||
exit /b 0
|
||||
)
|
||||
exit /b 1
|
||||
'@ | Out-File -LiteralPath (Join-Path $fakeBin "herdr.cmd") -Encoding ascii
|
||||
"@ | Out-File -LiteralPath (Join-Path $fakeBin "herdr.cmd") -Encoding ascii
|
||||
|
||||
$preserveHome = Join-Path $root "preserve-home"
|
||||
$preserveBin = Join-Path $root "preserve-bin"
|
||||
$env:HERDR_HOME = $preserveHome
|
||||
$env:Path = "$fakeBin;$oldProcessPath"
|
||||
& "$PSScriptRoot\..\distribution\install.ps1" `
|
||||
-ManifestUrl "http://127.0.0.1:$port/candidate.json" `
|
||||
-InstallDir $preserveBin `
|
||||
-ExpectedBuildId "installer-test"
|
||||
$preservedPreview = Get-ChildItem -LiteralPath (Join-Path $preserveHome "packages\standalone\releases") -Directory |
|
||||
Where-Object { $_.Name.StartsWith("0.0.0-preview.installer-test-") } |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $preservedPreview) {
|
||||
throw "installer did not preserve the existing preview channel"
|
||||
$unrecognizedCommandRejected = $false
|
||||
try {
|
||||
& "$PSScriptRoot\..\distribution\install.ps1" `
|
||||
-ManifestUrl "http://127.0.0.1:$port/candidate.json" `
|
||||
-InstallDir $preserveBin `
|
||||
-ExpectedBuildId "installer-test"
|
||||
} catch {
|
||||
if ($_.Exception.Message -notlike "Refusing to run unrecognized Herdr command*") {
|
||||
throw
|
||||
}
|
||||
$unrecognizedCommandRejected = $true
|
||||
}
|
||||
if (-not $unrecognizedCommandRejected) {
|
||||
throw "installer accepted implicit channel detection through an unrecognized command"
|
||||
}
|
||||
if (Test-Path -LiteralPath $fakeInvocationMarker) {
|
||||
throw "installer invoked an unrecognized command during implicit channel detection"
|
||||
}
|
||||
|
||||
& "$PSScriptRoot\..\distribution\install.ps1" `
|
||||
@@ -484,7 +613,10 @@ exit /b 1
|
||||
Where-Object { $_.Name.StartsWith("0.0.1-") } |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $explicitStable) {
|
||||
throw "explicit stable channel did not override the existing preview channel"
|
||||
throw "explicit stable channel did not install past the unrecognized command"
|
||||
}
|
||||
if (Test-Path -LiteralPath $fakeInvocationMarker) {
|
||||
throw "installer invoked an unrecognized command despite an explicit channel"
|
||||
}
|
||||
} finally {
|
||||
$env:HERDR_HOME = $oldHerdrHome
|
||||
@@ -493,5 +625,15 @@ exit /b 1
|
||||
if ($null -ne $server -and -not $server.HasExited) {
|
||||
Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
$realUserEnvironmentKey = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey("Environment")
|
||||
try {
|
||||
if ($realUserPathExisted) {
|
||||
$realUserEnvironmentKey.SetValue("Path", $realUserPath, $realUserPathKind)
|
||||
} else {
|
||||
$realUserEnvironmentKey.DeleteValue("Path", $false)
|
||||
}
|
||||
} finally {
|
||||
$realUserEnvironmentKey.Dispose()
|
||||
}
|
||||
Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
+1
-1
@@ -2188,7 +2188,7 @@ pub fn self_update(options: SelfUpdateOptions) -> Result<Version, String> {
|
||||
eprintln!("installed {}", release.label());
|
||||
print_outdated_integration_notice_with_updated_binary(&updated_exe);
|
||||
eprintln!(
|
||||
"Start Herdr again to use the updated client. Running servers remain active; restart them later only if you need server-side changes from {}.",
|
||||
"Open a new terminal, or reconnect SSH, then start Herdr again to use the updated client. Running servers remain active; restart them later only if you need server-side changes from {}.",
|
||||
release.label()
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user