3 Commits
Author SHA1 Message Date
fourbroad e3098f2225 ci: sync from octopus@0.15.0 2026-07-05 13:45:03 +08:00
fourbroadandGitea Server e79718a5fb Merge pull request 'fix(install): do not kill PowerShell host under 'irm | iex'' (#1) from workflow/bugfix/install-exit-kills-iex-host into main
Reviewed-on: http://octopus.51zxtx.com:3000/fourbroad/octopus-release/pulls/1
2026-07-02 09:54:18 +08:00
octopus-agent fbac1003f0 fix(install): do not kill PowerShell host under 'irm | iex'
The Windows installer used 'exit' inside Install-Octopus. When run via
'irm <url> | iex' (the documented one-liner), there is no script scope, so
'exit' terminates the entire PowerShell host process and closes the terminal
window instead of just ending the install. Most users hit this via the
'already installed' branch (exit 0).

Replace 'exit' with:
  - 'return' for the success (already-installed) path
  - 'throw' for error paths (still non-zero exit when run as .\install.ps1
    in CI; prints the error and returns to the prompt under iex)

Add tests/test.ps1 as a regression guard (source-verification + a behavioral
test proving iex no longer terminates the host).
2026-07-02 09:10:03 +08:00
+75
View File
@@ -0,0 +1,75 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Regression tests for install.ps1.
.BUG
`irm <url> | iex` closed the terminal window. Cause: the script used
`exit`, which under Invoke-Expression terminates the entire PowerShell
host process (there is no script scope to exit). Fix: use `return`
(success) / `throw` (errors) so the host survives.
.NOTES
Host-process termination cannot be observed from inside the dying process,
so these tests combine a source-verification guard with a behavioral test
run in a child process (`exit` cannot be caught by try/catch and kills the
child; `throw` is catchable).
#>
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent $PSScriptRoot
$installScript = Join-Path $repoRoot "install.ps1"
$failures = [System.Collections.Generic.List[string]]::new()
function Assert-True([bool]$Cond, [string]$Message) {
if (-not $Cond) { $failures.Add($Message) | Out-Null }
}
# --- Test 1 (source verification): no host-terminating `exit` ---
$content = Get-Content -Path $installScript -Raw
$exitCount = ([regex]::Matches($content, '(?m)^\s*exit\b')).Count
Assert-True ($exitCount -eq 0) "install.ps1 must not use bare 'exit' (found $exitCount); 'irm | iex' would kill the host. Use return/throw."
# --- Test 2: parses cleanly ---
$parseOk = $true
try { [void][scriptblock]::Create($content) } catch { $parseOk = $false }
Assert-True $parseOk "install.ps1 must parse without syntax errors."
# --- Test 3 (behavioral): iex must NOT kill the host ---
# Force the version lookup to fail fast against an unreachable address so the
# script reaches its early-exit/throw path offline. `exit` terminates the child
# (RESULT never emitted); `throw` is caught and RESULT is emitted.
$inner = @'
$ErrorActionPreference = "Stop"
$env:OCTOPUS_DOWNLOAD_BASE = "http://127.0.0.1:1"
$r = "nothing"
try {
Invoke-Expression (Get-Content -Raw "__INSTALL__")
$r = "completed"
} catch {
$r = "caught-throw"
}
Write-Output ("RESULT=$r")
'@.Replace("__INSTALL__", $installScript)
$tmp = Join-Path $env:TEMP ("octopus_iex_test_" + $PID + ".ps1")
Set-Content -Path $tmp -Value $inner -Encoding UTF8
try {
$childOut = (& powershell -NoProfile -File $tmp 2>&1 | Out-String)
} finally {
Remove-Item $tmp -ErrorAction SilentlyContinue
}
Assert-True ($childOut -match 'RESULT=') "iex must not terminate the host (try/catch must observe a RESULT). Child output:`n$childOut"
# --- Report ---
if ($failures.Count -gt 0) {
Write-Host ("FAIL: " + $failures.Count + " test(s)") -ForegroundColor Red
foreach ($f in $failures) { Write-Host (" - " + $f) -ForegroundColor Red }
exit 1
}
Write-Host "PASS: all regression tests passed." -ForegroundColor Green
exit 0