$ErrorActionPreference = 'Stop' $project = Split-Path -Parent $PSScriptRoot $binary = Join-Path $project 'build/linux-arm64/tera-clone-demo-web' $hash = (Get-FileHash -LiteralPath $binary -Algorithm SHA256).Hash.ToLowerInvariant() $script = @' #!/bin/sh set -eu stage=$(mktemp -d /tmp/tera-compact-ports.XXXXXX) wget -q -T 30 -O "$stage/web" http://192.168.0.100:9001/web echo '__HASH__ '"$stage/web" | sha256sum -c - backup=/userdata/tera-backups/compact-port-selector-$(date +%Y%m%d-%H%M%S) mkdir "$backup" cp -p /usr/bin/tera-clone-demo-web "$backup/web" echo "BACKUP=$backup" rollback() { trap - EXIT /etc/init.d/S61teraweb stop || true cp -p "$backup/web" /usr/bin/tera-clone-demo-web.restore mv /usr/bin/tera-clone-demo-web.restore /usr/bin/tera-clone-demo-web /etc/init.d/S61teraweb start || true echo COMPACT_PORT_ROLLED_BACK } trap rollback EXIT cp "$stage/web" /usr/bin/tera-clone-demo-web.next chmod 755 /usr/bin/tera-clone-demo-web.next chown root:root /usr/bin/tera-clone-demo-web.next /etc/init.d/S61teraweb stop mv /usr/bin/tera-clone-demo-web.next /usr/bin/tera-clone-demo-web /etc/init.d/S61teraweb start ready=0 for n in $(seq 1 30); do if wget -q -T 2 -O "$stage/status" http://127.0.0.1/login/backend-status && grep -q '"ready":true' "$stage/status"; then ready=1; break; fi sleep 1 done [ "$ready" = 1 ] wget -q -T 5 -O "$stage/style" http://127.0.0.1/static/css/style.css grep -q 'clamp(510px, 31vw, 550px)' "$stage/style" grep -q 'min-height: 62px;' "$stage/style" cat "$stage/status" sha256sum /usr/bin/tera-clone-demo-web sync trap - EXIT echo COMPACT_PORT_DEPLOY_OK '@ $script = $script.Replace('__HASH__', $hash).Replace("`r`n", "`n") + "`n" $rule = 'TERA-compact-ports-' + [guid]::NewGuid().ToString('N') $listener = New-Object System.Net.HttpListener $listener.Prefixes.Add('http://192.168.0.100:9001/') $serial = New-Object System.IO.Ports.SerialPort 'COM4',115200,'None',8,'One' $serial.Encoding = [Text.Encoding]::UTF8 Start-Transcript -Path (Join-Path $project 'runtime/compact-port-selector-transfer.log') -Force | Out-Null try { New-NetFirewallRule -DisplayName $rule -Direction Inbound -Action Allow -Protocol TCP -LocalPort 9001 -RemoteAddress 192.168.0.223 | Out-Null $listener.Start() $serial.Open() $serial.DiscardInBuffer() $serial.WriteLine('wget -q -T 15 -O /tmp/deploy-compact-port-selector.sh http://192.168.0.100:9001/deploy.sh && sh /tmp/deploy-compact-port-selector.sh') $deadline = [DateTime]::UtcNow.AddSeconds(150) $output = '' $pending = $listener.GetContextAsync() while ([DateTime]::UtcNow -lt $deadline) { if ($pending.IsCompleted) { $context = $pending.GetAwaiter().GetResult() $bytes = $null if ($context.Request.RemoteEndPoint.Address.ToString() -ne '192.168.0.223') { $context.Response.StatusCode = 403 } else { switch ($context.Request.Url.AbsolutePath) { '/deploy.sh' { $bytes = [Text.Encoding]::UTF8.GetBytes($script) } '/web' { $bytes = [IO.File]::ReadAllBytes($binary) } default { $context.Response.StatusCode = 404 } } } if ($null -ne $bytes) { $context.Response.ContentLength64 = $bytes.Length $context.Response.OutputStream.Write($bytes, 0, $bytes.Length) } $context.Response.Close() $pending = $listener.GetContextAsync() } $chunk = $serial.ReadExisting() if ($chunk) { $output += $chunk; Write-Host -NoNewline $chunk } if ($output.Contains('COMPACT_PORT_ROLLED_BACK')) { throw 'Deployment rolled back.' } if ($output.Contains('COMPACT_PORT_DEPLOY_OK')) { break } Start-Sleep -Milliseconds 100 } if (-not $output.Contains('COMPACT_PORT_DEPLOY_OK') -or -not $output.Contains($hash)) { throw 'Deployment verification incomplete; inspect console.' } $response = Invoke-WebRequest -UseBasicParsing -TimeoutSec 10 'http://192.168.0.223/static/css/style.css?v=20260916-compact-port-selector-1' $localCSS = [IO.File]::ReadAllText((Join-Path $project 'frontend/web/static/css/style.css')) if ($response.Content -cne $localCSS) { throw 'Served CSS differs from local CSS.' } Write-Output "VERIFIED deployed SHA256=$hash and exact CSS content" } finally { if ($serial.IsOpen) { $serial.Close() } $serial.Dispose() $listener.Close() Remove-NetFirewallRule -DisplayName $rule -ErrorAction SilentlyContinue Stop-Transcript | Out-Null }