From 8959d9fdb62876827a484614d469596ab58887d9 Mon Sep 17 00:00:00 2001 From: Kelly Michels Date: Wed, 19 Aug 2026 11:21:04 -0500 Subject: [PATCH] fix(zdeploy): stop passing ssh -n to scp, which rejects it (#52) 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 --- CHANGELOG.md | 13 +++++++++++++ CHANGELOG.txt | 13 +++++++++++++ ZHelpers.ps1 | 12 ++++++++++++ zdeploy.ps1 | 11 ++++++----- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df425d0..bfbdcbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,19 @@ Notable changes to the Evomedia.net Token Savers. ## 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 - **Deploys can no longer hang forever on an ssh prompt** — every deploy-path `ssh`/`scp` now carries `BatchMode=yes` plus connect and diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 5a4bd0e..357875b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -9,6 +9,19 @@ Notable changes to the Evomedia.net Token Savers. 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 ----- diff --git a/ZHelpers.ps1 b/ZHelpers.ps1 index 7e71736..ba2d370 100644 --- a/ZHelpers.ps1 +++ b/ZHelpers.ps1 @@ -142,6 +142,7 @@ function Get-Ec2Home { # rebooting) errors out in about a minute instead of hanging indefinitely. function Get-Ec2SshOpts { return @( + '-n', '-o', 'StrictHostKeyChecking=no', '-o', 'BatchMode=yes', '-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. function Invoke-Ec2Step { param( diff --git a/zdeploy.ps1 b/zdeploy.ps1 index 0637a94..3bbca14 100644 --- a/zdeploy.ps1 +++ b/zdeploy.ps1 @@ -53,6 +53,7 @@ $SSH_TARGET = Get-Ec2Target $RemoteHome = Get-Ec2Home $Ec2User = $cfg.ec2.user $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 if (-not (Test-Path -LiteralPath $TempRoot)) { @@ -148,9 +149,9 @@ function Invoke-Ec2PostDeployCleanup { function Send-DeployZip { 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) { - 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' }) foreach ($f in $files) { 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)" } } @@ -667,7 +668,7 @@ function Invoke-EdgeDeploy { $dirs = @(Get-ChildItem -LiteralPath $root -Directory | Where-Object { $skipDirs -notcontains $_.Name }) foreach ($d in $dirs) { 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)" } } @@ -702,7 +703,7 @@ function Invoke-DockerDeploy { $files = @(Get-ChildItem -LiteralPath $root -File -Force | Where-Object { $_.Name -ne 'nul' }) foreach ($f in $files) { 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)" } }