Files
KDL_WORK/kdl-wasm/web/scripts/deploy-demo-package.ps1
2026-06-28 20:58:18 +08:00

185 lines
5.9 KiB
PowerShell

param(
[string]$ZipPath,
[string]$Target = "ubuntu@82.156.24.101",
[string]$RemoteRoot = "/opt/kdl-olp-demo/8095",
[string]$BaseUrl = "https://82.156.24.101:8095",
[switch]$Sudo,
[switch]$NoSudo,
[switch]$SkipSmoke
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..\..")
$DeployRoot = Join-Path $RepoRoot "kdl-wasm\web\test-results\deploy"
if ([string]::IsNullOrWhiteSpace($ZipPath)) {
$ZipPath = Get-ChildItem -LiteralPath $DeployRoot -Filter "kdl-olp-demo-*.zip" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1 -ExpandProperty FullName
}
if ([string]::IsNullOrWhiteSpace($ZipPath) -or -not (Test-Path -LiteralPath $ZipPath)) {
throw "Demo zip not found. Run: npm run demo:build"
}
$ZipPath = (Resolve-Path -LiteralPath $ZipPath).Path
$ZipName = Split-Path -Leaf $ZipPath
if ($ZipName -notmatch '^kdl-olp-demo-(.+)\.zip$') {
throw "Zip name must match kdl-olp-demo-<release_id>.zip: $ZipName"
}
$ReleaseId = $Matches[1]
$RemoteZip = "/tmp/$ZipName"
$RemoteRelease = "$RemoteRoot/releases/$ReleaseId"
function Invoke-Remote {
param([string]$Command)
ssh -o StrictHostKeyChecking=accept-new $Target $Command
if ($LASTEXITCODE -ne 0) {
throw "Remote command failed: $Command"
}
}
function Remote-Cmd {
param([string]$Command)
if ($script:UseSudo) {
return "sudo sh -lc " + (Quote-Sh $Command)
}
return "sh -lc " + (Quote-Sh $Command)
}
function Quote-Sh {
param([string]$Value)
return "'" + ($Value -replace "'", "'\''") + "'"
}
function Get-RemoteParent {
param([string]$Path)
$Normalized = $Path.TrimEnd("/")
$Index = $Normalized.LastIndexOf("/")
if ($Index -le 0) {
return "/"
}
return $Normalized.Substring(0, $Index)
}
$RemoteParent = Get-RemoteParent $RemoteRoot
Write-Host "Deploying $ZipName to ${Target}:$RemoteRoot"
scp -o StrictHostKeyChecking=accept-new $ZipPath "${Target}:$RemoteZip"
if ($LASTEXITCODE -ne 0) {
throw "scp upload failed"
}
$script:UseSudo = $false
if ($Sudo) {
$script:UseSudo = $true
} elseif (-not $NoSudo) {
ssh -o StrictHostKeyChecking=accept-new $Target "test -w $(Quote-Sh $RemoteParent)"
if ($LASTEXITCODE -ne 0) {
ssh -o StrictHostKeyChecking=accept-new $Target "sudo -n true"
if ($LASTEXITCODE -eq 0) {
$script:UseSudo = $true
} else {
throw "Remote root is not writable and passwordless sudo is unavailable. Re-run with a writable -RemoteRoot or configure sudo."
}
}
}
$prepare = @(
"set -eu"
"mkdir -p $(Quote-Sh "$RemoteRoot/releases")"
"rm -rf $(Quote-Sh $RemoteRelease)"
"mkdir -p $(Quote-Sh $RemoteRelease)"
"unzip -q -o $(Quote-Sh $RemoteZip) -d $(Quote-Sh $RemoteRelease)"
"test -f $(Quote-Sh "$RemoteRelease/index.html")"
"test -f $(Quote-Sh "$RemoteRelease/demo-manifest.json")"
"test -f $(Quote-Sh "$RemoteRelease/app/virtual-controller.html")"
"if [ -L $(Quote-Sh "$RemoteRoot/current") ] || [ -e $(Quote-Sh "$RemoteRoot/current") ]; then rm -f $(Quote-Sh "$RemoteRoot/previous"); ln -sfn `"`$(readlink -f $(Quote-Sh "$RemoteRoot/current"))`" $(Quote-Sh "$RemoteRoot/previous"); fi"
"ln -sfn $(Quote-Sh "$RemoteRelease") $(Quote-Sh "$RemoteRoot/current")"
"rm -f $(Quote-Sh $RemoteZip)"
) -join "; "
Invoke-Remote (Remote-Cmd $prepare)
$restart = "if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files kdl-site-8095.service >/dev/null 2>&1; then systemctl restart kdl-site-8095; fi"
Invoke-Remote (Remote-Cmd $restart)
$smoke = @{
release_id = $ReleaseId
zip = $ZipPath
target = $Target
remote_root = $RemoteRoot
remote_release = $RemoteRelease
base_url = $BaseUrl
deployed_at = (Get-Date).ToUniversalTime().ToString("o")
urls = @()
}
if (-not $SkipSmoke) {
$nodeSmoke = @"
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const baseUrl = "$BaseUrl";
const get = async (path) => {
const response = await fetch(baseUrl + path);
const buffer = Buffer.from(await response.arrayBuffer());
if (response.status !== 200) {
throw new Error(path + " returned " + response.status);
}
return {
path,
status: response.status,
bytes: buffer.length,
text: buffer.toString("utf8")
};
};
const manifestResponse = await get("/demo-manifest.json");
const manifest = JSON.parse(manifestResponse.text);
const jobId = manifest.job_id;
const paths = [
"/",
"/demo-manifest.json",
"/spec-programs/manifest.json",
"/app/virtual-controller.html",
"/test-results/abb120-spec/" + jobId + "/report.json",
"/test-results/abb120-spec/" + jobId + "/report.html",
"/test-results/virtual-controller/virtual-controller-desktop.png",
"/test-results/virtual-controller/virtual-controller-mobile.png"
];
const urls = [];
for (const path of paths) {
const result = path === "/demo-manifest.json" ? manifestResponse : await get(path);
urls.push({ url: baseUrl + path, status: result.status, bytes: result.bytes });
}
const reportResponse = await get("/test-results/abb120-spec/" + jobId + "/report.json");
const report = JSON.parse(reportResponse.text);
if (Number(manifest.program_count) !== 19) {
throw new Error("Expected program_count=19, got " + manifest.program_count);
}
if (Number(report.summary.fail) !== 0) {
throw new Error("Expected summary.fail=0, got " + report.summary.fail);
}
console.log(JSON.stringify({
job_id: jobId,
program_count: Number(manifest.program_count),
summary_fail: Number(report.summary.fail),
urls
}));
"@
$nodeOutput = $nodeSmoke | node --input-type=module
if ($LASTEXITCODE -ne 0) {
throw "Smoke check failed"
}
$nodeSmokeResult = $nodeOutput | Select-Object -Last 1 | ConvertFrom-Json
$smoke.job_id = $nodeSmokeResult.job_id
$smoke.program_count = [int]$nodeSmokeResult.program_count
$smoke.summary_fail = [int]$nodeSmokeResult.summary_fail
$smoke.urls = @($nodeSmokeResult.urls)
}
$SmokePath = Join-Path $DeployRoot "kdl-olp-demo-$ReleaseId.deploy.json"
$smoke | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $SmokePath -Encoding UTF8
$smoke | ConvertTo-Json -Depth 8