fix(zdeploy): stop passing ssh -n to scp, which rejects it

Mirrors evo.scripts #66, which added -n to Get-Ec2SshOpts so a deploy step
cannot block on inherited stdin, plus the follow-up that keeps that flag away
from scp. OpenSSH's scp has no -n: it exits 1 with 'unknown option -- n' and
prints its usage block, so every upload failed once the shared option array
reached it.

Get-Ec2ScpOpts is derived from Get-Ec2SshOpts with -n filtered out rather than
duplicated, so the connect and keepalive timeouts cannot drift apart between
the two transports. All four scp call sites here use it - three plain uploads
and the recursive directory upload, which the private tree does not have.

The upload failure message asserted 'Likely server disk space' without checking
anything; it now points at scp's own output, where the real diagnosis already
was.

Verified: both files parse clean, Get-Ec2ScpOpts returns the five -o pairs with
no -n, and the added lines carry no real hosts, keys, or paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
KellyMichels 2026-08-19 11:05:16 -05:00
parent d99f8a1312
commit 7853760a92
4 changed files with 44 additions and 5 deletions

View File

@ -10,6 +10,19 @@ Notable changes to the Evomedia.net Token Savers.
## Unreleased ## Unreleased
### Fixed
- **`scp` no longer receives an ssh-only flag** — the stdin-hang fix added
`-n` to `Get-Ec2SshOpts`, and the deploy path splats that same array into
`scp` as well as `ssh`. OpenSSH's `scp` has no `-n`: it exits 1 with
`unknown option -- n` and prints its usage block, so every upload failed.
`Get-Ec2ScpOpts` now supplies the shared connection options with `-n`
filtered out, derived from `Get-Ec2SshOpts` rather than duplicated so the
timeouts cannot drift apart between the two transports. All four `scp`
call sites use it, including the recursive directory upload.
The upload failure message also asserted "Likely server disk space"
without checking; it now points at `scp`'s own output, where the real
diagnosis already was.
### Fixed ### Fixed
- **Deploys can no longer hang forever on an ssh prompt** — every - **Deploys can no longer hang forever on an ssh prompt** — every
deploy-path `ssh`/`scp` now carries `BatchMode=yes` plus connect and deploy-path `ssh`/`scp` now carries `BatchMode=yes` plus connect and

View File

@ -9,6 +9,19 @@ Notable changes to the Evomedia.net Token Savers.
Unreleased Unreleased
---------- ----------
Fixed
- scp no longer receives an ssh-only flag — the stdin-hang fix added
-n to Get-Ec2SshOpts, and the deploy path splats that same array into
scp as well as ssh. OpenSSH's scp has no -n: it exits 1 with
unknown option -- n and prints its usage block, so every upload failed.
Get-Ec2ScpOpts now supplies the shared connection options with -n
filtered out, derived from Get-Ec2SshOpts rather than duplicated so the
timeouts cannot drift apart between the two transports. All four scp
call sites use it, including the recursive directory upload.
The upload failure message also asserted "Likely server disk space"
without checking; it now points at scp's own output, where the real
diagnosis already was.
Fixed Fixed
----- -----

View File

