refactor(deploy): stream shell scripts over SSH and support binary-only updates

This commit is contained in:
lda committed 2026-10-19 18:44:12 +07:00 Verified
1 parent eb55554806
commit 134738cd32
6 files changed
+319 -131

No files matched your search

+4
View File
@@ -43,6 +43,10 @@ This folder has small helpers for build, CI, and router install. Keep it simple;
- `dev_push.ps1` — Fast dev loop: build + upload to router. Usage:
- `./scripts/dev_push.ps1 -Pass <password> [-HostName <ip>] [-RemotePath </root/.bin/wakey>] [-Restart] [-Quiet]`
- Uploads to `<RemotePath>.tmp` then atomically moves into place; `-Restart` restarts the service; `-Quiet` silences MOTD.
- Defaults to both binaries plus init/hooks, with restart enabled. Use `-Restart:$false` to leave the service running, or `-Quiet:$false` for deployment output. Config files are untouched.
- Binary-only agent update: `./scripts/dev_push.ps1 -HostName router -BinariesOnly -Package wakey-agent -Quiet:$false`
- Use `-BinariesOnly` without `-Package` for both binaries, or `-Package wakey` for only the CLI. Cargo still builds the selected package's dependencies.
- The checked-in `remote_deploy_wakey.sh` is streamed over SSH to `sh -s -- --batch ...` in all modes, with the deployment plan passed as quoted arguments. No remote helper is uploaded or required. Its original single-binary invocation remains supported. The shared `Invoke-Ssh -Cmd 'sh -s' -Stdin $script` supports stdin scripts for other callers.
- `package_rootfs.ps1` — Produces `dist/wakey-rootfs-<version>-<target>.tgz` with `/root/.bin/wakey` and `/etc/init.d/*`.
- `update_wakey_cc.sh` — Linux VPS updater for control-plane bundles; extracts into the selected directory (default: current directory) and restarts `wakey-cc.service`.
- `update_wakey_cc_from_repo.sh` — Linux VPS source updater; clones repo to temp dir, builds UI + control-plane locally, packages bundle tgz, installs into selected directory, and restarts `wakey-cc.service`.
+80 -95
View File
@@ -4,6 +4,9 @@
param(
[ValidateSet("cargo", "cross")]
[string]$Cargo = "cargo",
[ValidateSet("wakey", "wakey-agent")]
[ValidateNotNullOrEmpty()]
[string[]]$Package = @("wakey", "wakey-agent"),
[string]$Pass,
[string]$HostName = "192.168.100.1",
[int]$Port = 22,
@@ -17,6 +20,7 @@ param(
[string]$HostKey,
[switch]$ForcePassword,
[switch]$SkipInitScript,
[switch]$BinariesOnly,
[switch]$Restart = $true,
[switch]$Quiet = $true
)
@@ -26,109 +30,90 @@ try { $PSStyle.OutputRendering = 'Host' } catch {}
. "$PSScriptRoot/lib.ps1"
$Pass = Get-DefaultPassword $Pass
function Build-Packages {
param([string[]]$Packages)
$buildArgs = @('build', '--release', '--target', $Target)
foreach ($pkg in $Packages) { $buildArgs += @('-p', $pkg) }
Write-Host "[build] $Cargo $($buildArgs -join ' ')" -ForegroundColor Cyan
& $Cargo @buildArgs
if ($LASTEXITCODE -ne 0) { throw "$Cargo build failed ($LASTEXITCODE)" }
}
function New-DeploymentPlan {
param([string[]]$Packages)
# Keep CLI first so replacing both binaries restarts the service only once.
$binaries = @(
@{ Package = 'wakey'; Name = $BinName; Destination = $RemotePath }
@{ Package = 'wakey-agent'; Name = $AgentBinName; Destination = $AgentRemotePath }
) | Where-Object { $_.Package -in $Packages } | ForEach-Object {
[pscustomobject]@{
Local = Join-Path $repoRoot "target/$Target/release/$($_.Name)"
Temp = "$($_.Destination).tmp"
Destination = $_.Destination
}
}
$support = @(
if (-not $BinariesOnly) {
if (-not $SkipInitScript) {
@{ Source = 'init/openwrt/wakey'; Temp = '/var/tmp/wakey.init.tmp'; Destination = $RemoteInitPath }
}
@{ Source = 'hotplug/openwrt/dhcp/95-wakey'; Temp = '/var/tmp/wakey.hotplug.dhcp.tmp'; Destination = '/etc/hotplug.d/dhcp/95-wakey' }
@{ Source = 'hotplug/openwrt/neigh/95-wakey'; Temp = '/var/tmp/wakey.hotplug.neigh.tmp'; Destination = '/etc/hotplug.d/neigh/95-wakey' }
}
) | ForEach-Object {
$local = Join-Path $PSScriptRoot $_.Source
if (Test-Path -LiteralPath $local) {
[pscustomobject]@{ Local = $local; Temp = $_.Temp; Destination = $_.Destination }
}
}
foreach ($binary in $binaries) {
if (-not (Test-Path -LiteralPath $binary.Local)) { throw "binary not found: $($binary.Local)" }
}
[pscustomobject]@{ Binaries = @($binaries); Support = @($support) }
}
function Get-RemoteDeployment {
param($Plan)
$deployArgs = @('--batch')
for ($i = 0; $i -lt $Plan.Binaries.Count; $i++) {
$binary = $Plan.Binaries[$i]
$restartFlag = if ($Restart -and $i -eq $Plan.Binaries.Count - 1) { '1' } else { '0' }
$deployArgs += @('--binary', $binary.Temp, $binary.Destination, $restartFlag)
}
foreach ($file in $Plan.Support) {
$deployArgs += @('--install', $file.Temp, $file.Destination)
}
# Only data needs shell quoting; the checked-in shell program travels as stdin.
$command = 'sh -s -- ' + (($deployArgs | ForEach-Object { Quote-ShArg $_ }) -join ' ')
if ($Quiet) { $command += ' >/dev/null 2>&1' }
[pscustomobject]@{
Script = Get-Content -Raw -LiteralPath "$PSScriptRoot/remote_deploy_wakey.sh"
Command = $command
}
}
function Send-DeploymentFiles {
param($Plan)
foreach ($file in @($Plan.Binaries) + @($Plan.Support)) {
Invoke-Scp -Local $file.Local -Dest "$User@${HostName}:$($file.Temp)" -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
}
}
$Pass = Get-DefaultPassword $Pass
# Backward compatibility: allow HostName in the form host:port.
$hostPort = Split-HostPort -HostName $HostName -DefaultPort $Port
$HostName = $hostPort.Host
$Port = $hostPort.Port
function Get-DeployScript {
param($DeployPreferred, $DeployTmp, $RemoteTmp, $RemotePath, $RestartFlag)
return @"
DEPLOY=$DeployPreferred;
if [ ! -x "`$DEPLOY" ] && [ -f $DeployTmp ]; then
sed -i "s/\r$//" $DeployTmp 2>/dev/null || true
chmod +x $DeployTmp
DEPLOY=$DeployTmp
fi
sh "`$DEPLOY" $RemoteTmp $RemotePath $RestartFlag
"@
}
#---- Main Flow ----
$repoRoot = Split-Path -Parent $PSScriptRoot
Push-Location $repoRoot
try {
Write-Host "[build] $Cargo build --release --target $Target -p wakey -p wakey-agent" -ForegroundColor Cyan
. "$Cargo" build --release --target $Target -p wakey -p wakey-agent
if ($LASTEXITCODE -ne 0) { throw "$Cargo build failed ($LASTEXITCODE)" }
$localBin = Join-Path $repoRoot "target/$Target/release/$BinName"
$localAgentBin = Join-Path $repoRoot "target/$Target/release/$AgentBinName"
if (-not (Test-Path $localBin)) { throw "binary not found: $localBin" }
if (-not (Test-Path $localAgentBin)) { throw "binary not found: $localAgentBin" }
$remoteTmp = "$RemotePath.tmp"
$agentRemoteTmp = "$AgentRemotePath.tmp"
$destTmp = "$User@${HostName}:$remoteTmp"
$agentDestTmp = "$User@${HostName}:$agentRemoteTmp"
$initTmp = '/var/tmp/wakey.init.tmp'
$initDestTmp = "$User@${HostName}:$initTmp"
$localDeploy = Join-Path $repoRoot 'scripts/remote_deploy_wakey.sh'
$localInit = Join-Path $repoRoot 'scripts/init/openwrt/wakey'
$localDhcpHotplug = Join-Path $repoRoot 'scripts/hotplug/openwrt/dhcp/95-wakey'
$localNeighHotplug = Join-Path $repoRoot 'scripts/hotplug/openwrt/neigh/95-wakey'
$deployTmp = '/var/tmp/remote_deploy_wakey.sh'
$deployPreferred = '/root/.bin/remote_deploy_wakey.sh'
$dhcpHotplugTmp = '/var/tmp/wakey.hotplug.dhcp.tmp'
$neighHotplugTmp = '/var/tmp/wakey.hotplug.neigh.tmp'
# Push binaries
Invoke-Scp -Local $localBin -Dest $destTmp -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
Invoke-Scp -Local $localAgentBin -Dest $agentDestTmp -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
# Push OpenWrt init script unless explicitly skipped
if (-not $SkipInitScript -and (Test-Path $localInit)) {
Invoke-Scp -Local $localInit -Dest $initDestTmp -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
}
if (Test-Path $localDhcpHotplug) {
Invoke-Scp -Local $localDhcpHotplug -Dest "$User@${HostName}:$dhcpHotplugTmp" -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
}
if (Test-Path $localNeighHotplug) {
Invoke-Scp -Local $localNeighHotplug -Dest "$User@${HostName}:$neighHotplugTmp" -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
}
# Push deploy helper if exists
if (Test-Path $localDeploy) {
Invoke-Scp -Local $localDeploy -Dest "$User@${HostName}:$deployTmp" -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
}
# Build and run remote deploy command
$restartFlag = $(if ($Restart) { '1' } else { '0' })
$script = @"
$(Get-DeployScript $deployPreferred $deployTmp $remoteTmp $RemotePath 0)
$(Get-DeployScript $deployPreferred $deployTmp $agentRemoteTmp $AgentRemotePath $restartFlag)
$(if (-not $SkipInitScript) {
@"
if [ -f $initTmp ]; then
sed -i "s/\r$//" $initTmp 2>/dev/null || true
if command -v install >/dev/null 2>&1; then
install -m 0755 $initTmp $RemoteInitPath
else
cp -f $initTmp $RemoteInitPath
chmod 0755 $RemoteInitPath
fi
fi
"@
})
if [ -f $dhcpHotplugTmp ]; then
sed -i "s/\r$//" $dhcpHotplugTmp 2>/dev/null || true
mkdir -p /etc/hotplug.d/dhcp
cp -f $dhcpHotplugTmp /etc/hotplug.d/dhcp/95-wakey
fi
if [ -f $neighHotplugTmp ]; then
sed -i "s/\r$//" $neighHotplugTmp 2>/dev/null || true
mkdir -p /etc/hotplug.d/neigh
cp -f $neighHotplugTmp /etc/hotplug.d/neigh/95-wakey
fi
"@
$remoteCmd = "sh -lc '$($script -replace "`r",'')'"
if ($Quiet) { $remoteCmd += " >/dev/null 2>&1" }
Invoke-Ssh -Cmd $remoteCmd -User $User -Remote $HostName -Pass $Pass -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
Build-Packages ($Package | Select-Object -Unique)
$plan = New-DeploymentPlan $Package
# Prepare before uploading so missing local helpers fail without remote changes.
$deployment = Get-RemoteDeployment $plan
Send-DeploymentFiles $plan
Invoke-Ssh -Cmd $deployment.Command -Stdin $deployment.Script -User $User -Remote $HostName -Pass $Pass -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
Write-Host "done ✔" -ForegroundColor Green
}
finally {
+17 -6
View File
@@ -191,7 +191,7 @@ function Get-SshpassCommand {
}
function Invoke-Ext {
param($Exe, $Arguments, $Label)
param($Exe, $Arguments, $Label, [string]$Stdin)
$displayArgs = $Arguments.Clone()
for ($i = 0; $i -lt $displayArgs.Count; $i++) {
if ($displayArgs[$i] -eq '-pw' -and ($i + 1) -lt $displayArgs.Count) {
@@ -203,7 +203,12 @@ function Invoke-Ext {
}
Write-Host ("[{0}] {1} {2}" -f $Label, $Exe, ($displayArgs -join ' ')) -ForegroundColor Cyan
$out = & $Exe @Arguments 2>&1
# Keep stdin optional so interactive callers retain their existing behavior.
if ($PSBoundParameters.ContainsKey('Stdin')) {
$out = $Stdin | & $Exe @Arguments 2>&1
} else {
$out = & $Exe @Arguments 2>&1
}
if ($LASTEXITCODE -ne 0) {
if ($Exe -like '*sshpass*' -and $LASTEXITCODE -eq 6) {
Write-Error ("{0} failed: sshpass could not confirm host key automatically (code 6). Use ssh once manually, or enable StrictHostKeyChecking=accept-new." -f $Label)
@@ -284,8 +289,14 @@ function Invoke-Scp {
function Invoke-Ssh {
param($Cmd, $User, $Remote, $Pass, [int]$Port = 22, [switch]$Quiet, [switch]$ForcePassword)
# Supply Stdin for scripts, e.g. -Cmd 'sh -s' -Stdin $script. This avoids
# embedding a whole script in a remote shell command's quoting layer.
param($Cmd, $User, $Remote, $Pass, [int]$Port = 22, [switch]$Quiet, [switch]$ForcePassword, [string]$Stdin)
$Cmd = Normalize-LineEndings $Cmd
$inputArgs = @{}
if ($PSBoundParameters.ContainsKey('Stdin')) {
$inputArgs.Stdin = Normalize-LineEndings $Stdin
}
$isWin = [bool]$IsWindows
$isWsl = Test-IsWsl
@@ -297,7 +308,7 @@ function Invoke-Ssh {
if ($Pass) { $arguments += @('-pw', $Pass) }
$dest = if ($User) { "$User@$Remote" } else { $Remote }
$arguments += $dest, $Cmd
Invoke-Ext -Exe $plink.Path -Arguments $arguments -Label 'ssh'
Invoke-Ext -Exe $plink.Path -Arguments $arguments -Label 'ssh' @inputArgs
}
else {
$arguments = @()
@@ -319,7 +330,7 @@ function Invoke-Ssh {
$sshpassExe = Get-SshpassCommand
if ($sshpassExe) {
$wrapped = @('-p', $Pass, 'ssh') + $arguments
Invoke-Ext -Exe $sshpassExe -Arguments $wrapped -Label 'ssh'
Invoke-Ext -Exe $sshpassExe -Arguments $wrapped -Label 'ssh' @inputArgs
return
}
if (-not $script:WarnedNoSshpass) {
@@ -338,7 +349,7 @@ function Invoke-Ssh {
throw 'ForcePassword requires sshpass'
}
Invoke-Ext -Exe 'ssh' -Arguments $arguments -Label 'ssh'
Invoke-Ext -Exe 'ssh' -Arguments $arguments -Label 'ssh' @inputArgs
}
}
+69 -30
View File
@@ -1,41 +1,80 @@
#!/bin/sh
# Usage: remote_deploy_wakey.sh <bin_tmp> <dest_path> [restart_flag]
# restart_flag: 1 (default) to stop/start, 0 to only replace file.
# Or stream over SSH: sh -s -- --batch [--binary <tmp> <dest> <restart> |
# --install <tmp> <dest>]...
set -e
BIN_TMP="$1"
DEST="$2"
RESTART="${3:-1}"
INIT="/etc/init.d/wakey"
KILL="/root/.bin/kill_wakey.sh"
if [ -z "$BIN_TMP" ] || [ -z "$DEST" ]; then
echo "usage: $0 <bin_tmp> <dest_path> [restart_flag]" >&2
exit 2
fi
# A subshell keeps per-binary variables and exit statements local, including
# when several replacements are requested by one dev_push invocation.
deploy_binary() (
BIN_TMP="$1"
DEST="$2"
RESTART="${3:-1}"
INIT="/etc/init.d/wakey"
KILL="/root/.bin/kill_wakey.sh"
[ -f "$BIN_TMP" ] || {
echo "tmp binary not found: $BIN_TMP" >&2
exit 1
if [ -z "$BIN_TMP" ] || [ -z "$DEST" ]; then
echo "usage: $0 <bin_tmp> <dest_path> [restart_flag]" >&2
exit 2
fi
[ -f "$BIN_TMP" ] || {
echo "tmp binary not found: $BIN_TMP" >&2
exit 1
}
chmod +x "$BIN_TMP" || true
if [ "$RESTART" = "1" ]; then
if [ -x "$INIT" ]; then
"$INIT" stop || true
elif [ -x "$KILL" ]; then
sh "$KILL" || true
fi
fi
mv -f "$BIN_TMP" "$DEST"
if [ "$RESTART" = "1" ]; then
if [ -x "$INIT" ]; then
"$INIT" start || "$INIT" restart || true
else
nohup "$DEST" >/dev/null 2>&1 &
fi
fi
exit 0
)
install_support_file() {
sed -i 's/\r$//' "$1" 2>/dev/null || true
mkdir -p "$(dirname "$2")"
cp -f "$1" "$2"
chmod 0755 "$2"
}
chmod +x "$BIN_TMP" || true
if [ "$RESTART" = "1" ]; then
if [ -x "$INIT" ]; then
"$INIT" stop || true
elif [ -x "$KILL" ]; then
sh "$KILL" || true
fi
if [ "${1:-}" != '--batch' ]; then
deploy_binary "$@"
exit
fi
mv -f "$BIN_TMP" "$DEST"
if [ "$RESTART" = "1" ]; then
if [ -x "$INIT" ]; then
"$INIT" start || "$INIT" restart || true
else
nohup "$DEST" >/dev/null 2>&1 &
fi
fi
exit 0
shift
while [ "$#" -gt 0 ]; do
case "$1" in
--binary)
[ "$#" -ge 4 ] || { echo '--binary requires tmp, destination, restart' >&2; exit 2; }
deploy_binary "$2" "$3" "$4"
shift 4
;;
--install)
[ "$#" -ge 3 ] || { echo '--install requires tmp and destination' >&2; exit 2; }
install_support_file "$2" "$3"
shift 3
;;
*)
echo "unknown deployment operation: $1" >&2
exit 2
;;
esac
done
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env pwsh
# Run with pwsh -NoProfile -File scripts/tests/dev_push.ps1. No router is contacted.
$ErrorActionPreference = 'Stop'
$repo = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$fixture = Join-Path ([IO.Path]::GetTempPath()) "wakey-dev-push-$([guid]::NewGuid())"
function Assert($condition, $message) {
if (-not $condition) { throw $message }
}
try {
New-Item -ItemType Directory -Path "$fixture/scripts" -Force | Out-Null
Copy-Item "$repo/scripts/dev_push.ps1" "$fixture/scripts/dev_push.ps1"
Copy-Item "$repo/scripts/lib.ps1" "$fixture/scripts/actual-lib.ps1"
foreach ($file in @('remote_deploy_wakey.sh', 'init/openwrt/wakey', 'hotplug/openwrt/dhcp/95-wakey', 'hotplug/openwrt/neigh/95-wakey')) {
$dest = "$fixture/scripts/$file"
New-Item -ItemType Directory -Path (Split-Path -Parent $dest) -Force | Out-Null
Copy-Item "$repo/scripts/$file" $dest
}
# Override only external operations; keep path resolution and quoting real.
@'
. "$PSScriptRoot/actual-lib.ps1"
function cargo {
$global:DeployTest.Build = @($args)
$global:LASTEXITCODE = $global:DeployTest.BuildExit
}
function Invoke-Scp {
param($Local, $Dest, $Pass, $HostKey, $Port, [switch]$Quiet, [switch]$ForcePassword)
$global:DeployTest.Uploads.Add([pscustomobject]@{ Local = $Local; Dest = $Dest })
if ($global:DeployTest.Execute) {
Copy-Item -LiteralPath $Local -Destination ($Dest -split ':', 2)[1]
}
}
function Invoke-Ssh {
param($Cmd, $User, $Remote, $Pass, $Port, [switch]$Quiet, [switch]$ForcePassword, $Stdin)
$global:DeployTest.Command = $Cmd
$global:DeployTest.Body = $Stdin
if (-not $Cmd.StartsWith('sh -s -- ')) { throw 'Deployment must use SSH stdin with sh -s' }
# Parse exactly what would reach the remote shell, without executing it.
$Stdin | & sh -n
if ($LASTEXITCODE -ne 0) { throw 'Generated deployment shell is invalid' }
if ($global:DeployTest.Execute) {
$Stdin | & sh -c $Cmd
if ($LASTEXITCODE -ne 0) { throw 'Local fixture deployment failed' }
}
}
'@ | Set-Content "$fixture/scripts/lib.ps1"
function Run-Deploy($options = @{}, $binaries = @('wakey', 'wakey-agent'), $buildExit = 0, $execute = $false) {
$global:DeployTest = @{
Build = @(); BuildExit = $buildExit; Execute = $execute
Uploads = [System.Collections.Generic.List[object]]::new()
Command = $null
}
$output = "$fixture/target/test/release"
if (Test-Path $output) { Remove-Item -Recurse -Force $output }
New-Item -ItemType Directory -Path $output -Force | Out-Null
foreach ($binary in $binaries) { New-Item -ItemType File -Path "$output/$binary" | Out-Null }
& "$fixture/scripts/dev_push.ps1" -Pass test-only -HostName router -Target test -Quiet:$false @options
}
Run-Deploy
Assert ($global:DeployTest.Uploads.Count -eq 5) 'Default deploy must upload both binaries and three support files'
Assert (($global:DeployTest.Build -join ' ') -eq 'build --release --target test -p wakey -p wakey-agent') 'Default build changed'
Assert ($global:DeployTest.Command.Contains("'/root/.bin/wakey.tmp' '/root/.bin/wakey' '0'")) 'CLI must not restart before agent replacement'
Assert ($global:DeployTest.Command.Contains("'/root/.bin/wakey-agent.tmp' '/root/.bin/wakey-agent' '1'")) 'Agent must restart by default'
Assert ($global:DeployTest.Body -ceq (Get-Content -Raw "$repo/scripts/remote_deploy_wakey.sh")) 'Deployment must stream the checked-in file unchanged'
Run-Deploy @{ BinariesOnly = $true }
Assert ($global:DeployTest.Uploads.Count -eq 2) 'Binary-only deploy uploaded support files'
Assert (-not $global:DeployTest.Command.Contains('--install')) 'Binary-only deploy contains support installation'
foreach ($package in @('wakey', 'wakey-agent')) {
Run-Deploy @{ BinariesOnly = $true; Package = @($package, $package); Restart = $false } @($package)
Assert ($global:DeployTest.Uploads.Count -eq 1) 'Single-package deploy uploaded another file'
Assert (($global:DeployTest.Build -join ' ') -eq "build --release --target test -p $package") 'Package selection or deduplication failed'
Assert ($global:DeployTest.Command.Contains("'/root/.bin/$package.tmp' '/root/.bin/$package' '0'")) 'Restart override lost'
}
Run-Deploy @{ SkipInitScript = $true }
Assert ($global:DeployTest.Uploads.Count -eq 4) 'SkipInitScript must still upload hooks'
Run-Deploy @{ BinariesOnly = $true; Package = @('wakey-agent'); AgentRemotePath = "/tmp/owner's bin/agent" } @('wakey-agent')
Assert ($global:DeployTest.Command.Contains('owner')) 'Custom path lost'
# Execute only binary replacement inside the temporary fixture, with restart
# disabled. This also checks that the helper's exit doesn't skip binary two.
$cliPath = "$fixture/installed cli"
$agentPath = "$fixture/owner's agent"
Run-Deploy @{ BinariesOnly = $true; Restart = $false; RemotePath = $cliPath; AgentRemotePath = $agentPath } @('wakey', 'wakey-agent') 0 $true
Assert ((Test-Path -LiteralPath $cliPath) -and (Test-Path -LiteralPath $agentPath)) 'Streamed helper did not install both binaries'
Assert (-not (Test-Path -LiteralPath "$agentPath.tmp")) 'Temporary upload was not moved'
$supportTemp = "$fixture/support.tmp"
$supportDest = "$fixture/hook dir/owner's hook"
[IO.File]::WriteAllText($supportTemp, "#!/bin/sh`r`necho hook`r`n")
& sh "$fixture/scripts/remote_deploy_wakey.sh" --batch --install $supportTemp $supportDest
Assert ($LASTEXITCODE -eq 0) 'Support installation failed'
Assert (-not (Get-Content -Raw -LiteralPath $supportDest).Contains("`r")) 'Support CRLF was not normalized'
& sh -c 'test -x "$1"' sh $supportDest
Assert ($LASTEXITCODE -eq 0) 'Installed support file is not executable'
New-Item -ItemType File -Path "$fixture/legacy.tmp" | Out-Null
& sh "$fixture/scripts/remote_deploy_wakey.sh" "$fixture/legacy.tmp" "$fixture/legacy" 0
Assert ($LASTEXITCODE -eq 0 -and (Test-Path "$fixture/legacy")) 'Original helper invocation broke'
$failed = $false
try { Run-Deploy @{} @() 1 } catch { $failed = $_.Exception.Message -like '*build failed*' }
Assert $failed 'Build failure did not abort deployment'
Assert ($global:DeployTest.Uploads.Count -eq 0 -and $null -eq $global:DeployTest.Command) 'Failed build attempted deployment'
Write-Host 'dev_push tests passed'
} finally {
Remove-Variable DeployTest -Scope Global -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $fixture
}
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env pwsh
# Run with pwsh -NoProfile -File scripts/tests/ssh_stdin.ps1 on Linux/WSL.
$ErrorActionPreference = 'Stop'
. "$PSScriptRoot/../lib.ps1"
# Exercise the real native-command pipeline without contacting a server.
$body = "printf '%s\n' `"owner's directory`"`nprintf '%s\n' 'second line'"
$output = @(Invoke-Ext -Exe sh -Arguments @('-s') -Label test -Stdin $body)
if (($output -join '|') -cne "owner's directory|second line") { throw 'stdin script changed in transit' }
$output = @(Invoke-Ext -Exe sh -Arguments @('-c', 'printf command-only') -Label test)
if (($output -join '') -cne 'command-only') { throw 'Command-only execution changed' }
$failed = $false
try { Invoke-Ext -Exe sh -Arguments @('-s') -Label test -Stdin 'exit 7' } catch { $failed = $true }
if (-not $failed) { throw 'stdin execution swallowed a failure' }
# Test SSH argument and stdin forwarding independently of native SSH availability.
function Test-IsWsl { return $true }
function Get-SshpassCommand { return $script:Sshpass }
function Invoke-Ext {
param($Exe, $Arguments, $Label, [string]$Stdin)
$script:Call = @{ Exe = $Exe; Arguments = $Arguments; Stdin = $Stdin; HasStdin = $PSBoundParameters.ContainsKey('Stdin') }
}
$script:Sshpass = $null
Invoke-Ssh -Cmd 'sh -s' -User root -Remote test -Stdin "printf ok`r`n"
if ($script:Call.Exe -ne 'ssh' -or $script:Call.Stdin.Contains("`r") -or -not $script:Call.HasStdin) {
throw 'SSH stdin was not normalized and forwarded'
}
if (($script:Call.Arguments[-2..-1] -join '|') -ne 'root@test|sh -s') { throw 'SSH command changed' }
Invoke-Ssh -Cmd 'true' -User root -Remote test
if ($script:Call.HasStdin) { throw 'Command-only SSH unexpectedly redirects stdin' }
$script:Sshpass = '/fake/sshpass'
Invoke-Ssh -Cmd 'sh -s' -User root -Remote test -Pass test-only -Stdin $body
if ($script:Call.Exe -ne '/fake/sshpass' -or $script:Call.Stdin -cne $body) { throw 'sshpass lost stdin' }
Write-Host 'SSH stdin tests passed'