@ -142,6 +142,7 @@ function Get-Ec2Home {
# rebooting) errors out in about a minute instead of hanging indefinitely. # rebooting) errors out in about a minute instead of hanging indefinitely.
function Get-Ec2SshOpts { function Get-Ec2SshOpts {
return @( return @(
'-n',
'-o', 'StrictHostKeyChecking=no', '-o', 'StrictHostKeyChecking=no',
'-o', 'BatchMode=yes', '-o', 'BatchMode=yes',
'-o', 'ConnectTimeout=15', '-o', 'ConnectTimeout=15',
@ -150,6 +151,17 @@ function Get-Ec2SshOpts {
) )
} }
# The same options for scp, which does NOT accept -n: OpenSSH's scp exits 1 with
# "unknown option -- n" and prints its usage block. That failure is easy to
# misread, because the caller's own error text is what the operator sees while
# the usage text scrolls past above it.
#
# Derived from Get-Ec2SshOpts rather than duplicated, so the timeouts can never
# drift apart between the two transports.
function Get-Ec2ScpOpts {
return @(Get-Ec2SshOpts | Where-Object { $_ -ne '-n' })
}
# Run one bash command on the server; throw on non-zero exit. # Run one bash command on the server; throw on non-zero exit.
function Invoke-Ec2Step { function Invoke-Ec2Step {
param( param(

View File

@ -53,6 +53,7 @@ $SSH_TARGET = Get-Ec2Target
$RemoteHome = Get-Ec2Home $RemoteHome = Get-Ec2Home
$Ec2User = $cfg.ec2.user $Ec2User = $cfg.ec2.user
$SSH_OPTS = Get-Ec2SshOpts # see ZHelpers.ps1 - these are what stop a deploy hanging $SSH_OPTS = Get-Ec2SshOpts # see ZHelpers.ps1 - these are what stop a deploy hanging
$SCP_OPTS = Get-Ec2ScpOpts # same, minus -n: scp rejects it with a usage error
$TempRoot = $cfg.paths.temp $TempRoot = $cfg.paths.temp
if (-not (Test-Path -LiteralPath $TempRoot)) { if (-not (Test-Path -LiteralPath $TempRoot)) {
@ -148,9 +149,9 @@ function Invoke-Ec2PostDeployCleanup {
function Send-DeployZip { function Send-DeployZip {
param([string]$LocalZip, [string]$ZipName) param([string]$LocalZip, [string]$ZipName)
scp @SSH_OPTS -i $PEM_KEY $LocalZip "${SSH_TARGET}:$RemoteHome/" scp @SCP_OPTS -i $PEM_KEY $LocalZip "${SSH_TARGET}:$RemoteHome/"
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
throw "SCP upload failed (exit $LASTEXITCODE). Likely server disk space. Try: rm -f $RemoteHome/$ZipName" throw "SCP upload failed (exit $LASTEXITCODE). Read scp's own output above: a usage block means bad arguments, not disk. If it is genuinely full, clear the stale archive: rm -f $RemoteHome/$ZipName"
} }
} }
@ -655,7 +656,7 @@ function Invoke-EdgeDeploy {
$files = @(Get-ChildItem -LiteralPath $root -File | Where-Object { $_.Name -ne 'nul' }) $files = @(Get-ChildItem -LiteralPath $root -File | Where-Object { $_.Name -ne 'nul' })
foreach ($f in $files) { foreach ($f in $files) {
Write-Host " >> uploading $($f.Name)" -ForegroundColor DarkCyan Write-Host " >> uploading $($f.Name)" -ForegroundColor DarkCyan
scp @SSH_OPTS -i $PEM_KEY $f.FullName "${SSH_TARGET}:$remotePath/" scp @SCP_OPTS -i $PEM_KEY $f.FullName "${SSH_TARGET}:$remotePath/"
if ($LASTEXITCODE -ne 0) { throw "SCP failed for $($f.Name) (exit $LASTEXITCODE)" } if ($LASTEXITCODE -ne 0) { throw "SCP failed for $($f.Name) (exit $LASTEXITCODE)" }
} }
@ -667,7 +668,7 @@ function Invoke-EdgeDeploy {
$dirs = @(Get-ChildItem -LiteralPath $root -Directory | Where-Object { $skipDirs -notcontains $_.Name }) $dirs = @(Get-ChildItem -LiteralPath $root -Directory | Where-Object { $skipDirs -notcontains $_.Name })
foreach ($d in $dirs) { foreach ($d in $dirs) {
Write-Host " >> uploading $($d.Name)/ (recursive)" -ForegroundColor DarkCyan Write-Host " >> uploading $($d.Name)/ (recursive)" -ForegroundColor DarkCyan
scp -r @SSH_OPTS -i $PEM_KEY $d.FullName "${SSH_TARGET}:$remotePath/" scp -r @SCP_OPTS -i $PEM_KEY $d.FullName "${SSH_TARGET}:$remotePath/"
if ($LASTEXITCODE -ne 0) { throw "SCP failed for $($d.Name) (exit $LASTEXITCODE)" } if ($LASTEXITCODE -ne 0) { throw "SCP failed for $($d.Name) (exit $LASTEXITCODE)" }
} }
@ -702,7 +703,7 @@ function Invoke-DockerDeploy {
$files = @(Get-ChildItem -LiteralPath $root -File -Force | Where-Object { $_.Name -ne 'nul' }) $files = @(Get-ChildItem -LiteralPath $root -File -Force | Where-Object { $_.Name -ne 'nul' })
foreach ($f in $files) { foreach ($f in $files) {
Write-Host " >> uploading $($f.Name)" -ForegroundColor DarkCyan Write-Host " >> uploading $($f.Name)" -ForegroundColor DarkCyan
scp @SSH_OPTS -i $PEM_KEY $f.FullName "${SSH_TARGET}:$remotePath/" scp @SCP_OPTS -i $PEM_KEY $f.FullName "${SSH_TARGET}:$remotePath/"
if ($LASTEXITCODE -ne 0) { throw "SCP failed for $($f.Name) (exit $LASTEXITCODE)" } if ($LASTEXITCODE -ne 0) { throw "SCP failed for $($f.Name) (exit $LASTEXITCODE)" }
} }